From 6fc57458a776339a99a493ba0b334e564e38980c Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Sat, 2 Aug 2025 10:48:31 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=96=B0=E8=AE=BE=E8=AE=A1=E4=BA=86Li?= =?UTF-8?q?bray.Json=20Api=E4=BB=A5=E5=8F=8A=20WebSocket=20=E7=9A=84?= =?UTF-8?q?=E4=BA=A4=E4=BA=92=E5=A4=84=E7=90=86=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Library/Api/IJsonProvider.cs | 31 +- Library/ScriptBaseFunc.cs | 9 +- Library/Utils/JsonHelper.cs | 1 - .../NewtonsoftJsonArrayToken.cs | 3 + .../NewtonsoftJsonObjectToken.cs | 3 + .../NewtonsoftJsonValueToken.cs | 3 + .../Attributes/SendAttribute.cs | 15 + .../Attributes/UseRequestAttribute.cs | 5 +- ...tribute.cs => WebSocketModuleAttribute.cs} | 6 +- ...andleAttribute.cs => WsMethodAttribute.cs} | 8 +- Serein.Proto.WebSocket/Handle/Attribute.cs | 14 - ...ration.cs => MethodInvokeConfiguration.cs} | 33 +- .../Handle/WebSocketHandleModule.cs | 247 ++++++----- ...figuration.cs => WebSocketMethodConfig.cs} | 2 +- ...duleConfig.cs => WebSocketModuleConfig.cs} | 5 +- .../Handle/WebSocketMsgHandleHelper.cs | 39 +- .../ISereinWebSocketService.cs | 55 +++ Serein.Proto.WebSocket/ISocketHandleModule.cs | 5 +- Serein.Proto.WebSocket/SendType.cs | 30 ++ .../Serein.Proto.WebSocket.csproj | 12 +- .../SereinWebSocketService.cs | 400 ++++++++++++++++++ Serein.Proto.WebSocket/SocketExtension.cs | 29 ++ Serein.Proto.WebSocket/TestClass.cs | 37 ++ Serein.Proto.WebSocket/WebSocketClient.cs | 100 +---- ...sgContext.cs => WebSocketHandleContext.cs} | 82 ++-- ...cs => WebSocketMessageTransmissionTool.cs} | 37 +- Serein.Proto.WebSocket/WebSocketServer.cs | 6 +- Workbench/App.xaml.cs | 2 +- Workbench/Test.cs | 12 + 29 files changed, 883 insertions(+), 348 deletions(-) create mode 100644 Serein.Proto.WebSocket/Attributes/SendAttribute.cs rename Serein.Proto.WebSocket/Attributes/{AutoSocketModuleAttribute.cs => WebSocketModuleAttribute.cs} (82%) rename Serein.Proto.WebSocket/Attributes/{AutoSocketHandleAttribute.cs => WsMethodAttribute.cs} (83%) delete mode 100644 Serein.Proto.WebSocket/Handle/Attribute.cs rename Serein.Proto.WebSocket/Handle/{HandleConfiguration.cs => MethodInvokeConfiguration.cs} (78%) rename Serein.Proto.WebSocket/Handle/{WebSocketHandleConfiguration.cs => WebSocketMethodConfig.cs} (73%) rename Serein.Proto.WebSocket/Handle/{WebSocketHandleModuleConfig.cs => WebSocketModuleConfig.cs} (94%) create mode 100644 Serein.Proto.WebSocket/ISereinWebSocketService.cs create mode 100644 Serein.Proto.WebSocket/SendType.cs create mode 100644 Serein.Proto.WebSocket/SereinWebSocketService.cs create mode 100644 Serein.Proto.WebSocket/SocketExtension.cs create mode 100644 Serein.Proto.WebSocket/TestClass.cs rename Serein.Proto.WebSocket/{Handle/WebSocketMsgContext.cs => WebSocketHandleContext.cs} (52%) rename Serein.Proto.WebSocket/{TestExtension.cs => WebSocketMessageTransmissionTool.cs} (59%) create mode 100644 Workbench/Test.cs diff --git a/Library/Api/IJsonProvider.cs b/Library/Api/IJsonProvider.cs index 30e2b5f..744a701 100644 --- a/Library/Api/IJsonProvider.cs +++ b/Library/Api/IJsonProvider.cs @@ -7,11 +7,36 @@ using System.Threading.Tasks; namespace Serein.Library.Api { + + /// /// JSON数据交互的Token接口,允许使用不同的JSON库进行数据处理。 /// public interface IJsonToken { + /// + /// 获取当前Token的类型,可能是值、对象或数组。 + /// + TokenType Type { get; } + + /// + /// 获取当前Token的类型,可能是值、对象或数组。 + /// + public enum TokenType + { + /// + /// 表示一个值类型的Token,例如字符串、数字或布尔值。 + /// + Value, + /// + /// 表示一个对象类型的Token,通常是一个键值对集合。 + /// + Object, + /// + /// 表示一个数组类型的Token,通常是一个元素列表。 + /// + Array, + } /// /// 获取 Token /// @@ -19,12 +44,6 @@ namespace Serein.Library.Api /// IJsonToken this[object name] { get; } - /* /// - /// 获取 Token 数组的元素,允许通过索引访问数组中的元素。 - /// - /// - /// - IJsonToken this[int index] { get; }*/ /// /// 获取指定名称的属性,如果存在则返回true,并通过out参数返回对应的IJsonToken对象。 diff --git a/Library/ScriptBaseFunc.cs b/Library/ScriptBaseFunc.cs index 24f0787..25b782b 100644 --- a/Library/ScriptBaseFunc.cs +++ b/Library/ScriptBaseFunc.cs @@ -145,15 +145,14 @@ namespace Serein.Library /// public static IJsonToken json(string content) { + /*if (string.IsNullOrWhiteSpace(content)) + { + return JsonHelper.Object(dict => { }) ; + }*/ return JsonHelper.Parse(content); } - - - - - #endregion diff --git a/Library/Utils/JsonHelper.cs b/Library/Utils/JsonHelper.cs index bbcdd2f..41b12b8 100644 --- a/Library/Utils/JsonHelper.cs +++ b/Library/Utils/JsonHelper.cs @@ -56,7 +56,6 @@ namespace Serein.Library.Utils public static IJsonToken Parse(string json) { return provider.Parse(json); - } /// diff --git a/Serein.Extend.NewtonsoftJson/NewtonsoftJsonArrayToken.cs b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonArrayToken.cs index 0ac901d..470eb01 100644 --- a/Serein.Extend.NewtonsoftJson/NewtonsoftJsonArrayToken.cs +++ b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonArrayToken.cs @@ -6,11 +6,14 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using static Serein.Library.Api.IJsonToken; namespace Serein.Extend.NewtonsoftJson { public sealed class NewtonsoftJsonArrayToken : IJsonToken, IList { + public TokenType Type => TokenType.Array; + private readonly JArray _array; public NewtonsoftJsonArrayToken(JArray array) => _array = array; diff --git a/Serein.Extend.NewtonsoftJson/NewtonsoftJsonObjectToken.cs b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonObjectToken.cs index 8e7404c..d74e938 100644 --- a/Serein.Extend.NewtonsoftJson/NewtonsoftJsonObjectToken.cs +++ b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonObjectToken.cs @@ -5,6 +5,7 @@ using Serein.Library.Utils; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using static Serein.Library.Api.IJsonToken; namespace Serein.Extend.NewtonsoftJson { @@ -13,6 +14,8 @@ namespace Serein.Extend.NewtonsoftJson /// public sealed class NewtonsoftJsonObjectToken : IJsonToken, IDictionary { + public TokenType Type => TokenType.Object; + private readonly JObject _object; public NewtonsoftJsonObjectToken(JObject obj) => _object = obj; diff --git a/Serein.Extend.NewtonsoftJson/NewtonsoftJsonValueToken.cs b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonValueToken.cs index 9f3540b..6b8f1b7 100644 --- a/Serein.Extend.NewtonsoftJson/NewtonsoftJsonValueToken.cs +++ b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonValueToken.cs @@ -6,11 +6,14 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using static Serein.Library.Api.IJsonToken; namespace Serein.Extend.NewtonsoftJson { public sealed class NewtonsoftJsonValueToken : IJsonToken { + public TokenType Type => TokenType.Value; + private readonly JToken _token; public NewtonsoftJsonValueToken(JToken token) diff --git a/Serein.Proto.WebSocket/Attributes/SendAttribute.cs b/Serein.Proto.WebSocket/Attributes/SendAttribute.cs new file mode 100644 index 0000000..54ff108 --- /dev/null +++ b/Serein.Proto.WebSocket/Attributes/SendAttribute.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Proto.WebSocket.Attributes +{ + /// + /// 指示需要发送消息的处理方法 + /// + public sealed class SendAttribute : Attribute + { + } +} diff --git a/Serein.Proto.WebSocket/Attributes/UseRequestAttribute.cs b/Serein.Proto.WebSocket/Attributes/UseRequestAttribute.cs index cbb2367..f6183bd 100644 --- a/Serein.Proto.WebSocket/Attributes/UseRequestAttribute.cs +++ b/Serein.Proto.WebSocket/Attributes/UseRequestAttribute.cs @@ -6,7 +6,10 @@ using System.Threading.Tasks; namespace Serein.Proto.WebSocket.Attributes { - internal sealed class UseRequestAttribute : Attribute + /// + /// 指示使用 WebSocket 中请求的整体数据 + /// + public sealed class UseRequestAttribute : Attribute { } } diff --git a/Serein.Proto.WebSocket/Attributes/AutoSocketModuleAttribute.cs b/Serein.Proto.WebSocket/Attributes/WebSocketModuleAttribute.cs similarity index 82% rename from Serein.Proto.WebSocket/Attributes/AutoSocketModuleAttribute.cs rename to Serein.Proto.WebSocket/Attributes/WebSocketModuleAttribute.cs index 43c9734..ae23a90 100644 --- a/Serein.Proto.WebSocket/Attributes/AutoSocketModuleAttribute.cs +++ b/Serein.Proto.WebSocket/Attributes/WebSocketModuleAttribute.cs @@ -1,8 +1,8 @@ namespace Serein.Proto.WebSocket.Attributes { /// - /// 标记该类是处理模板,需要获取WebSocketServer/WebSocketClient了实例后,使用(Server/Client).MsgHandleHelper.AddModule()进行添加。 - /// 处理模板需要继承 ISocketHandleModule 接口,否则WebSocket接受到数据时,将无法进行调用相应的处理模板。 + /// 标记该类是处理模板 + /// 处理模板需要继承 ISocketHandleModule 接口,否则接受到 WebSocket 数据时,将无法进行调用相应的处理模板。 /// 使用方式: /// [AutoSocketModule(ThemeKey = "theme", DataKey = "data")] /// public class PlcSocketService : ISocketHandleModule @@ -16,7 +16,7 @@ /// /// [AttributeUsage(AttributeTargets.Class)] - public sealed class AutoSocketModuleAttribute : Attribute + public sealed class WebSocketModuleAttribute : Attribute { /// /// 业务标识 diff --git a/Serein.Proto.WebSocket/Attributes/AutoSocketHandleAttribute.cs b/Serein.Proto.WebSocket/Attributes/WsMethodAttribute.cs similarity index 83% rename from Serein.Proto.WebSocket/Attributes/AutoSocketHandleAttribute.cs rename to Serein.Proto.WebSocket/Attributes/WsMethodAttribute.cs index 96aeadf..f94b0dc 100644 --- a/Serein.Proto.WebSocket/Attributes/AutoSocketHandleAttribute.cs +++ b/Serein.Proto.WebSocket/Attributes/WsMethodAttribute.cs @@ -16,7 +16,7 @@ /// Func<dynamic,Task> : 会自动将对象解析为Json字符串,异步发送文本内容。 /// [AttributeUsage(AttributeTargets.Method)] - public sealed class AutoSocketHandleAttribute : Attribute + public sealed class WsMethodAttribute : Attribute { /// /// 描述Json业务字段,如果不设置,将默认使用方法名称。 @@ -32,12 +32,6 @@ /// 会进行异步等待,当Task结束后,自动获取TResult进行发送(请避免Task<Task<TResult>>诸如此类的Task泛型嵌套) /// public bool IsReturnValue = true; - /// - /// 表示该方法所有入参不能为空(所需的参数在请求Json的Data不存在) - /// 若有一个参数无法从data获取,则不会进行调用该方法 - /// 如果设置该属性为 false ,但某些入参不能为空,而不希望在代码中进行检查,请为入参添加[NotNull]/[Needful]特性 - /// - public bool ArgNotNull = true; } diff --git a/Serein.Proto.WebSocket/Handle/Attribute.cs b/Serein.Proto.WebSocket/Handle/Attribute.cs deleted file mode 100644 index 70275d4..0000000 --- a/Serein.Proto.WebSocket/Handle/Attribute.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Serein.Proto.WebSocket.Handle -{ - /// - /// 表示参数不能为空(Net462不能使用NutNull的情况) - /// - public sealed class NeedfulAttribute : Attribute - { - } - - - -} diff --git a/Serein.Proto.WebSocket/Handle/HandleConfiguration.cs b/Serein.Proto.WebSocket/Handle/MethodInvokeConfiguration.cs similarity index 78% rename from Serein.Proto.WebSocket/Handle/HandleConfiguration.cs rename to Serein.Proto.WebSocket/Handle/MethodInvokeConfiguration.cs index 1b9c3d8..a1105a3 100644 --- a/Serein.Proto.WebSocket/Handle/HandleConfiguration.cs +++ b/Serein.Proto.WebSocket/Handle/MethodInvokeConfiguration.cs @@ -1,4 +1,5 @@ using Serein.Library; +using static Serein.Proto.WebSocket.SereinWebSocketService; @@ -8,18 +9,13 @@ namespace Serein.Proto.WebSocket.Handle /// socket模块处理数据配置 /// - public class HandleConfiguration + public class MethodInvokeConfiguration { /// /// Emit委托 /// public DelegateDetails? DelegateDetails { get; set; } - /// - /// 未捕获的异常跟踪 - /// - public Action>? OnExceptionTracking { get; set; } - /// /// 所使用的实例 /// @@ -30,11 +26,6 @@ namespace Serein.Proto.WebSocket.Handle /// public bool IsReturnValue { get; set; } = true; - /// - /// 是否要求必须不为null - /// - public bool ArgNotNull { get; set; } = true; - /// /// 是否使用Data整体内容作为入参参数 /// @@ -45,6 +36,21 @@ namespace Serein.Proto.WebSocket.Handle /// public bool[] UseRequest { get; set; } = []; + /// + /// 是否需要发送消息的委托 + /// + public bool[] IsNeedSendDelegate { get; set; } = []; + + /// + /// 发送消息的委托类型 + /// + public SendType[] SendDelegateType { get; set; } = []; + + /// + /// 缓存的发送委托数组 + /// + public Delegate?[] CachedSendDelegates ; + /// /// 是否使用消息ID作为入参参数 /// @@ -60,11 +66,6 @@ namespace Serein.Proto.WebSocket.Handle /// public Type[] ParameterType { get; set; } = []; - /// - /// 是否检查变量为空 - /// - public bool[] IsCheckArgNotNull { get; set; } = []; - } diff --git a/Serein.Proto.WebSocket/Handle/WebSocketHandleModule.cs b/Serein.Proto.WebSocket/Handle/WebSocketHandleModule.cs index bd0fd29..c86c303 100644 --- a/Serein.Proto.WebSocket/Handle/WebSocketHandleModule.cs +++ b/Serein.Proto.WebSocket/Handle/WebSocketHandleModule.cs @@ -1,8 +1,10 @@ using Serein.Library; +using Serein.Library.Api; using Serein.Library.Utils; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Text.Json.Nodes; using System.Threading.Tasks; namespace Serein.Proto.WebSocket.Handle @@ -16,35 +18,38 @@ namespace Serein.Proto.WebSocket.Handle /// /// Json消息处理模块 /// - public WebSocketHandleModule(WebSocketHandleModuleConfig config) + public WebSocketHandleModule(WebSocketModuleConfig config) { - moduleConfig = config; + _moduleConfig = config; + _methodInvokeConfigs = new ConcurrentDictionary(); + _myMsgIdHash = new HashSet(); } /// /// 模块的处理配置 /// - private readonly WebSocketHandleModuleConfig moduleConfig; + private readonly WebSocketModuleConfig _moduleConfig; /// /// 用来判断消息是否重复 /// - private HashSet _myMsgIdHash = new HashSet(); + private readonly HashSet _myMsgIdHash; + /// /// 存储处理数据的配置 /// - public ConcurrentDictionary MyHandleConfigs = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _methodInvokeConfigs ; /// /// 添加处理配置 /// /// 处理模块 - internal bool AddHandleConfigs(WebSocketHandleConfiguration config) + internal bool AddHandleConfigs(WebSocketMethodConfig config) { - if (!MyHandleConfigs.ContainsKey(config.ThemeValue)) + if (!_methodInvokeConfigs.ContainsKey(config.ThemeValue)) { - MyHandleConfigs[config.ThemeValue] = config; + _methodInvokeConfigs[config.ThemeValue] = config; return true; } else @@ -60,13 +65,13 @@ namespace Serein.Proto.WebSocket.Handle /// public bool RemoveConfig(ISocketHandleModule socketControlBase) { - foreach (var kv in MyHandleConfigs.ToArray()) + foreach (var kv in _methodInvokeConfigs.ToArray()) { var config = kv.Value; - MyHandleConfigs.TryRemove(kv.Key, out _); + _methodInvokeConfigs.TryRemove(kv.Key, out _); } - return MyHandleConfigs.Count == 0; + return _methodInvokeConfigs.Count == 0; } /// @@ -74,8 +79,8 @@ namespace Serein.Proto.WebSocket.Handle /// public void UnloadConfig() { - var temp = MyHandleConfigs.Values; - MyHandleConfigs.Clear(); + var temp = _methodInvokeConfigs.Values; + _methodInvokeConfigs.Clear(); } @@ -83,46 +88,64 @@ namespace Serein.Proto.WebSocket.Handle /// /// 处理JSON数据 /// - public async Task HandleAsync(WebSocketMsgContext context) + public async Task HandleAsync(WebSocketHandleContext context) { var jsonObject = context.MsgRequest; // 获取到消息 if (jsonObject is null) { - // SereinEnv.WriteLine(InfoType.WARN, "没有获取到消息"); + context.TriggerExceptionTracking($"请求没有获取到消息"); + return; // 没有获取到消息 + } + if(!jsonObject.TryGetValue(_moduleConfig.ThemeJsonKey, out var themeToken) || themeToken.IsNull) + { + context.TriggerExceptionTracking($"请求没有获取到主题\"{_moduleConfig.ThemeJsonKey}\""); + return; // 没有获取到消息 + } + if(themeToken.Type != IJsonToken.TokenType.Value) + { + context.TriggerExceptionTracking($"请求主题需要值类型 \"{_moduleConfig.ThemeJsonKey}\""); return; // 没有获取到消息 } + var theme = themeToken.ToString(); // 获取主题 // 验证主题 - if (!jsonObject.TryGetValue(moduleConfig.ThemeJsonKey, out var themeToken) - || themeToken.ToString() is not string theme - || !MyHandleConfigs.TryGetValue(theme, out var handldConfig)) + if (!_methodInvokeConfigs.TryGetValue(theme, out var handldConfig)) { - // SereinEnv.WriteLine(InfoType.WARN, $"{theme} 主题不存在"); + context.TriggerExceptionTracking($"{_moduleConfig.ThemeJsonKey} 主题不存在"); return; } - // 验证消息ID - if (!jsonObject.TryGetValue(moduleConfig.MsgIdJsonKey, out var msgIdToken) - || msgIdToken.ToString() is not string msgId) + + if (!jsonObject.TryGetValue(_moduleConfig.MsgIdJsonKey, out var msgIdToken) || themeToken.IsNull) { - // SereinEnv.WriteLine(InfoType.WARN, $"[{msgId}]{theme} 没有消息Id"); - return; + context.TriggerExceptionTracking($"主题 {theme} 没有消息Id"); + return; // 没有获取到消息 + } + if (themeToken.Type != IJsonToken.TokenType.Value) + { + context.TriggerExceptionTracking($"请求消息Id需要值类型 \"{_moduleConfig.ThemeJsonKey}\""); + return; // 没有获取到消息 } + var msgId = msgIdToken.ToString(); // 获取主题 // 验证消息ID是否重复 if (!_myMsgIdHash.Add(msgId)) { - // SereinEnv.WriteLine(InfoType.WARN, $"[{msgId}]{theme} 消息重复"); + context.TriggerExceptionTracking($"主题 {theme} 消息Id {msgId} 消息重复"); return; // 消息重复 } // 验证数据 - if (!jsonObject.TryGetValue(moduleConfig.DataJsonKey, out var dataToken)) + if (!jsonObject.TryGetValue(_moduleConfig.DataJsonKey, out var dataToken)) { - // SereinEnv.WriteLine(InfoType.WARN, $"[{msgId}]{theme} 消息重复"); + context.TriggerExceptionTracking($"主题 {theme} 消息Id {msgId} 数据提取失败,当前指定键\"{_moduleConfig.DataJsonKey}\""); return; // 没有主题 } + if(dataToken.Type != IJsonToken.TokenType.Object) + { + context.TriggerExceptionTracking($"主题 {theme} 消息Id {msgId} 数据需要 JSON Object"); + } context.MsgTheme = theme; // 添加主题 context.MsgId = msgId; // 添加 ID @@ -135,17 +158,17 @@ namespace Serein.Proto.WebSocket.Handle var result = await HandleAsync(handldConfig, args); if (handldConfig.IsReturnValue) { - await context.RepliedAsync(moduleConfig, context, result); + await RepliedAsync(_moduleConfig, context, result); } } else { - SereinEnv.WriteLine(InfoType.WARN, $"[{msgId}]{theme} 参数获取失败"); + context.TriggerExceptionTracking($"主题 {theme} 消息Id {msgId} 参数获取失败"); } } catch (Exception ex) { - SereinEnv.WriteLine(InfoType.ERROR, $"error in ws : {ex.Message}{Environment.NewLine}json value:{jsonObject}"); + context.TriggerExceptionTracking(ex); } finally { @@ -153,6 +176,7 @@ namespace Serein.Proto.WebSocket.Handle } } + /// /// 调用 @@ -160,7 +184,7 @@ namespace Serein.Proto.WebSocket.Handle /// /// /// - public static async Task HandleAsync(HandleConfiguration config, object?[] args) + public static async Task HandleAsync(MethodInvokeConfiguration config, object?[] args) { if (config.DelegateDetails is null) { @@ -179,14 +203,15 @@ namespace Serein.Proto.WebSocket.Handle /// 处理上下文 /// 返回的入参参数 /// - internal static bool TryGetParameters(HandleConfiguration config, WebSocketMsgContext context, out object?[] args) + internal static bool TryGetParameters(MethodInvokeConfiguration config, WebSocketHandleContext context, out object?[] args) { args = new object[config.ParameterType.Length]; + var theme = context.MsgTheme; + var msgId = context.MsgId; + List exTips = [$"主题 {theme} 消息Id {msgId}"]; bool isCanInvoke = true; ; // 表示是否可以调用方法 - for (int i = 0; i < config.ParameterType.Length; i++) { - var type = config.ParameterType[i]; // 入参变量类型 var argName = config.ParameterName[i]; // 入参参数名称 #region 传递消息ID @@ -207,93 +232,123 @@ namespace Serein.Proto.WebSocket.Handle args[i] = context.MsgData?.ToObject(type); } #endregion - #region 值类型参数 - else if (type.IsValueType) + #region 入参参数 + else if (!config.IsNeedSendDelegate[i]) { var jsonValue = context.MsgData?.GetValue(argName); - if (jsonValue is not null) + if(jsonValue is null) { - args[i] = jsonValue.ToObject(type); + isCanInvoke = false; + exTips.Add($"参数 {argName}({i}) 不存在,请检查参数名称是否正确"); + continue; } - else + var data = jsonValue.ToObject(type); + if (data is null) { - if (config.ArgNotNull && !config.IsCheckArgNotNull[i]) // 检查不能为空 - { - - args[i] = Activator.CreateInstance(type); // 值类型返回默认值 - } - else - { - isCanInvoke = false; // 参数不能为空,终止调用 - break; - } - } - } - #endregion - #region 引用类型参数 - else if (type.IsClass) - { - var jsonValue = context.MsgData?.GetValue(argName); - if (!(jsonValue is null)) - { - args[i] = jsonValue.ToObject(type); - } - else - { - if (!config.ArgNotNull && !config.IsCheckArgNotNull[i]) - { - - args[i] = null; // 引用类型返回null - } - else - { - isCanInvoke = false; // 参数不能为空,终止调用 - break; - } + isCanInvoke = false; + exTips.Add($"参数 {argName}({i}) 解析失败,类型:{type.FullName},值:{jsonValue},请检查参数类型是否正确"); + continue; } + args[i] = data; } #endregion #region 传递消息委托 - else if (type.IsGenericType) // 传递SendAsync委托 + else if (config.IsNeedSendDelegate[i]) // 传递SendAsync委托 { - if (type.IsAssignableFrom(typeof(Func))) + if (config.CachedSendDelegates != null && config.CachedSendDelegates[i] != null) { - args[i] = new Func(async data => - { - var jsonText = JsonHelper.Serialize(data); - await context.SendAsync(jsonText); - }); + args[i] = config.CachedSendDelegates[i]; + continue; } - else if (type.IsAssignableFrom(typeof(Func))) + + Delegate? del = null; + var st = config.SendDelegateType[i]; + switch (st) { - args[i] = new Func(async data => - { - await context.SendAsync(data); - }); + case SereinWebSocketService.SendType.ObjectAsync: + del = new Func(async data => + { + var jsonText = JsonHelper.Serialize(data); + await context.SendAsync(jsonText); + }); + break; + case SereinWebSocketService.SendType.StringAsync: + del = new Func(async data => + { + await context.SendAsync(data); + }); + break; + case SereinWebSocketService.SendType.Object: + del = new Action(data => + { + var jsonText = JsonHelper.Serialize(data); + _ = context.SendAsync(jsonText); + }); + break; + case SereinWebSocketService.SendType.String: + del = new Action(data => + { + _ = context.SendAsync(data); + }); + break; } - else if (type.IsAssignableFrom(typeof(Action))) + + if (del is not null) { - args[i] = new Action(async data => - { - var jsonText = JsonHelper.Serialize(data); - await context.SendAsync(jsonText); - }); + config.CachedSendDelegates![i] = del; + args[i] = del; } - else if (type.IsAssignableFrom(typeof(Action))) + else { - args[i] = new Action(async data => - { - var jsonText = JsonHelper.Serialize(data); - await context.SendAsync(jsonText); - }); + isCanInvoke = false; // 方法要求参数不能为空,终止调用 + exTips.Add($"参数 {argName}({i}) 发送委托类型错误"); + break; } } #endregion } + if (!isCanInvoke) + { + string ex = string.Join(Environment.NewLine, exTips); + context.TriggerExceptionTracking(ex); + } return isCanInvoke; } + + + /// + /// 返回消息 + /// + /// + /// + /// + /// + public async Task RepliedAsync(WebSocketModuleConfig moduleConfig, + WebSocketHandleContext context, + object data) + { + if (moduleConfig.IsResponseUseReturn) + { + var responseContent = JsonHelper.Serialize(data); + await context.SendAsync(responseContent); + } + else + { + + IJsonToken jsonData; + jsonData = JsonHelper.Object(obj => + { + obj[moduleConfig.MsgIdJsonKey] = context.MsgId; + obj[moduleConfig.ThemeJsonKey] = context.MsgTheme; + obj[moduleConfig.DataJsonKey] = data is null ? null : JsonHelper.FromObject(data); + }); + + var msg = jsonData.ToString(); + await context.SendAsync(msg); + } + } } diff --git a/Serein.Proto.WebSocket/Handle/WebSocketHandleConfiguration.cs b/Serein.Proto.WebSocket/Handle/WebSocketMethodConfig.cs similarity index 73% rename from Serein.Proto.WebSocket/Handle/WebSocketHandleConfiguration.cs rename to Serein.Proto.WebSocket/Handle/WebSocketMethodConfig.cs index f3bc066..29484bf 100644 --- a/Serein.Proto.WebSocket/Handle/WebSocketHandleConfiguration.cs +++ b/Serein.Proto.WebSocket/Handle/WebSocketMethodConfig.cs @@ -1,6 +1,6 @@ namespace Serein.Proto.WebSocket.Handle { - internal class WebSocketHandleConfiguration : HandleConfiguration + public class WebSocketMethodConfig : MethodInvokeConfiguration { /// /// 主题 diff --git a/Serein.Proto.WebSocket/Handle/WebSocketHandleModuleConfig.cs b/Serein.Proto.WebSocket/Handle/WebSocketModuleConfig.cs similarity index 94% rename from Serein.Proto.WebSocket/Handle/WebSocketHandleModuleConfig.cs rename to Serein.Proto.WebSocket/Handle/WebSocketModuleConfig.cs index 719a3b2..819e7e7 100644 --- a/Serein.Proto.WebSocket/Handle/WebSocketHandleModuleConfig.cs +++ b/Serein.Proto.WebSocket/Handle/WebSocketModuleConfig.cs @@ -3,20 +3,23 @@ /// /// 远程环境配置 /// - public class WebSocketHandleModuleConfig + public class WebSocketModuleConfig { /// /// 有关消息ID的 Json Key /// public string MsgIdJsonKey { get; set; } = string.Empty; + /// /// 有关消息主题的 Json Key /// public string ThemeJsonKey { get; set; } = string.Empty; + /// /// 有关数据的 Json Key /// public string DataJsonKey { get; set; } = string.Empty; + /// /// 使用怎么样的数据 /// diff --git a/Serein.Proto.WebSocket/Handle/WebSocketMsgHandleHelper.cs b/Serein.Proto.WebSocket/Handle/WebSocketMsgHandleHelper.cs index cf9abb5..297a1c4 100644 --- a/Serein.Proto.WebSocket/Handle/WebSocketMsgHandleHelper.cs +++ b/Serein.Proto.WebSocket/Handle/WebSocketMsgHandleHelper.cs @@ -3,6 +3,7 @@ using Serein.Proto.WebSocket.Attributes; using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Threading.Tasks; namespace Serein.Proto.WebSocket.Handle { @@ -27,7 +28,7 @@ namespace Serein.Proto.WebSocket.Handle /// /// 模块配置 /// - private WebSocketHandleModule AddMyHandleModule(WebSocketHandleModuleConfig moduleConfig) + private WebSocketHandleModule AddMsgHandleModule(WebSocketHandleModuleConfig moduleConfig) { var key = (moduleConfig.ThemeJsonKey, moduleConfig.DataJsonKey); if (!MyHandleModuleDict.TryGetValue(key, out var myHandleModule)) @@ -45,7 +46,7 @@ namespace Serein.Proto.WebSocket.Handle public void RemoveModule(ISocketHandleModule socketControlBase) { var type = socketControlBase.GetType(); - var moduleAttribute = type.GetCustomAttribute(); + var moduleAttribute = type.GetCustomAttribute(); if (moduleAttribute is null) { return; @@ -71,7 +72,7 @@ namespace Serein.Proto.WebSocket.Handle where T : ISocketHandleModule { var type = typeof(T); - var moduleAttribute = type.GetCustomAttribute(); + var moduleAttribute = type.GetCustomAttribute(); if (moduleAttribute is null) { return; @@ -89,11 +90,11 @@ namespace Serein.Proto.WebSocket.Handle IsResponseUseReturn = isResponseUseReturn, }; - var handleModule = AddMyHandleModule(moduleConfig); + var handleModule = AddMsgHandleModule(moduleConfig); var configs = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) - .Select(methodInfo => + .Select(methodInfo => { - var methodsAttribute = methodInfo.GetCustomAttribute(); + var methodsAttribute = methodInfo.GetCustomAttribute(); if (methodsAttribute is null) { return default; @@ -105,8 +106,7 @@ namespace Serein.Proto.WebSocket.Handle methodsAttribute.ThemeValue = methodInfo.Name; } - #region 生成处理配置 - var config = new WebSocketHandleConfiguration(); + var config = new WebSocketMethodHandleConfig(); config.ThemeValue = methodsAttribute.ThemeValue; config.ArgNotNull = methodsAttribute.ArgNotNull; @@ -142,24 +142,7 @@ namespace Serein.Proto.WebSocket.Handle config.IsCheckArgNotNull = parameterInfos.Select(p => p.GetCustomAttribute() != null).ToArray(); // 是否检查null #endif - if (config.IsCheckArgNotNull is null) - { - config.IsCheckArgNotNull = parameterInfos.Select(p => p.GetCustomAttribute() != null).ToArray(); // 是否检查null - } - else - { - // 兼容两种非空特性的写法 - var argNotNull = parameterInfos.Select(p => p.GetCustomAttribute() != null).ToArray(); // 是否检查null - for (int i = 0; i < config.IsCheckArgNotNull.Length; i++) - { - if (!config.IsCheckArgNotNull[i] && argNotNull[i]) - { - config.IsCheckArgNotNull[i] = true; - } - } - } - #endregion - + var value = methodsAttribute.ThemeValue; @@ -189,7 +172,7 @@ namespace Serein.Proto.WebSocket.Handle /// /// 此次请求的上下文 /// - public void Handle(WebSocketMsgContext context) + public async Task HandleAsync(WebSocketMsgContext context) { foreach (var module in MyHandleModuleDict.Values) { @@ -197,7 +180,7 @@ namespace Serein.Proto.WebSocket.Handle { return; } - _ = module.HandleAsync(context); + await module.HandleAsync(context); } diff --git a/Serein.Proto.WebSocket/ISereinWebSocketService.cs b/Serein.Proto.WebSocket/ISereinWebSocketService.cs new file mode 100644 index 0000000..7377093 --- /dev/null +++ b/Serein.Proto.WebSocket/ISereinWebSocketService.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NetWebSocket = System.Net.WebSockets.WebSocket; +namespace Serein.Proto.WebSocket +{ + /// + /// WebSocket服务 + /// + public interface ISereinWebSocketService + { + /// + /// 目前有多少个连接 + /// + int ConcetionCount { get; } + + /// + /// 添加处理模块 + /// + /// + /// + ISereinWebSocketService AddHandleModule() where T : ISocketHandleModule, new(); + + /// + /// 添加处理模块 + /// + /// + /// 使用指定的实例 + /// + ISereinWebSocketService AddHandleModule(Func instanceFactory) where T : ISocketHandleModule; + + /// + /// 跟踪未处理的异常 + /// + /// + /// + ISereinWebSocketService TrackUnhandledExceptions(Func, Task> onExceptionTrackingAsync); + + /// + /// 添加新的 WebSocket 连接进行处理消息 + /// + /// + Task AddWebSocketHandleAsync(NetWebSocket webSocket); + + /// + /// 推送消息 + /// + /// + /// + Task PushDataAsync(object latestData); + + } +} diff --git a/Serein.Proto.WebSocket/ISocketHandleModule.cs b/Serein.Proto.WebSocket/ISocketHandleModule.cs index 26a7ba2..0d368f1 100644 --- a/Serein.Proto.WebSocket/ISocketHandleModule.cs +++ b/Serein.Proto.WebSocket/ISocketHandleModule.cs @@ -1,10 +1,7 @@ -using System; - -namespace Serein.Proto.WebSocket +namespace Serein.Proto.WebSocket { public interface ISocketHandleModule { } - } diff --git a/Serein.Proto.WebSocket/SendType.cs b/Serein.Proto.WebSocket/SendType.cs new file mode 100644 index 0000000..555af82 --- /dev/null +++ b/Serein.Proto.WebSocket/SendType.cs @@ -0,0 +1,30 @@ +namespace Serein.Proto.WebSocket +{ +public partial class SereinWebSocketService + { + public enum SendType + { + /// + /// 不发送数据 + /// + None, + /// + /// 发送字符串 + /// + String, + /// + /// 发送对象 + /// + Object, + /// + /// 异步发送字符串 + /// + StringAsync, + /// + /// 异步发送对象 + /// + ObjectAsync + } + } + +} diff --git a/Serein.Proto.WebSocket/Serein.Proto.WebSocket.csproj b/Serein.Proto.WebSocket/Serein.Proto.WebSocket.csproj index dbe2498..14c48d3 100644 --- a/Serein.Proto.WebSocket/Serein.Proto.WebSocket.csproj +++ b/Serein.Proto.WebSocket/Serein.Proto.WebSocket.csproj @@ -1,7 +1,7 @@  - net8.0;net462 + net8.0; enable latest enable @@ -16,8 +16,18 @@ + + + + + + + + + + diff --git a/Serein.Proto.WebSocket/SereinWebSocketService.cs b/Serein.Proto.WebSocket/SereinWebSocketService.cs new file mode 100644 index 0000000..0b1e86c --- /dev/null +++ b/Serein.Proto.WebSocket/SereinWebSocketService.cs @@ -0,0 +1,400 @@ +using Serein.Library; +using Serein.Library.Utils; +using Serein.Proto.WebSocket.Attributes; +using Serein.Proto.WebSocket.Handle; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Data; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Net.Sockets; +using System.Net.WebSockets; +using System.Reactive; +using System.Reflection; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using NetWebSocket = System.Net.WebSockets.WebSocket; + +namespace Serein.Proto.WebSocket +{ + /// + /// WebSocket 服务类,负责管理所有 WebSocket 连接和处理模块 + /// + public partial class SereinWebSocketService : ISereinWebSocketService + { + /// + /// (Theme Name ,Data Name) - HandleModule + /// + private readonly ConcurrentDictionary<(string, string), WebSocketHandleModule> _socketModules; + /// + /// 追踪未处理的异常 + /// + + private Func, Task> _onExceptionTrackingAsync; + /// + /// 维护所有 WebSocket 连接 + /// + private readonly List _sockets; + + /// + /// 用于增加、移除 WebSocket 连接时,保证线程安全操作 + /// + private readonly object _lock = new object(); + + public int ConcetionCount => _sockets.Count; + + public SereinWebSocketService() + { + _socketModules = new ConcurrentDictionary<(string, string), WebSocketHandleModule>(); + _sockets = new List(); + _lock = new object(); + } + + + + + + #region 添加处理模块 + + /// + /// 添加处理模块,使用指定的实例工厂和异常追踪回调 + /// + /// + /// + /// + public ISereinWebSocketService AddHandleModule() where T : ISocketHandleModule, new() + { + var type = typeof(T); + Func instanceFactory = () => (T)Activator.CreateInstance(type); + return AddHandleModule(type, instanceFactory); + } + + /// + /// 添加处理模块,使用指定的实例工厂和异常追踪回调 + /// + /// + /// + /// + public ISereinWebSocketService AddHandleModule(Func instanceFactory) where T : ISocketHandleModule + { + var type = typeof(T); + return AddHandleModule(type, instanceFactory); + } + + /// + /// 添加处理模块,使用指定的类型、实例工厂和异常追踪回调 + /// + /// + /// + /// + private ISereinWebSocketService AddHandleModule(Type type, Func instanceFactory) + { + if(!CheckAttribute(type, out var attribute)) + { + throw new Exception($"类型 {type} 需要标记 WebSocketModuleAttribute 特性"); + } + var modbuleConfig = GetConfig(attribute); + var module = GetOrAddModule(modbuleConfig); + var methodInfos = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ToArray(); + var methodConfigs = CreateMethodConfig(methodInfos, instanceFactory); + + SereinEnv.WriteLine(InfoType.INFO, $"add websocket handle model :"); + SereinEnv.WriteLine(InfoType.INFO, $"theme key, data key : {modbuleConfig.ThemeJsonKey}, {modbuleConfig.DataJsonKey}"); + foreach (var methodConfig in methodConfigs) + { + SereinEnv.WriteLine(InfoType.INFO, $"theme value : {methodConfig!.ThemeValue} "); + var result = module.AddHandleConfigs(methodConfig); + } + return this; + } + + /// + /// 检查特性 + /// + /// + /// + /// + /// + private bool CheckAttribute(Type type, out TAttribute attribute) where TAttribute : Attribute + { + attribute = type.GetCustomAttribute(); + if (attribute is null) + { + return false; + } + return true; + } + + /// + /// 获取模块配置 + /// + /// + /// + private WebSocketModuleConfig GetConfig(WebSocketModuleAttribute moduleAttribute) + { + var themeKey = moduleAttribute.ThemeKey; + var dataKey = moduleAttribute.DataKey; + var msgIdKey = moduleAttribute.MsgIdKey; + var isResponseUseReturn = moduleAttribute.IsResponseUseReturn; + var moduleConfig = new WebSocketModuleConfig() + { + ThemeJsonKey = themeKey, + DataJsonKey = dataKey, + MsgIdJsonKey = msgIdKey, + IsResponseUseReturn = isResponseUseReturn, + }; + return moduleConfig; + } + + /// + /// 获取或添加消息处理与异常处理 + /// + /// 模块配置 + /// + private WebSocketHandleModule GetOrAddModule(WebSocketModuleConfig moduleConfig) + { + var key = (moduleConfig.ThemeJsonKey, moduleConfig.DataJsonKey); + if (_socketModules.TryGetValue(key, out var wsHandleModule)) + { + return wsHandleModule; + } + wsHandleModule = new WebSocketHandleModule(moduleConfig); + _socketModules[key] = wsHandleModule; + return wsHandleModule; + } + + /// + /// 创建方法配置 + /// + /// + /// + /// + /// + private List CreateMethodConfig(MethodInfo[] methodInfos, Func instanceFactory) + { + List configs = []; + foreach (var methodInfo in methodInfos) + { + var wsMethodAttribute = methodInfo.GetCustomAttribute(); + if (wsMethodAttribute is null) + { + continue; + } + var parameterInfos = methodInfo.GetParameters(); + + var temp_array = parameterInfos.Select(p => + { + var isSend = CheckSendType(p.ParameterType, out var sendType); + return new + { + IsNeedSend = isSend, + Type = sendType + }; + }).ToArray(); + + var config = new WebSocketMethodConfig + { + ThemeValue = string.IsNullOrEmpty(wsMethodAttribute.ThemeValue) ? methodInfo.Name : wsMethodAttribute.ThemeValue, + IsReturnValue = wsMethodAttribute.IsReturnValue, + DelegateDetails = new DelegateDetails(methodInfo), // 对应theme的emit构造委托调用工具类 + InstanceFactory = instanceFactory, // 调用emit委托时的实例 + //OnExceptionTracking = onExceptionTracking, // 异常追踪 + ParameterType = parameterInfos.Select(t => t.ParameterType).ToArray(), // 入参参数类型 + ParameterName = parameterInfos.Select(t => $"{t.Name}").ToArray(), // 入参参数名称 + UseRequest = parameterInfos.Select(p => p.GetCustomAttribute() is not null).ToArray(),// 是否使用整体data数据 + UseData = parameterInfos.Select(p => p.GetCustomAttribute() is not null).ToArray(), // 是否使用整体data数据 + UseMsgId = parameterInfos.Select(p => p.GetCustomAttribute() is not null).ToArray(), // 是否使用消息ID + IsNeedSendDelegate = temp_array.Select(p => p.IsNeedSend).ToArray(), // 是否需要发送消息的委托 + SendDelegateType = temp_array.Select(p => p.Type).ToArray(), // 发送消息的委托类型 + CachedSendDelegates = new Delegate[temp_array.Length], // 提前缓存发送委托数组 + }; + configs.Add(config); + } + return configs; + } + + private bool CheckSendType(Type type , out SendType sendType) + { + if (type.IsAssignableFrom(typeof(Func))) + { + sendType = SendType.ObjectAsync; + } + else if (type.IsAssignableFrom(typeof(Func))) + { + sendType = SendType.StringAsync; + } + else if (type.IsAssignableFrom(typeof(Action))) + { + sendType = SendType.StringAsync; + } + else if (type.IsAssignableFrom(typeof(Action))) + { + sendType = SendType.StringAsync; + } + else + { + sendType = SendType.None; + return false; + } + return true; + } + + #endregion + + /// + /// 跟踪未处理的异常 + /// + /// + public ISereinWebSocketService TrackUnhandledExceptions(Func, Task> onExceptionTrackingAsync) + { + _onExceptionTrackingAsync = onExceptionTrackingAsync; + return this; + } + + + + /// + /// 传入新的 WebSocket 连接,开始进行处理 + /// + /// + + + /// + /// 处理新的 WebSocket 连接 + /// + /// + /// + public async Task AddWebSocketHandleAsync(NetWebSocket socket) + { + lock (_lock) { _sockets.Add(socket); } + var buffer = new byte[4096]; + + var msgHandleUtil = new WebSocketMessageTransmissionTool(); // 消息队列 + + _ = Task.Run(async () => + { + await HandleMsgAsync(socket, msgHandleUtil); + }); + + try + { + while (socket.State == WebSocketState.Open) + { + var result = await socket.ReceiveAsync(buffer: new ArraySegment(buffer), CancellationToken.None); + if (result.MessageType == WebSocketMessageType.Close) + { + break; + } + else if (result.MessageType == WebSocketMessageType.Text) + { + var message = Encoding.UTF8.GetString(buffer, 0, result.Count); + Console.WriteLine($"收到客户端消息: {message}"); + var echo = Encoding.UTF8.GetBytes(message); + await msgHandleUtil.WriteMsgAsync(message); // 异步传递消息 + } + } + } + catch (Exception ex) + { + Console.WriteLine($"WebSocket 异常: {ex.Message}"); + } + finally + { + lock (_lock) { _sockets.Remove(socket); } + await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "关闭连接", CancellationToken.None); + socket.Dispose(); + } + } + + /// + /// 处理消息 + /// + /// + /// + /// + private async Task HandleMsgAsync(NetWebSocket webSocket,WebSocketMessageTransmissionTool tranTool) + { + //var AuthorizedClients = new ConcurrentDictionary(); + async Task sendasync(string text) + { + await SocketExtension.SendAsync(webSocket, text); // 回复客户端,处理方法中入参如果需要发送消息委托,则将该回调方法作为委托参数传入 + } + /* + ObjectPool contextPool = new ObjectPool(() => + { + return new WebSocketMsgContext(sendasync); + }, 20); + var context = contextPool.Allocate(); + contextPool.Free(context); + */ + while (webSocket.State == WebSocketState.Open) + { + var message = await tranTool.WaitMsgAsync(); // 有消息时通知 + var context = new WebSocketHandleContext(sendasync); + context.MsgRequest = JsonHelper.Parse(message); + context.OnExceptionTrackingAsync = _onExceptionTrackingAsync; + await HandleAsync(context); // 处理消息 + } + + } + + /// + /// 异步消息 + /// + /// 此次请求的上下文 + /// + private async Task HandleAsync(WebSocketHandleContext context) + { + foreach (var module in _socketModules.Values) + { + if (context.Handle) + { + return; + } + await module.HandleAsync(context); + } + + + } + + /// + /// 给所有客户端广播最新数据 + /// + /// + /// + public async Task PushDataAsync(object latestData) + { + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + var json = JsonSerializer.Serialize(latestData, options); + var buffer = Encoding.UTF8.GetBytes(json); + var segment = new ArraySegment(buffer); + + List socketsCopy; + lock (_lock) + { + socketsCopy = _sockets.ToList(); + } + + foreach (var socket in socketsCopy) + { + if (socket.State == WebSocketState.Open) + { + try + { + await socket.SendAsync(segment, WebSocketMessageType.Text, true, CancellationToken.None); + } + catch + { + // 忽略异常或移除失效连接 + } + } + } + } + } + +} diff --git a/Serein.Proto.WebSocket/SocketExtension.cs b/Serein.Proto.WebSocket/SocketExtension.cs new file mode 100644 index 0000000..aec6877 --- /dev/null +++ b/Serein.Proto.WebSocket/SocketExtension.cs @@ -0,0 +1,29 @@ +using Serein.Library.Utils; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using NetWebSocket = System.Net.WebSockets.WebSocket; +namespace Serein.Proto.WebSocket +{ + + public class SocketExtension + { + /// + /// 发送消息 + /// + /// + /// + /// + public static async Task SendAsync(NetWebSocket webSocket, string message) + { + var buffer = Encoding.UTF8.GetBytes(message); + await webSocket.SendAsync(new ArraySegment(buffer), WebSocketMessageType.Text, true, CancellationToken.None); + } + } +} diff --git a/Serein.Proto.WebSocket/TestClass.cs b/Serein.Proto.WebSocket/TestClass.cs new file mode 100644 index 0000000..654a27f --- /dev/null +++ b/Serein.Proto.WebSocket/TestClass.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Proto.WebSocket +{ + + public class ClassA : ISocketHandleModule + { + + } + public class ClassB : ISocketHandleModule + { + + } + public class ClassC : ISocketHandleModule + { + + } + internal class TestClass + { + public void Run() + { + SereinWebSocketService sereinWebSocketService = new SereinWebSocketService(); + sereinWebSocketService.AddHandleModule(); + sereinWebSocketService.AddHandleModule(() => new ClassB()); + sereinWebSocketService.TrackUnhandledExceptions(OnExceptionTrackingAsync); + } + + private static async Task OnExceptionTrackingAsync(Exception ex, Func SendAsync) + { + await SendAsync(""); + } + } +} diff --git a/Serein.Proto.WebSocket/WebSocketClient.cs b/Serein.Proto.WebSocket/WebSocketClient.cs index f86bdce..951be18 100644 --- a/Serein.Proto.WebSocket/WebSocketClient.cs +++ b/Serein.Proto.WebSocket/WebSocketClient.cs @@ -6,6 +6,7 @@ using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; +using NetWebSocket = System.Net.WebSockets.WebSocket; namespace Serein.Proto.WebSocket { @@ -50,6 +51,7 @@ namespace Serein.Proto.WebSocket } } + /// /// 发送消息 /// @@ -67,7 +69,7 @@ namespace Serein.Proto.WebSocket private async Task ReceiveAsync() { - var msgQueueUtil = new MsgHandleUtil(); + var msgQueueUtil = new WebSocketMessageTransmissionTool(); _ = Task.Run(async () => { await HandleMsgAsync(_client, msgQueueUtil); @@ -100,12 +102,6 @@ namespace Serein.Proto.WebSocket { await _client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None); } - //else - //{ - // var completeMessage = receivedMessage.ToString(); - // MsgHandleHelper.HandleMsg(SendAsync, completeMessage); // 处理消息,如果方法入参是需要发送消息委托时,将 SendAsync 作为委托参数提供 - // //Debug.WriteLine($"Received: {completeMessage}"); - //} } @@ -116,8 +112,13 @@ namespace Serein.Proto.WebSocket } } - - public async Task HandleMsgAsync(System.Net.WebSockets.WebSocket webSocket, MsgHandleUtil msgQueueUtil) + /// + /// 处理消息 + /// + /// + /// + /// + public async Task HandleMsgAsync(NetWebSocket webSocket, WebSocketMessageTransmissionTool msgQueueUtil) { async Task sendasync(string text) { @@ -128,87 +129,8 @@ namespace Serein.Proto.WebSocket var message = await msgQueueUtil.WaitMsgAsync(); // 有消息时通知 var context = new WebSocketMsgContext(sendasync); context.MsgRequest = JsonHelper.Parse(message); - MsgHandleHelper.Handle(context); // 处理消息 - - //using (var context = new WebSocketMsgContext(sendasync)) - //{ - // context.JsonObject = JObject.Parse(message); - // await MsgHandleHelper.HandleAsync(context); // 处理消息 - //} - - //_ = Task.Run(() => { - // JObject json = JObject.Parse(message); - // WebSocketMsgContext context = new WebSocketMsgContext(async (text) => - // { - // await SocketExtension.SendAsync(webSocket, text); // 回复客户端,处理方法中入参如果需要发送消息委托,则将该回调方法作为委托参数传入 - // }); - // context.JsonObject = json; - // await MsgHandleHelper.HandleAsync(context); // 处理消息 - //}); - + MsgHandleHelper.HandleAsync(context); // 处理消息 } - - - /* #region 消息处理 - private readonly string ThemeField; - private readonly ConcurrentDictionary ThemeConfigs = new ConcurrentDictionary(); - - public async Task HandleSocketMsg(string jsonStr) - { - JObject json; - try - { - json = JObject.Parse(jsonStr); - } - catch (Exception ex) - { - await SendAsync(_client, ex.Message); - return; - } - // 获取到消息 - string themeName = json[ThemeField]?.ToString(); - if (!ThemeConfigs.TryGetValue(themeName, out var handldConfig)) - { - return; - } - - object dataValue; - if (string.IsNullOrEmpty(handldConfig.DataField)) - { - dataValue = json.ToObject(handldConfig.DataType); - } - else - { - dataValue = json[handldConfig.DataField].ToObject(handldConfig.DataType); - } - await handldConfig.Invoke(dataValue, SendAsync); - } - - public void AddConfig(string themeName, Type dataType, MsgHandler msgHandler) - { - if (!ThemeConfigs.TryGetValue(themeName, out var handldConfig)) - { - handldConfig = new HandldConfig - { - DataField = themeName, - DataType = dataType - }; - ThemeConfigs.TryAdd(themeName, handldConfig); - } - handldConfig.HandldAsync += msgHandler; - } - public void RemoteConfig(string themeName, MsgHandler msgHandler) - { - if (ThemeConfigs.TryGetValue(themeName, out var handldConfig)) - { - handldConfig.HandldAsync -= msgHandler; - if (!handldConfig.HasSubscribers) - { - ThemeConfigs.TryRemove(themeName, out _); - } - } - } - #endregion*/ } } } diff --git a/Serein.Proto.WebSocket/Handle/WebSocketMsgContext.cs b/Serein.Proto.WebSocket/WebSocketHandleContext.cs similarity index 52% rename from Serein.Proto.WebSocket/Handle/WebSocketMsgContext.cs rename to Serein.Proto.WebSocket/WebSocketHandleContext.cs index acb8407..ebdb58b 100644 --- a/Serein.Proto.WebSocket/Handle/WebSocketMsgContext.cs +++ b/Serein.Proto.WebSocket/WebSocketHandleContext.cs @@ -1,21 +1,29 @@ using Serein.Library.Api; using Serein.Library.Utils; +using Serein.Proto.WebSocket.Handle; using System; using System.Threading.Tasks; -namespace Serein.Proto.WebSocket.Handle +namespace Serein.Proto.WebSocket { /// /// 消息处理上下文 /// - public class WebSocketMsgContext : IDisposable + public class WebSocketHandleContext : IDisposable { - public WebSocketMsgContext(Func sendAsync) + /// + /// 构造函数,传入发送消息的异步方法 + /// + /// + public WebSocketHandleContext(Func sendAsync) { _sendAsync = sendAsync; } + /// + /// 释放资源,清理消息上下文 + /// public void Dispose() { MsgRequest = null; @@ -23,7 +31,6 @@ namespace Serein.Proto.WebSocket.Handle MsgId = string.Empty; MsgData = null; MsgData = null; - _sendAsync = null; } /// @@ -37,7 +44,8 @@ namespace Serein.Proto.WebSocket.Handle } } } - public bool _handle = false; + private bool _handle = false; + /// /// 消息本体(IJsonToken) @@ -59,8 +67,16 @@ namespace Serein.Proto.WebSocket.Handle /// public IJsonToken? MsgData { get; set; } + /// + /// 异常外部感知使能 + /// + public Func, Task> OnExceptionTrackingAsync { get; set; } - private Func? _sendAsync; + /// + /// 发送消息 + /// + + private Func _sendAsync; /// /// 发送消息 @@ -69,48 +85,40 @@ namespace Serein.Proto.WebSocket.Handle /// public async Task SendAsync(string msg) { - if (_sendAsync is null) return; await _sendAsync.Invoke(msg); } - /// - /// 返回消息 + /// 触发异常追踪 /// - /// - /// - /// - /// - public async Task RepliedAsync(WebSocketHandleModuleConfig moduleConfig, - WebSocketMsgContext context, - object data) + public void TriggerExceptionTracking(string exMessage) { - if (moduleConfig.IsResponseUseReturn) + var ex = new Exception(exMessage); + Func func = async (data) => { - var responseContent = JsonHelper.Serialize(data); - await SendAsync(responseContent); - } - else + var msg = JsonHelper.Serialize(data); + await _sendAsync.Invoke(msg); + + }; + OnExceptionTrackingAsync.Invoke(ex, func); + } + + /// + /// 触发异常追踪 + /// + public void TriggerExceptionTracking(Exception ex) + { + Func func = async (data) => { + var msg = JsonHelper.Serialize(data); + await _sendAsync.Invoke(msg); - IJsonToken jsonData; - - jsonData = JsonHelper.Object(obj => - { - obj[moduleConfig.MsgIdJsonKey] = context.MsgId; - obj[moduleConfig.ThemeJsonKey] = context.MsgTheme; - obj[moduleConfig.DataJsonKey] = data is null ? null - : JsonHelper.FromObject(data); - }); - - var msg = jsonData.ToString(); - //Console.WriteLine($"[{msgId}] => {theme}"); - await SendAsync(msg); - } - + }; + OnExceptionTrackingAsync.Invoke(ex, func); } - + + } } diff --git a/Serein.Proto.WebSocket/TestExtension.cs b/Serein.Proto.WebSocket/WebSocketMessageTransmissionTool.cs similarity index 59% rename from Serein.Proto.WebSocket/TestExtension.cs rename to Serein.Proto.WebSocket/WebSocketMessageTransmissionTool.cs index a12effc..0a358da 100644 --- a/Serein.Proto.WebSocket/TestExtension.cs +++ b/Serein.Proto.WebSocket/WebSocketMessageTransmissionTool.cs @@ -1,28 +1,15 @@ -using Serein.Library.Utils; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; +using System.Threading.Channels; namespace Serein.Proto.WebSocket { /// /// 消息处理工具 /// - public class MsgHandleUtil + public class WebSocketMessageTransmissionTool { private readonly Channel _msgChannel; - /// - /// 初始化优先容器 - /// - /// - public MsgHandleUtil(int capacity = 100) + public WebSocketMessageTransmissionTool(int capacity = 100) { _msgChannel = Channel.CreateBounded(new BoundedChannelOptions(capacity) { @@ -72,22 +59,4 @@ namespace Serein.Proto.WebSocket _msgChannel.Writer.Complete(); } } - - - - - public class SocketExtension - { - /// - /// 发送消息 - /// - /// - /// - /// - public static async Task SendAsync(System.Net.WebSockets.WebSocket webSocket, string message) - { - var buffer = Encoding.UTF8.GetBytes(message); - await webSocket.SendAsync(new ArraySegment(buffer), WebSocketMessageType.Text, true, CancellationToken.None); - } - } } diff --git a/Serein.Proto.WebSocket/WebSocketServer.cs b/Serein.Proto.WebSocket/WebSocketServer.cs index b06b4fa..bc5b16e 100644 --- a/Serein.Proto.WebSocket/WebSocketServer.cs +++ b/Serein.Proto.WebSocket/WebSocketServer.cs @@ -189,7 +189,7 @@ namespace Serein.Proto.WebSocket return; } - var msgQueueUtil = new MsgHandleUtil(); + var msgQueueUtil = new WebSocketMessageTransmissionTool(); _ = Task.Run(async () => { await HandleMsgAsync(webSocket,msgQueueUtil, authorizedHelper); @@ -240,7 +240,7 @@ namespace Serein.Proto.WebSocket public async Task HandleMsgAsync(System.Net.WebSockets.WebSocket webSocket, - MsgHandleUtil msgQueueUtil, + WebSocketMessageTransmissionTool msgQueueUtil, WebSocketAuthorizedHelper authorizedHelper) { async Task sendasync(string text) @@ -265,7 +265,7 @@ namespace Serein.Proto.WebSocket } var context = new WebSocketMsgContext(sendasync); context.MsgRequest = JsonHelper.Parse(message); - MsgHandleHelper.Handle(context); // 处理消息 + MsgHandleHelper.HandleAsync(context); // 处理消息 //using (var context = new WebSocketMsgContext(sendasync)) //{ diff --git a/Workbench/App.xaml.cs b/Workbench/App.xaml.cs index ea53be4..e3ce229 100644 --- a/Workbench/App.xaml.cs +++ b/Workbench/App.xaml.cs @@ -51,7 +51,7 @@ namespace Serein.Workbench private void Application_Startup(object sender, StartupEventArgs e) { -#if DEBUG && true +#if DEBUG && false try { diff --git a/Workbench/Test.cs b/Workbench/Test.cs new file mode 100644 index 0000000..b2fa14c --- /dev/null +++ b/Workbench/Test.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Workbench +{ + internal class Test + { + } +}