发布:4.0.3

This commit is contained in:
若汝棋茗
2025-12-13 15:57:27 +08:00
parent 89655b8f62
commit 601ae76387
15 changed files with 206 additions and 22 deletions

View File

@@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<BaseVersion>4.0.2</BaseVersion> <BaseVersion>4.0.3</BaseVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'"> <PropertyGroup Condition="'$(Configuration)'=='Release'">

View File

@@ -182,7 +182,7 @@ public class WebSocketDmtpService : ConnectableService<WebSocketDmtpSessionClien
/// </summary> /// </summary>
/// <returns>异步任务。</returns> /// <returns>异步任务。</returns>
/// <exception cref="NotSupportedException">抛出不支持异常。</exception> /// <exception cref="NotSupportedException">抛出不支持异常。</exception>
public override Task StartAsync() public override Task StartAsync(CancellationToken cancellationToken = default)
{ {
throw new NotSupportedException("此服务的生命周期跟随主Host"); throw new NotSupportedException("此服务的生命周期跟随主Host");
} }

View File

@@ -169,14 +169,22 @@ public abstract class HttpDmtpSessionClient : HttpSessionClient, IHttpDmtpSessio
/// <inheritdoc/> /// <inheritdoc/>
protected override async Task OnTcpClosed(ClosedEventArgs e) protected override async Task OnTcpClosed(ClosedEventArgs e)
{ {
await this.OnDmtpClosed(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (this.m_dmtpActor!=null)
{
await this.OnDmtpClosed(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
}
await base.OnTcpClosed(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await base.OnTcpClosed(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override async Task OnTcpClosing(ClosingEventArgs e) protected override async Task OnTcpClosing(ClosingEventArgs e)
{ {
await this.PluginManager.RaiseAsync(typeof(IDmtpClosingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (this.m_dmtpActor != null)
{
await this.PluginManager.RaiseIDmtpClosingPluginAsync(this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
}
await base.OnTcpClosing(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await base.OnTcpClosing(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
} }
@@ -187,7 +195,7 @@ public abstract class HttpDmtpSessionClient : HttpSessionClient, IHttpDmtpSessio
{ {
if (!await this.m_dmtpActor.InputReceivedData(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) if (!await this.m_dmtpActor.InputReceivedData(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext))
{ {
await this.PluginManager.RaiseAsync(typeof(IDmtpReceivedPlugin), this.Resolver, this, new DmtpMessageEventArgs(message)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await this.PluginManager.RaiseIDmtpReceivedPluginAsync(this.Resolver, this, new DmtpMessageEventArgs(message)).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
} }
} }
await base.OnTcpReceived(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await base.OnTcpReceived(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext);

View File

@@ -13,7 +13,7 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using TouchSocket.Core.AspNetCore; using TouchSocket.Core.AspNetCore;
using TouchSocket.Hosting; using TouchSocket.Hosting;
using TouchSocket.Hosting.Sockets.HostService; using TouchSocket.Hosting.HostedServices;
using TouchSocket.Sockets; using TouchSocket.Sockets;
namespace Microsoft.Extensions.DependencyInjection; namespace Microsoft.Extensions.DependencyInjection;
@@ -143,6 +143,20 @@ public static class ServiceCollectionExtensions
return AddSetupConfigObjectHostedService<ServiceHost<TObjectService>, TObjectService, TObjectImpService>(services, actionConfig); return AddSetupConfigObjectHostedService<ServiceHost<TObjectService>, TObjectService, TObjectImpService>(services, actionConfig);
} }
public static IServiceCollection AddClientHostedService<TObjectClient, [DynamicallyAccessedMembers(AOT.Container)] TClientImpService>(this IServiceCollection services, Action<TouchSocketConfig> actionConfig)
where TObjectClient : class, ISetupConfigObject, IConnectableClient,IClosableClient
where TClientImpService : class, TObjectClient
{
return AddSetupConfigObjectHostedService<ClientHost<TObjectClient>, TObjectClient, TClientImpService>(services, actionConfig);
}
public static IServiceCollection AddSetupConfigObjectHostedService<TObject, [DynamicallyAccessedMembers(AOT.Container)] TObjectImp>(this IServiceCollection services, Action<TouchSocketConfig> actionConfig)
where TObject : class, ISetupConfigObject
where TObjectImp : class, TObject
{
return AddSetupConfigObjectHostedService<SetupConfigObjectHostedService<TObject>, TObject, TObjectImp>(services, actionConfig);
}
/// <summary> /// <summary>
/// 添加配置对象托管服务 /// 添加配置对象托管服务
/// </summary> /// </summary>

View File

@@ -0,0 +1,50 @@
// ------------------------------------------------------------------------------
// 此代码版权除特别声明或在XREF结尾的命名空间的代码归作者本人若汝棋茗所有
// 源代码使用协议遵循本仓库的开源协议及附加协议若本仓库没有设置则按MIT开源协议授权
// CSDN博客https://blog.csdn.net/qq_40374647
// 哔哩哔哩视频https://space.bilibili.com/94253567
// Gitee源代码仓库https://gitee.com/RRQM_Home
// Github源代码仓库https://github.com/RRQM
// API首页https://touchsocket.net/
// 交流QQ群234762506
// 感谢您的下载和使用
// ------------------------------------------------------------------------------
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using TouchSocket.Resources;
using TouchSocket.Sockets;
namespace TouchSocket.Hosting.HostedServices;
internal class ClientHost<TService> : SetupConfigObjectHostedService<TService> where TService : ISetupConfigObject, IConnectableClient,IClosableClient
{
private ILogger<TService> m_logger;
protected override void OnSetResolver(IResolver resolver)
{
base.OnSetResolver(resolver);
this.m_logger = resolver.GetService<ILogger<TService>>();
}
public override async Task StartAsync(CancellationToken cancellationToken)
{
try
{
await base.StartAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
await this.ConfigObject.ConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
this.m_logger.LogInformation("{Message}", TouchSocketHostingResource.HostServerStarted);
}
catch (Exception ex)
{
this.m_logger.LogError(ex, "{Message}", ex.Message);
}
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
await this.ConfigObject.CloseAsync("服务停止",cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
await base.StopAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
}
}

View File

@@ -15,7 +15,7 @@ using Microsoft.Extensions.Logging;
using TouchSocket.Resources; using TouchSocket.Resources;
using TouchSocket.Sockets; using TouchSocket.Sockets;
namespace TouchSocket.Hosting.Sockets.HostService; namespace TouchSocket.Hosting.HostedServices;
internal class ServiceHost<TService> : SetupConfigObjectHostedService<TService> where TService : ISetupConfigObject, IServiceBase internal class ServiceHost<TService> : SetupConfigObjectHostedService<TService> where TService : ISetupConfigObject, IServiceBase
{ {
@@ -32,7 +32,7 @@ internal class ServiceHost<TService> : SetupConfigObjectHostedService<TService>
try try
{ {
await base.StartAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await base.StartAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
await this.ConfigObject.StartAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); await this.ConfigObject.StartAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
this.m_logger.LogInformation("{Message}", TouchSocketHostingResource.HostServerStarted); this.m_logger.LogInformation("{Message}", TouchSocketHostingResource.HostServerStarted);
} }
@@ -44,6 +44,7 @@ internal class ServiceHost<TService> : SetupConfigObjectHostedService<TService>
public override async Task StopAsync(CancellationToken cancellationToken) public override async Task StopAsync(CancellationToken cancellationToken)
{ {
await this.ConfigObject.StopAsync(); await this.ConfigObject.StopAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
await base.StopAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
} }
} }

View File

@@ -18,7 +18,7 @@ namespace TouchSocket.Hosting;
/// <summary> /// <summary>
/// SetupObjectHostedService /// SetupObjectHostedService
/// </summary> /// </summary>
public abstract class SetupConfigObjectHostedService<TConfigObject> : IHostedService where TConfigObject : ISetupConfigObject public class SetupConfigObjectHostedService<TConfigObject> : IHostedService where TConfigObject : ISetupConfigObject
{ {
private TouchSocketConfig m_config; private TouchSocketConfig m_config;
private TConfigObject m_configObject; private TConfigObject m_configObject;
@@ -81,5 +81,9 @@ public abstract class SetupConfigObjectHostedService<TConfigObject> : IHostedSer
} }
/// <inheritdoc/> /// <inheritdoc/>
public abstract Task StopAsync(CancellationToken cancellationToken); public virtual Task StopAsync(CancellationToken cancellationToken)
{
this.m_configObject.Dispose();
return Task.CompletedTask;
}
} }

View File

@@ -0,0 +1,107 @@
// ------------------------------------------------------------------------------
// 此代码版权除特别声明或在XREF结尾的命名空间的代码归作者本人若汝棋茗所有
// 源代码使用协议遵循本仓库的开源协议及附加协议若本仓库没有设置则按MIT开源协议授权
// CSDN博客https://blog.csdn.net/qq_40374647
// 哔哩哔哩视频https://space.bilibili.com/94253567
// Gitee源代码仓库https://gitee.com/RRQM_Home
// Github源代码仓库https://github.com/RRQM
// API首页https://touchsocket.net/
// 交流QQ群234762506
// 感谢您的下载和使用
// ------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Mail;
using System.Text;
using System.Threading.Tasks;
using TouchSocket.Sockets;
namespace TouchSocket.Http.WebSockets;
/// <summary>
/// 提供用于为 <see cref="ReconnectionOption{TClient}"/> 配置 WebSocket 心跳检查的扩展方法。
/// </summary>
public static class ReconnectionOptionsExtension
{
/// <summary>
/// 为 <see cref="ReconnectionOption{TClient}"/> 设置一个基于活动时间与 Ping 的检查动作。
/// </summary>
/// <typeparam name="TClient">实现了 <see cref="IConnectableClient"/>, <see cref="IOnlineClient"/>, <see cref="IDependencyClient"/>, <see cref="IWebSocketClient"/> 的客户端类型。</typeparam>
/// <param name="reconnectionOption">要配置的 <see cref="ReconnectionOption{TClient}"/> 实例。</param>
/// <param name="activeTimeSpan">在此时间范围内若有活动则跳过心跳检测,默认 3 秒。</param>
/// <param name="pingTimeout">执行 Ping 与 Close 操作时的超时时间,默认 5 秒。</param>
/// <exception cref="ArgumentOutOfRangeException">当 <paramref name="activeTimeSpan"/> 或 <paramref name="pingTimeout"/> 小于或等于零时抛出。</exception>
public static void UseWebSocketCheckAction<TClient>(
this ReconnectionOption<TClient> reconnectionOption,
TimeSpan? activeTimeSpan = null,
TimeSpan? pingTimeout = null)
where TClient : IConnectableClient, IOnlineClient, IDependencyClient, IWebSocketClient
{
ThrowHelper.ThrowIfNull(reconnectionOption, nameof(reconnectionOption));
var span = activeTimeSpan ?? TimeSpan.FromSeconds(3);
var timeout = pingTimeout ?? TimeSpan.FromSeconds(5);
// 验证时间参数的有效性
if (span <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(activeTimeSpan), "活动时间间隔必须大于零");
}
if (timeout <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(pingTimeout), "Ping超时时间必须大于零");
}
reconnectionOption.CheckAction = async (client) =>
{
// 第1步快速在线状态检查
// 如果客户端已经离线无需进一步检查直接返回Dead状态
if (!client.Online)
{
return ConnectionCheckResult.Dead;
}
// 第2步活动时间检查
// 如果客户端在指定时间内有活动,说明连接正常,跳过本次心跳检查
var lastActiveTime = client.GetLastActiveTime();
var timeSinceLastActivity = DateTimeOffset.UtcNow - lastActiveTime;
if (timeSinceLastActivity < span)
{
return ConnectionCheckResult.Skip;
}
// 第3步主动心跳检查
// 通过Ping操作验证连接的实际可用性
try
{
using var pingCts = new CancellationTokenSource(timeout);
var pingResult = await client.PingAsync(pingCts.Token).ConfigureAwait(false);
if (pingResult.IsSuccess)
{
return ConnectionCheckResult.Alive;
}
using var closeCts = new CancellationTokenSource(timeout);
var closeResult = await client.CloseAsync("心跳插件ping失败主动断开连接", closeCts.Token).ConfigureAwait(false);
return ConnectionCheckResult.Dead;
}
catch (OperationCanceledException)
{
// Ping超时认为连接已死
return ConnectionCheckResult.Dead;
}
catch
{
// 其他异常也认为连接不可用
return ConnectionCheckResult.Dead;
}
};
}
}

View File

@@ -119,7 +119,7 @@ public abstract class NamedPipeServiceBase<TClient> : ConnectableService<TClient
} }
} }
/// <inheritdoc/> /// <inheritdoc/>
public override async Task StartAsync() public override async Task StartAsync(CancellationToken cancellationToken = default)
{ {
this.ThrowIfConfigIsNull(); this.ThrowIfConfigIsNull();

View File

@@ -24,7 +24,7 @@ public abstract class ServiceBase : SetupConfigObject, IServiceBase
public abstract ServerState ServerState { get; } public abstract ServerState ServerState { get; }
/// <inheritdoc/> /// <inheritdoc/>
public abstract Task StartAsync(); public abstract Task StartAsync(CancellationToken cancellationToken=default);
/// <inheritdoc/> /// <inheritdoc/>
public abstract Task<Result> StopAsync(CancellationToken cancellationToken = default); public abstract Task<Result> StopAsync(CancellationToken cancellationToken = default);

View File

@@ -139,7 +139,7 @@ public abstract class TcpServiceBase<TClient> : ConnectableService<TClient>, ITc
} }
/// <inheritdoc/> /// <inheritdoc/>
public override async Task StartAsync() public override async Task StartAsync(CancellationToken cancellationToken=default)
{ {
this.ThrowIfDisposed(); this.ThrowIfDisposed();
this.ThrowIfConfigIsNull(); this.ThrowIfConfigIsNull();

View File

@@ -140,7 +140,7 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase
} }
/// <inheritdoc/> /// <inheritdoc/>
public override async Task StartAsync() public override async Task StartAsync(CancellationToken cancellationToken = default)
{ {
this.ThrowIfDisposed(); this.ThrowIfDisposed();
try try

View File

@@ -87,7 +87,7 @@ public static class ServiceExtension
} }
/// <inheritdoc cref="IServiceBase.StartAsync"/> /// <inheritdoc cref="IServiceBase.StartAsync"/>
public static async Task StartAsync<TService>(this TService service, IPHost iPHost) where TService : IUdpSession public static async Task StartAsync<TService>(this TService service, IPHost iPHost, CancellationToken cancellationToken = default) where TService : IUdpSession
{ {
TouchSocketConfig config; TouchSocketConfig config;
if (service.Config == null) if (service.Config == null)
@@ -101,7 +101,7 @@ public static class ServiceExtension
config = service.Config; config = service.Config;
config.SetBindIPHost(iPHost); config.SetBindIPHost(iPHost);
} }
await service.StartAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); await service.StartAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
} }
#endregion Udp #endregion Udp

View File

@@ -184,11 +184,11 @@ public static class TouchSocketConfigExtension
/// <typeparam name="TClient"></typeparam> /// <typeparam name="TClient"></typeparam>
/// <param name="config"></param> /// <param name="config"></param>
/// <returns></returns> /// <returns></returns>
public static async Task<TClient> BuildClientAsync<TClient>(this TouchSocketConfig config) where TClient : ISetupConfigObject, IConnectableClient, new() public static async Task<TClient> BuildClientAsync<TClient>(this TouchSocketConfig config, CancellationToken cancellationToken = default) where TClient : ISetupConfigObject, IConnectableClient, new()
{ {
var client = new TClient(); var client = new TClient();
await client.SetupAsync(config).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await client.SetupAsync(config).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
await client.ConnectAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); await client.ConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
return client; return client;
} }
@@ -198,11 +198,11 @@ public static class TouchSocketConfigExtension
/// <typeparam name="TService"></typeparam> /// <typeparam name="TService"></typeparam>
/// <param name="config"></param> /// <param name="config"></param>
/// <returns></returns> /// <returns></returns>
public static async Task<TService> BuildServiceAsync<TService>(this TouchSocketConfig config) where TService : IServiceBase, new() public static async Task<TService> BuildServiceAsync<TService>(this TouchSocketConfig config, CancellationToken cancellationToken = default) where TService : IServiceBase, new()
{ {
var service = new TService(); var service = new TService();
await service.SetupAsync(config).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await service.SetupAsync(config).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
await service.StartAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); await service.StartAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
return service; return service;
} }
#endregion #endregion

View File

@@ -31,7 +31,7 @@ public interface IServiceBase : ISetupConfigObject
/// 异步启动 /// 异步启动
/// </summary> /// </summary>
/// <exception cref="Exception">可能启动时遇到的异常</exception> /// <exception cref="Exception">可能启动时遇到的异常</exception>
Task StartAsync(); Task StartAsync(CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// 异步停止服务器 /// 异步停止服务器