diff --git a/TouchSocketVersion.props b/TouchSocketVersion.props index afe83fca2..91c6a2be0 100644 --- a/TouchSocketVersion.props +++ b/TouchSocketVersion.props @@ -1,7 +1,7 @@ - 4.0.2 + 4.0.3 diff --git a/src/TouchSocket.AspNetCore/Dmtp/Components/WebSocketDmtpService.cs b/src/TouchSocket.AspNetCore/Dmtp/Components/WebSocketDmtpService.cs index 4be4bd2c3..4670da429 100644 --- a/src/TouchSocket.AspNetCore/Dmtp/Components/WebSocketDmtpService.cs +++ b/src/TouchSocket.AspNetCore/Dmtp/Components/WebSocketDmtpService.cs @@ -182,7 +182,7 @@ public class WebSocketDmtpService : ConnectableService /// 异步任务。 /// 抛出不支持异常。 - public override Task StartAsync() + public override Task StartAsync(CancellationToken cancellationToken = default) { throw new NotSupportedException("此服务的生命周期跟随主Host"); } diff --git a/src/TouchSocket.Dmtp/Components/Http/HttpDmtpSessionClient.cs b/src/TouchSocket.Dmtp/Components/Http/HttpDmtpSessionClient.cs index 93fc5ed0a..74a56ba07 100644 --- a/src/TouchSocket.Dmtp/Components/Http/HttpDmtpSessionClient.cs +++ b/src/TouchSocket.Dmtp/Components/Http/HttpDmtpSessionClient.cs @@ -169,14 +169,22 @@ public abstract class HttpDmtpSessionClient : HttpSessionClient, IHttpDmtpSessio /// 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); } /// 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); } @@ -187,7 +195,7 @@ public abstract class HttpDmtpSessionClient : HttpSessionClient, IHttpDmtpSessio { 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); diff --git a/src/TouchSocket.Hosting/Extensions/ServiceCollectionExtensions.cs b/src/TouchSocket.Hosting/Extensions/ServiceCollectionExtensions.cs index b6d9b376d..ec8de3d42 100644 --- a/src/TouchSocket.Hosting/Extensions/ServiceCollectionExtensions.cs +++ b/src/TouchSocket.Hosting/Extensions/ServiceCollectionExtensions.cs @@ -13,7 +13,7 @@ using System.Diagnostics.CodeAnalysis; using TouchSocket.Core.AspNetCore; using TouchSocket.Hosting; -using TouchSocket.Hosting.Sockets.HostService; +using TouchSocket.Hosting.HostedServices; using TouchSocket.Sockets; namespace Microsoft.Extensions.DependencyInjection; @@ -143,6 +143,20 @@ public static class ServiceCollectionExtensions return AddSetupConfigObjectHostedService, TObjectService, TObjectImpService>(services, actionConfig); } + public static IServiceCollection AddClientHostedService(this IServiceCollection services, Action actionConfig) + where TObjectClient : class, ISetupConfigObject, IConnectableClient,IClosableClient + where TClientImpService : class, TObjectClient + { + return AddSetupConfigObjectHostedService, TObjectClient, TClientImpService>(services, actionConfig); + } + + public static IServiceCollection AddSetupConfigObjectHostedService(this IServiceCollection services, Action actionConfig) + where TObject : class, ISetupConfigObject + where TObjectImp : class, TObject + { + return AddSetupConfigObjectHostedService, TObject, TObjectImp>(services, actionConfig); + } + /// /// 添加配置对象托管服务 /// diff --git a/src/TouchSocket.Hosting/HostedServices/ClientHost.cs b/src/TouchSocket.Hosting/HostedServices/ClientHost.cs new file mode 100644 index 000000000..5905e4fd7 --- /dev/null +++ b/src/TouchSocket.Hosting/HostedServices/ClientHost.cs @@ -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 : SetupConfigObjectHostedService where TService : ISetupConfigObject, IConnectableClient,IClosableClient +{ + private ILogger m_logger; + + protected override void OnSetResolver(IResolver resolver) + { + base.OnSetResolver(resolver); + this.m_logger = resolver.GetService>(); + } + + 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); + } +} \ No newline at end of file diff --git a/src/TouchSocket.Hosting/HostedServices/ServiceHost.cs b/src/TouchSocket.Hosting/HostedServices/ServiceHost.cs index 2212992d7..e486942da 100644 --- a/src/TouchSocket.Hosting/HostedServices/ServiceHost.cs +++ b/src/TouchSocket.Hosting/HostedServices/ServiceHost.cs @@ -15,7 +15,7 @@ using Microsoft.Extensions.Logging; using TouchSocket.Resources; using TouchSocket.Sockets; -namespace TouchSocket.Hosting.Sockets.HostService; +namespace TouchSocket.Hosting.HostedServices; internal class ServiceHost : SetupConfigObjectHostedService where TService : ISetupConfigObject, IServiceBase { @@ -32,7 +32,7 @@ internal class ServiceHost : SetupConfigObjectHostedService try { 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); } @@ -44,6 +44,7 @@ internal class ServiceHost : SetupConfigObjectHostedService 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); } } \ No newline at end of file diff --git a/src/TouchSocket.Hosting/SetupConfigObject/SetupConfigObjectHostedService.cs b/src/TouchSocket.Hosting/SetupConfigObject/SetupConfigObjectHostedService.cs index d2eab006c..5dc8bb9e2 100644 --- a/src/TouchSocket.Hosting/SetupConfigObject/SetupConfigObjectHostedService.cs +++ b/src/TouchSocket.Hosting/SetupConfigObject/SetupConfigObjectHostedService.cs @@ -18,7 +18,7 @@ namespace TouchSocket.Hosting; /// /// SetupObjectHostedService /// -public abstract class SetupConfigObjectHostedService : IHostedService where TConfigObject : ISetupConfigObject +public class SetupConfigObjectHostedService : IHostedService where TConfigObject : ISetupConfigObject { private TouchSocketConfig m_config; private TConfigObject m_configObject; @@ -81,5 +81,9 @@ public abstract class SetupConfigObjectHostedService : IHostedSer } /// - public abstract Task StopAsync(CancellationToken cancellationToken); + public virtual Task StopAsync(CancellationToken cancellationToken) + { + this.m_configObject.Dispose(); + return Task.CompletedTask; + } } \ No newline at end of file diff --git a/src/TouchSocket.Http/WebSockets/Extensions/ReconnectionOptionsExtension.cs b/src/TouchSocket.Http/WebSockets/Extensions/ReconnectionOptionsExtension.cs new file mode 100644 index 000000000..13201ba78 --- /dev/null +++ b/src/TouchSocket.Http/WebSockets/Extensions/ReconnectionOptionsExtension.cs @@ -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; + +/// +/// 提供用于为 配置 WebSocket 心跳检查的扩展方法。 +/// +public static class ReconnectionOptionsExtension +{ + + /// + /// 为 设置一个基于活动时间与 Ping 的检查动作。 + /// + /// 实现了 , , , 的客户端类型。 + /// 要配置的 实例。 + /// 在此时间范围内若有活动则跳过心跳检测,默认 3 秒。 + /// 执行 Ping 与 Close 操作时的超时时间,默认 5 秒。 + /// 小于或等于零时抛出。 + public static void UseWebSocketCheckAction( + this ReconnectionOption 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; + } + }; + } +} diff --git a/src/TouchSocket.NamedPipe/Components/NamedPipeServiceBaseT.cs b/src/TouchSocket.NamedPipe/Components/NamedPipeServiceBaseT.cs index 61d898d0d..7687e04f9 100644 --- a/src/TouchSocket.NamedPipe/Components/NamedPipeServiceBaseT.cs +++ b/src/TouchSocket.NamedPipe/Components/NamedPipeServiceBaseT.cs @@ -119,7 +119,7 @@ public abstract class NamedPipeServiceBase : ConnectableService - public override async Task StartAsync() + public override async Task StartAsync(CancellationToken cancellationToken = default) { this.ThrowIfConfigIsNull(); diff --git a/src/TouchSocket/Components/Base/ServiceBase.cs b/src/TouchSocket/Components/Base/ServiceBase.cs index 53a979548..7429b8206 100644 --- a/src/TouchSocket/Components/Base/ServiceBase.cs +++ b/src/TouchSocket/Components/Base/ServiceBase.cs @@ -24,7 +24,7 @@ public abstract class ServiceBase : SetupConfigObject, IServiceBase public abstract ServerState ServerState { get; } /// - public abstract Task StartAsync(); + public abstract Task StartAsync(CancellationToken cancellationToken=default); /// public abstract Task StopAsync(CancellationToken cancellationToken = default); diff --git a/src/TouchSocket/Components/Tcp/TcpServiceBaseT.cs b/src/TouchSocket/Components/Tcp/TcpServiceBaseT.cs index 2f757c727..8727d1b84 100644 --- a/src/TouchSocket/Components/Tcp/TcpServiceBaseT.cs +++ b/src/TouchSocket/Components/Tcp/TcpServiceBaseT.cs @@ -139,7 +139,7 @@ public abstract class TcpServiceBase : ConnectableService, ITc } /// - public override async Task StartAsync() + public override async Task StartAsync(CancellationToken cancellationToken=default) { this.ThrowIfDisposed(); this.ThrowIfConfigIsNull(); diff --git a/src/TouchSocket/Components/Udp/UdpSessionBase.cs b/src/TouchSocket/Components/Udp/UdpSessionBase.cs index 8499b6d68..1c51c529d 100644 --- a/src/TouchSocket/Components/Udp/UdpSessionBase.cs +++ b/src/TouchSocket/Components/Udp/UdpSessionBase.cs @@ -140,7 +140,7 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase } /// - public override async Task StartAsync() + public override async Task StartAsync(CancellationToken cancellationToken = default) { this.ThrowIfDisposed(); try diff --git a/src/TouchSocket/Extensions/ServiceExtension.cs b/src/TouchSocket/Extensions/ServiceExtension.cs index d795c8a4b..1a16c0b42 100644 --- a/src/TouchSocket/Extensions/ServiceExtension.cs +++ b/src/TouchSocket/Extensions/ServiceExtension.cs @@ -87,7 +87,7 @@ public static class ServiceExtension } /// - public static async Task StartAsync(this TService service, IPHost iPHost) where TService : IUdpSession + public static async Task StartAsync(this TService service, IPHost iPHost, CancellationToken cancellationToken = default) where TService : IUdpSession { TouchSocketConfig config; if (service.Config == null) @@ -101,7 +101,7 @@ public static class ServiceExtension config = service.Config; config.SetBindIPHost(iPHost); } - await service.StartAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await service.StartAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } #endregion Udp diff --git a/src/TouchSocket/Extensions/TouchSocketConfigExtension.cs b/src/TouchSocket/Extensions/TouchSocketConfigExtension.cs index 0bcd0abcf..deb2e04ca 100644 --- a/src/TouchSocket/Extensions/TouchSocketConfigExtension.cs +++ b/src/TouchSocket/Extensions/TouchSocketConfigExtension.cs @@ -184,11 +184,11 @@ public static class TouchSocketConfigExtension /// /// /// - public static async Task BuildClientAsync(this TouchSocketConfig config) where TClient : ISetupConfigObject, IConnectableClient, new() + public static async Task BuildClientAsync(this TouchSocketConfig config, CancellationToken cancellationToken = default) where TClient : ISetupConfigObject, IConnectableClient, new() { var client = new TClient(); await client.SetupAsync(config).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await client.ConnectAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await client.ConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return client; } @@ -198,11 +198,11 @@ public static class TouchSocketConfigExtension /// /// /// - public static async Task BuildServiceAsync(this TouchSocketConfig config) where TService : IServiceBase, new() + public static async Task BuildServiceAsync(this TouchSocketConfig config, CancellationToken cancellationToken = default) where TService : IServiceBase, new() { var service = new TService(); await service.SetupAsync(config).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await service.StartAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await service.StartAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return service; } #endregion 创建 diff --git a/src/TouchSocket/Interfaces/IServiceBase.cs b/src/TouchSocket/Interfaces/IServiceBase.cs index d7972c5e2..6426d76f3 100644 --- a/src/TouchSocket/Interfaces/IServiceBase.cs +++ b/src/TouchSocket/Interfaces/IServiceBase.cs @@ -31,7 +31,7 @@ public interface IServiceBase : ISetupConfigObject /// 异步启动 /// /// 可能启动时遇到的异常 - Task StartAsync(); + Task StartAsync(CancellationToken cancellationToken = default); /// /// 异步停止服务器