From d3c3210ccce341e7da41f141f5410f32ffb6b94c Mon Sep 17 00:00:00 2001
From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com>
Date: Sun, 27 Jul 2025 23:34:01 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BB=8ESerein.Library=E5=88=86=E7=A6=BB?=
=?UTF-8?q?=E4=BA=86WebSocket/Modbus=EF=BC=9B=E6=96=B0=E5=A2=9E=E4=BA=86Js?=
=?UTF-8?q?on=E9=97=A8=E6=88=B7=E7=B1=BB=EF=BC=8C=E7=94=A8=E4=BA=8E?=
=?UTF-8?q?=E6=9C=AA=E6=9D=A5=E7=9A=84Http=E3=80=81WebSocket=E3=80=81Mqtt?=
=?UTF-8?q?=E3=80=81gRPC=E3=80=81QUIC=E6=89=A9=E5=B1=95=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Library/Api/IFlowEnvironment.cs | 4 +-
Library/Api/IFlowNode.cs | 2 +-
Library/Api/IJsonProvider.cs | 285 ++++++++++++
.../{IDynamicFlowNode.cs => ISereinFlow.cs} | 4 +-
.../FlowNode/LightweightFlowEnvironment.cs | 12 +-
Library/FlowNode/NodeDebugSetting.cs | 6 +-
Library/FlowNode/ParameterDetails.cs | 4 +-
Library/FlowNode/SereinProjectData.cs | 9 +-
Library/Network/Http/ApiHandleConfig.cs | 26 +-
Library/Network/Http/Router.cs | 9 +-
Library/Network/Http/SereinExtension.cs | 13 +-
Library/Network/WebSocket/Attribute.cs | 168 -------
Library/Network/WebSocket/Handle/Attribute.cs | 20 -
.../Network/WebSocket/SocketControlBase.cs | 11 -
Library/Serein.Library.csproj | 9 +-
Library/Utils/ConvertHelper.cs | 46 +-
Library/Utils/FlowTrigger/TaskFlowTrigger.cs | 9 +-
Library/Utils/JsonHelper.cs | 70 +++
Library/Utils/ObjectConvertHelper.cs | 7 +-
Library/Utils/RemoteMsgUtil.cs | 7 +-
Library/Utils/SereinEnv.cs | 3 +
.../SereinExpression/SereinConditionParser.cs | 5 +-
.../SerinExpressionEvaluator.cs | 4 +-
NodeFlow/Env/FlowEnvironment.cs | 16 +-
NodeFlow/Env/LocalFlowEnvironment.cs | 34 +-
NodeFlow/Model/Node/NodeModelBaseData.cs | 2 +-
NodeFlow/Model/Node/NodeModelBaseFunc.cs | 21 +-
NodeFlow/Model/Node/SingleExpOpNode.cs | 6 +-
NodeFlow/Model/Node/SingleFlowCallNode.cs | 11 +-
NodeFlow/Model/Node/SingleGlobalDataNode.cs | 10 +-
NodeFlow/Services/FlowCoreGenerateService.cs | 13 +-
NodeFlow/Services/FlowModelService.cs | 17 +-
.../NewtonsoftJsonProvider.cs | 90 ++++
.../NewtonsoftJsonToken.cs | 59 +++
.../Serein.Extend.NewtonsoftJson.csproj | 18 +
Serein.Proto.Modbus/HexExtensions.cs | 11 +
Serein.Proto.Modbus/IModbusClient.cs | 58 +++
Serein.Proto.Modbus/ModbusClientFactory.cs | 114 +++++
Serein.Proto.Modbus/ModbusException.cs | 29 ++
Serein.Proto.Modbus/ModbusFunctionCode.cs | 41 ++
Serein.Proto.Modbus/ModbusRequest.cs | 21 +
Serein.Proto.Modbus/ModbusRtuClient.cs | 322 +++++++++++++
Serein.Proto.Modbus/ModbusRtuRequest.cs | 15 +
Serein.Proto.Modbus/ModbusTcpClient.cs | 428 ++++++++++++++++++
Serein.Proto.Modbus/ModbusTcpRequest.cs | 12 +
Serein.Proto.Modbus/ModbusUdpClient.cs | 261 +++++++++++
.../Serein.Proto.Modbus.csproj | 18 +
.../Attributes/AutoSocketHandleAttribute.cs | 46 ++
.../Attributes/AutoSocketModuleAttribute.cs | 43 ++
.../Attributes/UseDataAttribute.cs | 23 +
.../Attributes/UseMsgIdAttribute.cs | 14 +
.../Attributes/UseRequestAttribute.cs | 12 +
Serein.Proto.WebSocket/Handle/Attribute.cs | 14 +
.../Handle/HandleConfiguration.cs | 73 +++
.../Handle/WebSocketHandleConfiguration.cs | 14 +
.../Handle/WebSocketHandleModule.cs | 37 +-
.../Handle/WebSocketHandleModuleConfig.cs | 6 +-
.../Handle/WebSocketMsgContext.cs | 61 ++-
.../Handle/WebSocketMsgHandleHelper.cs | 17 +-
Serein.Proto.WebSocket/ISocketHandleModule.cs | 10 +
.../Serein.Proto.WebSocket.csproj | 14 +
.../TestExtension.cs | 4 +-
.../WebSocketClient.cs | 15 +-
.../WebSocketServer.cs | 36 +-
Serein.Script/SereinScriptInterpreter.cs | 9 +-
Serein.Script/SereinScriptLexer.cs | 8 +-
Serein.Script/SereinScriptParser.cs | 11 +-
Serein.Script/SereinScriptToCsharpScript.cs | 13 +-
SereinFlow.sln | 18 +
Workbench/ViewModels/MainMenuBarViewModel.cs | 2 +-
70 files changed, 2306 insertions(+), 554 deletions(-)
create mode 100644 Library/Api/IJsonProvider.cs
rename Library/Api/{IDynamicFlowNode.cs => ISereinFlow.cs} (66%)
delete mode 100644 Library/Network/WebSocket/Attribute.cs
delete mode 100644 Library/Network/WebSocket/Handle/Attribute.cs
delete mode 100644 Library/Network/WebSocket/SocketControlBase.cs
create mode 100644 Library/Utils/JsonHelper.cs
create mode 100644 Serein.Extend.NewtonsoftJson/NewtonsoftJsonProvider.cs
create mode 100644 Serein.Extend.NewtonsoftJson/NewtonsoftJsonToken.cs
create mode 100644 Serein.Extend.NewtonsoftJson/Serein.Extend.NewtonsoftJson.csproj
create mode 100644 Serein.Proto.Modbus/HexExtensions.cs
create mode 100644 Serein.Proto.Modbus/IModbusClient.cs
create mode 100644 Serein.Proto.Modbus/ModbusClientFactory.cs
create mode 100644 Serein.Proto.Modbus/ModbusException.cs
create mode 100644 Serein.Proto.Modbus/ModbusFunctionCode.cs
create mode 100644 Serein.Proto.Modbus/ModbusRequest.cs
create mode 100644 Serein.Proto.Modbus/ModbusRtuClient.cs
create mode 100644 Serein.Proto.Modbus/ModbusRtuRequest.cs
create mode 100644 Serein.Proto.Modbus/ModbusTcpClient.cs
create mode 100644 Serein.Proto.Modbus/ModbusTcpRequest.cs
create mode 100644 Serein.Proto.Modbus/ModbusUdpClient.cs
create mode 100644 Serein.Proto.Modbus/Serein.Proto.Modbus.csproj
create mode 100644 Serein.Proto.WebSocket/Attributes/AutoSocketHandleAttribute.cs
create mode 100644 Serein.Proto.WebSocket/Attributes/AutoSocketModuleAttribute.cs
create mode 100644 Serein.Proto.WebSocket/Attributes/UseDataAttribute.cs
create mode 100644 Serein.Proto.WebSocket/Attributes/UseMsgIdAttribute.cs
create mode 100644 Serein.Proto.WebSocket/Attributes/UseRequestAttribute.cs
create mode 100644 Serein.Proto.WebSocket/Handle/Attribute.cs
create mode 100644 Serein.Proto.WebSocket/Handle/HandleConfiguration.cs
create mode 100644 Serein.Proto.WebSocket/Handle/WebSocketHandleConfiguration.cs
rename {Library/Network/WebSocket => Serein.Proto.WebSocket}/Handle/WebSocketHandleModule.cs (90%)
rename {Library/Network/WebSocket => Serein.Proto.WebSocket}/Handle/WebSocketHandleModuleConfig.cs (74%)
rename {Library/Network/WebSocket => Serein.Proto.WebSocket}/Handle/WebSocketMsgContext.cs (60%)
rename {Library/Network/WebSocket => Serein.Proto.WebSocket}/Handle/WebSocketMsgHandleHelper.cs (92%)
create mode 100644 Serein.Proto.WebSocket/ISocketHandleModule.cs
create mode 100644 Serein.Proto.WebSocket/Serein.Proto.WebSocket.csproj
rename {Library/Network/WebSocket => Serein.Proto.WebSocket}/TestExtension.cs (94%)
rename {Library/Network/WebSocket => Serein.Proto.WebSocket}/WebSocketClient.cs (93%)
rename {Library/Network/WebSocket => Serein.Proto.WebSocket}/WebSocketServer.cs (89%)
diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs
index 2288cdc..f567fe2 100644
--- a/Library/Api/IFlowEnvironment.cs
+++ b/Library/Api/IFlowEnvironment.cs
@@ -929,7 +929,7 @@ namespace Serein.Library.Api
#region 远程相关
- ///
+ /*///
/// 启动远程服务
///
Task StartRemoteServerAsync(int port = 7525);
@@ -957,7 +957,7 @@ namespace Serein.Library.Api
/// 退出远程环境
///
void ExitRemoteEnv();
-
+ */
///
/// (用于远程)通知节点属性变更
///
diff --git a/Library/Api/IFlowNode.cs b/Library/Api/IFlowNode.cs
index e4913db..8247a2a 100644
--- a/Library/Api/IFlowNode.cs
+++ b/Library/Api/IFlowNode.cs
@@ -14,7 +14,7 @@ namespace Serein.Library.Api
///
/// 流程节点
///
- public interface IFlowNode : INotifyPropertyChanged, IDynamicFlowNode
+ public interface IFlowNode : INotifyPropertyChanged, ISereinFlow
{
///
/// 节点持有的运行环境
diff --git a/Library/Api/IJsonProvider.cs b/Library/Api/IJsonProvider.cs
new file mode 100644
index 0000000..421d413
--- /dev/null
+++ b/Library/Api/IJsonProvider.cs
@@ -0,0 +1,285 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Serein.Library.Api
+{
+ ///
+ /// JSON数据交互的Token接口,允许使用不同的JSON库进行数据处理。
+ ///
+ public interface IJsonToken
+ {
+ ///
+ /// 获取指定名称的属性,如果存在则返回true,并通过out参数返回对应的IJsonToken对象。
+ ///
+ ///
+ ///
+ ///
+ bool TryGetValue(string name, out IJsonToken token);
+
+ ///
+ /// 获取指定名称的属性值,如果不存在则返回null。
+ ///
+ ///
+ ///
+#nullable enable
+ IJsonToken? GetValue(string name);
+
+ ///
+ /// 获取当前Token的字符串值,如果是null则返回null。
+ ///
+ ///
+ string GetString();
+
+ ///
+ /// 获取当前Token的整数值,如果是null则抛出异常。
+ ///
+ ///
+ int GetInt32();
+
+ ///
+ /// 获取当前Token的布尔值,如果是null则抛出异常。
+ ///
+ ///
+ bool GetBoolean();
+
+ ///
+ /// 判断当前Token是否为null。
+ ///
+ bool IsNull { get; }
+
+ ///
+ /// 枚举当前Token作为数组时的所有元素,返回一个IEnumerable<IJsonTokens>。
+ ///
+ ///
+ IEnumerable EnumerateArray();
+
+ ///
+ /// 将当前Token转换为指定类型的对象。
+ ///
+ ///
+ ///
+ T ToObject();
+
+ ///
+ /// 将当前Token转换为指定类型的对象。
+ ///
+ ///
+ ///
+ object ToObject(Type type);
+
+ ///
+ /// 将当前Token转换为字符串表示形式。
+ ///
+ ///
+ string ToString();
+ }
+
+
+ ///
+ /// 使用Json进行数据交互的门户,允许外部使用第三方JSON库进行数据处理。
+ ///
+ public interface IJsonProvider
+ {
+ ///
+ /// JSON文本转为指定类型
+ ///
+ ///
+ ///
+ ///
+ T Deserialize(string jsonText);
+
+ ///
+ /// JSON文本转为指定类型
+ ///
+ ///
+ ///
+ ///
+ object Deserialize(string jsonText, Type type);
+
+ ///
+ /// 从对象转换为JSON文本
+ ///
+ ///
+ ///
+ string Serialize(object obj);
+
+ ///
+ /// 解析为Token
+ ///
+ ///
+ ///
+ IJsonToken Parse(string json);
+
+ ///
+ /// 创建对象
+ ///
+ ///
+ ///
+ IJsonToken CreateObject(IDictionary values = null);
+
+ ///
+ /// 创建数组
+ ///
+ ///
+ ///
+ IJsonToken CreateArray(IEnumerable
public class ApiHandleConfig
{
+ private readonly IJsonPortal jsonPortal;
private readonly DelegateDetails delegateDetails;
///
@@ -39,8 +39,9 @@ namespace Serein.Library.Network
///
/// 添加处理配置
///
+ ///
///
- public ApiHandleConfig(MethodInfo methodInfo)
+ public ApiHandleConfig(IJsonPortal jsonPortal, MethodInfo methodInfo)
{
delegateDetails = new DelegateDetails(methodInfo);
var parameterInfos = methodInfo.GetParameters();
@@ -92,7 +93,7 @@ namespace Serein.Library.Network
}
else // if (type.IsValueType)
{
- args[i] = JsonConvert.DeserializeObject(argValue, type);
+ args[i] = jsonPortal.Deserialize(argValue, type); // JsonConvert.DeserializeObject(argValue, type);
}
}
else
@@ -103,7 +104,7 @@ namespace Serein.Library.Network
return args;
}
- public object[] GetArgsOfPost(Dictionary routeData, JObject jsonObject)
+ public object[] GetArgsOfPost(Dictionary routeData, IJsonToken jsonObject)
{
object[] args = new object[ParameterType.Length];
for (int i = 0; i < ParameterType.Length; i++)
@@ -120,7 +121,7 @@ namespace Serein.Library.Network
}
else // if (type.IsValueType)
{
- args[i] = JsonConvert.DeserializeObject(argValue, type);
+ args[i] = jsonPortal.Deserialize(argValue, type);
}
}
else
@@ -134,16 +135,15 @@ namespace Serein.Library.Network
}
else if (jsonObject != null)
{
- var jsonValue = jsonObject.GetValue(argName);
- if (jsonValue is null)
+ if(jsonObject.TryGetProperty(argName, out var jsonToken))
{
- // 值类型返回默认值,引用类型返回null
- args[i] = type.IsValueType ? Activator.CreateInstance(type) : null;
+ args[i] = jsonToken.ToObject(type);
}
else
{
- args[i] = jsonValue.ToObject(type);
+ args[i] = type.IsValueType ? Activator.CreateInstance(type) : null;
}
+
}
}
return args;
diff --git a/Library/Network/Http/Router.cs b/Library/Network/Http/Router.cs
index 632e059..994848c 100644
--- a/Library/Network/Http/Router.cs
+++ b/Library/Network/Http/Router.cs
@@ -1,8 +1,5 @@
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using Serein.Library.Api;
+using Serein.Library.Api;
using Serein.Library.Network;
-using Serein.Library.Utils;
using System;
using System.Collections;
using System.Collections.Concurrent;
@@ -47,7 +44,7 @@ namespace Serein.Library.Web
public class Router : IRouter
{
private readonly ISereinIOC SereinIOC; // 用于存储路由信息
-
+ private readonly IJsonPortal jsonPortal; // JSON门户
///
/// 控制器实例对象的类型,每次调用都会重新实例化,[Url - ControllerType]
@@ -109,7 +106,7 @@ namespace Serein.Library.Web
SereinEnv.WriteLine(InfoType.INFO, url);
var apiType = routeAttribute.ApiType.ToString();
- var config = new ApiHandleConfig(method);
+ var config = new ApiHandleConfig(jsonPortal, method);
if(!HandleModels.TryGetValue(apiType, out var configs))
{
configs = new ConcurrentDictionary();
diff --git a/Library/Network/Http/SereinExtension.cs b/Library/Network/Http/SereinExtension.cs
index cafaa6f..9074147 100644
--- a/Library/Network/Http/SereinExtension.cs
+++ b/Library/Network/Http/SereinExtension.cs
@@ -1,13 +1,6 @@
-using Newtonsoft.Json.Linq;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Serein.Library.Network.Http
+namespace Serein.Library.Network.Http
{
- internal static partial class SereinExtension
+ /*internal static partial class SereinExtension
{
#region JSON相关
@@ -60,5 +53,5 @@ namespace Serein.Library.Network.Http
}
}
#endregion
- }
+ }*/
}
diff --git a/Library/Network/WebSocket/Attribute.cs b/Library/Network/WebSocket/Attribute.cs
deleted file mode 100644
index af189a7..0000000
--- a/Library/Network/WebSocket/Attribute.cs
+++ /dev/null
@@ -1,168 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Threading.Tasks;
-using System.Threading;
-using System.Net.WebSockets;
-
-namespace Serein.Library.Network.WebSocketCommunication
-{
- ///
- /// 标记该类是处理模板,需要获取WebSocketServer/WebSocketClient了实例后,使用(Server/Client).MsgHandleHelper.AddModule()进行添加。
- /// 处理模板需要继承 ISocketHandleModule 接口,否则WebSocket接受到数据时,将无法进行调用相应的处理模板。
- /// 使用方式:
- /// [AutoSocketModule(ThemeKey = "theme", DataKey = "data")]
- /// public class PlcSocketService : ISocketHandleModule
- /// 类中方法示例:void AddUser(string name,int age)
- /// Json示例:{ "theme":"AddUser", //【ThemeKey】
- /// "data": { // 【DataKey】
- /// "name":"张三",
- /// "age":35, } }
- /// WebSocket中收到以上该Json时,通过ThemeKey获取到"AddUser",然后找到AddUser()方法
- /// 然后根据方法入参名称,从data对应的json数据中取出"name""age"对应的数据作为入参进行调用。AddUser("张三",35)
- ///
- ///
- [AttributeUsage(AttributeTargets.Class)]
- public sealed class AutoSocketModuleAttribute : Attribute
- {
- ///
- /// 业务标识
- ///
- public string ThemeKey;
- ///
- /// 数据标识
- ///
- public string DataKey;
- ///
- /// ID标识
- ///
- public string MsgIdKey;
- }
-
-
- ///
- /// 作用:WebSocket中处理Json时,将通过Json中ThemeKey 对应的内容(ThemeValue)自动路由到相应方法进行处理,同时要求Data中必须存在对应入参。
- /// 如果没有显式设置 ThemeValue,将默认使用方法名称作为ThemeValue。
- /// 如果没有显式设置 IsReturnValue 标记为 false ,当方法顺利完成(没有抛出异常,且返回对象非null),会自动转为json文本发送回去
- /// 如果没有显式设置 ArgNotNull 标记为 false ,当外部尝试调用时,若 Json Data 不包含响应的数据,将会被忽略此次调用
- /// 如果返回类型为Task或Task<TResult>,将会自动等待异步完成并获取结果(无法处理Task<Task<TResult>>的情况)。
- /// 如果返回了值类型,会自动装箱为引用对象。
- /// 如果有方法执行过程中发送消息的需求,请在入参中声明以下类型的成员,调用时将传入发送消息的委托。
- /// Action<string> : 发送文本内容。
- /// Action<object> : 会自动将对象解析为Json字符串,发送文本内容。
- /// Action<dynamic> : 会自动将对象解析为Json字符串,发送文本内容。
- /// Func<string,Task> : 异步发送文本内容。
- /// Func<object,Task> : 会自动将对象解析为Json字符串,异步发送文本内容。
- /// Func<dynamic,Task> : 会自动将对象解析为Json字符串,异步发送文本内容。
- ///
- [AttributeUsage(AttributeTargets.Method)]
- public sealed class AutoSocketHandleAttribute : Attribute
- {
- ///
- /// 描述Json业务字段,如果不设置,将默认使用方法名称。
- ///
- public string ThemeValue = string.Empty;
- ///
- /// 标记方法执行完成后是否需要将结果发送。
- /// 注意以下返回值,返回的 json 中将不会新建 DataKey 字段:
- /// 1.返回类型为 void
- /// 2.返回类型为 Task
- /// 2.返回类型为 Unit
- /// 补充:如果返回类型是Task<TResult>
- /// 会进行异步等待,当Task结束后,自动获取TResult进行发送(请避免Task<Task<TResult>>诸如此类的Task泛型嵌套)
- ///
- public bool IsReturnValue = true;
- ///
- /// 表示该方法所有入参不能为空(所需的参数在请求Json的Data不存在)
- /// 若有一个参数无法从data获取,则不会进行调用该方法
- /// 如果设置该属性为 false ,但某些入参不能为空,而不希望在代码中进行检查,请为入参添加[NotNull]/[Needful]特性
- ///
- public bool ArgNotNull = true;
- }
-
- ///
- /// 使用 DataKey 整体数据
- ///
- [AttributeUsage(AttributeTargets.Parameter)]
- public sealed class UseDataAttribute : Attribute
- {
- }
- ///
- /// 使用 MsgIdKey 整体数据
- ///
- [AttributeUsage(AttributeTargets.Parameter)]
- public sealed class UseMsgIdAttribute : Attribute
- {
- }
-
-
- internal class WebSocketHandleConfiguration : HandleConfiguration
- {
- ///
- /// 主题
- ///
- public string ThemeValue { get; set; } = string.Empty;
- }
-
- ///
- /// socket模块处理数据配置
- ///
-
- public class HandleConfiguration
- {
- ///
- /// Emit委托
- ///
- public DelegateDetails DelegateDetails { get; set; }
-
- ///
- /// 未捕获的异常跟踪
- ///
- public Action> OnExceptionTracking { get; set; }
-
- ///
- /// 所使用的实例
- ///
- public ISocketHandleModule Instance { get; set; }
-
- ///
- /// 是否需要返回
- ///
- public bool IsReturnValue { get; set; } = true;
-
- ///
- /// 是否要求必须不为null
- ///
- public bool ArgNotNull { get; set; } = true;
-
- ///
- /// 是否使Data整体内容作为入参参数
- ///
- public bool[] UseData { get; set; }
-
- ///
- /// 是否使用消息ID作为入参参数
- ///
- public bool[] UseMsgId { get; set; }
-
- ///
- /// 参数名称
- ///
- public string[] ParameterName { get; set; }
-
- ///
- /// 参数类型
- ///
- public Type[] ParameterType { get; set; }
-
- ///
- /// 是否检查变量为空
- ///
- public bool[] IsCheckArgNotNull { get; set; }
-
- }
-
-
-
-
-}
diff --git a/Library/Network/WebSocket/Handle/Attribute.cs b/Library/Network/WebSocket/Handle/Attribute.cs
deleted file mode 100644
index 051898f..0000000
--- a/Library/Network/WebSocket/Handle/Attribute.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Newtonsoft.Json.Linq;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Serein.Library.Network.WebSocketCommunication.Handle
-{
- ///
- /// 表示参数不能为空(Net462不能使用NutNull的情况)
- ///
- public sealed class NeedfulAttribute : Attribute
- {
- }
-
-
-
-}
diff --git a/Library/Network/WebSocket/SocketControlBase.cs b/Library/Network/WebSocket/SocketControlBase.cs
deleted file mode 100644
index e194fae..0000000
--- a/Library/Network/WebSocket/SocketControlBase.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using System;
-
-namespace Serein.Library.Network.WebSocketCommunication
-{
- public interface ISocketHandleModule
- {
- Guid HandleGuid { get; }
- }
-
-
-}
diff --git a/Library/Serein.Library.csproj b/Library/Serein.Library.csproj
index b6d829b..a3436b5 100644
--- a/Library/Serein.Library.csproj
+++ b/Library/Serein.Library.csproj
@@ -24,13 +24,13 @@
-
+
-
+
-
+
@@ -40,6 +40,7 @@
+
@@ -47,10 +48,10 @@
-
+
diff --git a/Library/Utils/ConvertHelper.cs b/Library/Utils/ConvertHelper.cs
index ecec2d5..090f79d 100644
--- a/Library/Utils/ConvertHelper.cs
+++ b/Library/Utils/ConvertHelper.cs
@@ -1,11 +1,5 @@
-using Newtonsoft.Json;
-using System;
-using System.Collections.Generic;
+using System;
using System.Globalization;
-using System.Linq;
-using System.Net.Http.Headers;
-using System.Text;
-using System.Threading.Tasks;
namespace Serein.Library.Utils
{
@@ -118,37 +112,7 @@ namespace Serein.Library.Utils
- ///
- /// 对象转JSON文本
- ///
- ///
- ///
- public static string ToJsonText(this object obj)
- {
- var jsonText = JsonConvert.SerializeObject(obj, Formatting.Indented);
- return jsonText;
- }
-
- ///
- /// JSON文本转对象
- ///
- /// 转换类型
- /// JSON文本
- ///
- public static T ToJsonObject(this string json)
- {
- try
- {
- return JsonConvert.DeserializeObject(json);
- }
- catch (Exception)
- {
- return default(T);
- }
- }
-
-
-
+
///
/// 对象转换为对应类型
///
@@ -162,18 +126,18 @@ namespace Serein.Library.Utils
{
return default;
}
- return (TResult)data.ToConvert(type);
+ return (TResult)data.ToConvertValueType(type);
}
///
- /// 对象转换(好像没啥用)
+ /// 对象转换
///
///
///
///
- public static object ToConvert(this object data, Type type)
+ public static object ToConvertValueType(this object data, Type type)
{
if (type.IsValueType)
{
diff --git a/Library/Utils/FlowTrigger/TaskFlowTrigger.cs b/Library/Utils/FlowTrigger/TaskFlowTrigger.cs
index 2b3c45c..4822dec 100644
--- a/Library/Utils/FlowTrigger/TaskFlowTrigger.cs
+++ b/Library/Utils/FlowTrigger/TaskFlowTrigger.cs
@@ -1,16 +1,9 @@
-using Microsoft.Extensions.ObjectPool;
-using Newtonsoft.Json.Linq;
-using Serein.Library.Api;
-using Serein.Library.Utils;
+using Serein.Library.Api;
using System;
using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading;
-using System.Threading.Channels;
using System.Threading.Tasks;
-using System.Transactions;
namespace Serein.Library.Utils
{
diff --git a/Library/Utils/JsonHelper.cs b/Library/Utils/JsonHelper.cs
new file mode 100644
index 0000000..00a73b6
--- /dev/null
+++ b/Library/Utils/JsonHelper.cs
@@ -0,0 +1,70 @@
+using Serein.Library.Api;
+using System;
+using System.Collections.Generic;
+
+namespace Serein.Library.Utils
+{
+ ///
+ /// Json门户类,需要你提供实现
+ ///
+ public static class JsonHelper
+ {
+ ///
+ /// Json门户类,需要你提供实现
+ ///
+ private static IJsonProvider provider;
+
+ ///
+ /// 使用第三方包进行解析
+ ///
+ ///
+ public static void UseJsonLibrary(IJsonProvider jsonPortal)
+ {
+ JsonHelper.provider = jsonPortal;
+ }
+
+
+ public static T Deserialize(string jsonText)
+ {
+ return provider.Deserialize(jsonText);
+ }
+
+ public static object Deserialize(string jsonText, Type type)
+ {
+ return provider.Deserialize(jsonText, type);
+
+ }
+
+ public static IJsonToken Parse(string json)
+ {
+ return provider.Parse(json);
+
+ }
+
+ public static string Serialize(object obj)
+ {
+ return provider.Serialize(obj);
+ }
+ public static IJsonToken Object(Action> init)
+ {
+ var dict = new Dictionary();
+ init(dict);
+ return provider.CreateObject(dict);
+ }
+
+ public static IJsonToken Array(IEnumerable values)
+ {
+ return provider.CreateArray(values);
+ }
+
+ public static IJsonToken FromObject(object obj)
+ {
+ if (obj is System.Collections.IEnumerable && !(obj is string))
+ return provider.CreateObject(obj as IDictionary);
+ return provider.CreateArray(obj as IEnumerable);
+
+ }
+ }
+
+
+}
diff --git a/Library/Utils/ObjectConvertHelper.cs b/Library/Utils/ObjectConvertHelper.cs
index 1442c48..06d0a62 100644
--- a/Library/Utils/ObjectConvertHelper.cs
+++ b/Library/Utils/ObjectConvertHelper.cs
@@ -1,12 +1,7 @@
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using Newtonsoft.Json.Serialization;
-using System;
+using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace Serein.Library.Utils
{
diff --git a/Library/Utils/RemoteMsgUtil.cs b/Library/Utils/RemoteMsgUtil.cs
index 88fab40..be184d2 100644
--- a/Library/Utils/RemoteMsgUtil.cs
+++ b/Library/Utils/RemoteMsgUtil.cs
@@ -1,11 +1,6 @@
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using Serein.Library.Network.WebSocketCommunication;
+using Serein.Library.Network.WebSocketCommunication;
using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Net.Sockets;
-using System.Text;
using System.Threading.Tasks;
namespace Serein.Library.Utils
diff --git a/Library/Utils/SereinEnv.cs b/Library/Utils/SereinEnv.cs
index d5348d7..b475451 100644
--- a/Library/Utils/SereinEnv.cs
+++ b/Library/Utils/SereinEnv.cs
@@ -12,6 +12,9 @@ using System.Xml.Linq;
namespace Serein.Library
{
+ ///
+ /// 全局运行环境
+ ///
public static class SereinEnv
{
private static IFlowEnvironment environment;
diff --git a/Library/Utils/SereinExpression/SereinConditionParser.cs b/Library/Utils/SereinExpression/SereinConditionParser.cs
index f8cf317..912367b 100644
--- a/Library/Utils/SereinExpression/SereinConditionParser.cs
+++ b/Library/Utils/SereinExpression/SereinConditionParser.cs
@@ -1,9 +1,6 @@
-using Newtonsoft.Json.Linq;
-using Serein.Library.Utils;
-using Serein.Library.Utils.SereinExpression.Resolver;
+using Serein.Library.Utils.SereinExpression.Resolver;
using System;
using System.Collections.Generic;
-using System.ComponentModel.Design;
using System.Globalization;
using System.Linq;
using System.Reflection;
diff --git a/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs b/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs
index 916ffc9..99a7aa1 100644
--- a/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs
+++ b/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs
@@ -276,7 +276,7 @@ namespace Serein.Library.Utils.SereinExpression
if (hasType)
{
- target = target.ToConvert(type);
+ target = target.ToConvertValueType(type);
}
@@ -437,7 +437,7 @@ namespace Serein.Library.Utils.SereinExpression
int endIndex = expression.IndexOf('>');
if(endIndex == expression.Length -1)
{
- return value.ToConvert(type);
+ return value.ToConvertValueType(type);
}
else
{
diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs
index 2ab330c..d23aa00 100644
--- a/NodeFlow/Env/FlowEnvironment.cs
+++ b/NodeFlow/Env/FlowEnvironment.cs
@@ -152,7 +152,7 @@ namespace Serein.NodeFlow.Env
currentFlowEnvironment.FlowControl.ActivateFlipflopNode(nodeGuid);
}
- ///
+ /* ///
public async Task<(bool, RemoteMsgUtil)> ConnectRemoteEnv(string addres, int port, string token)
{
// 连接成功,切换远程环境
@@ -160,11 +160,11 @@ namespace Serein.NodeFlow.Env
if (isConnect)
{
- /* remoteFlowEnvironment ??= new RemoteFlowEnvironment(remoteMsgUtil, this.Event, this.UIContextOperation);
- currentFlowEnvironment = remoteFlowEnvironment;*/
+ *//* remoteFlowEnvironment ??= new RemoteFlowEnvironment(remoteMsgUtil, this.Event, this.UIContextOperation);
+ currentFlowEnvironment = remoteFlowEnvironment;*//*
}
return (isConnect, remoteMsgUtil);
- }
+ }*/
///
public async Task ExitFlowAsync()
@@ -172,7 +172,7 @@ namespace Serein.NodeFlow.Env
return await currentFlowEnvironment.FlowControl.ExitFlowAsync();
}
- ///
+ /* ///
public void ExitRemoteEnv()
{
currentFlowEnvironment.ExitRemoteEnv();
@@ -183,7 +183,7 @@ namespace Serein.NodeFlow.Env
public async Task GetEnvInfoAsync()
{
return await currentFlowEnvironment.GetEnvInfoAsync();
- }
+ }*/
///
public async Task GetProjectInfoAsync()
@@ -288,7 +288,7 @@ namespace Serein.NodeFlow.Env
return await currentFlowEnvironment.FlowControl.StartFlowAsync(startNodeGuid);
}
- ///
+ /* ///
public async Task StartRemoteServerAsync(int port = 7525)
{
await currentFlowEnvironment.StartRemoteServerAsync(port);
@@ -298,7 +298,7 @@ namespace Serein.NodeFlow.Env
public void StopRemoteServer()
{
currentFlowEnvironment.StopRemoteServer();
- }
+ }*/
///
public void TerminateFlipflopNode(string nodeGuid)
diff --git a/NodeFlow/Env/LocalFlowEnvironment.cs b/NodeFlow/Env/LocalFlowEnvironment.cs
index 36f2136..eed67f7 100644
--- a/NodeFlow/Env/LocalFlowEnvironment.cs
+++ b/NodeFlow/Env/LocalFlowEnvironment.cs
@@ -1,25 +1,10 @@
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Newtonsoft.Json;
-using Serein.Library;
+using Serein.Library;
using Serein.Library.Api;
-using Serein.Library.FlowNode;
using Serein.Library.Utils;
using Serein.Library.Utils.SereinExpression;
using Serein.NodeFlow.Services;
using Serein.NodeFlow.Tool;
-using Serein.Script.Node;
-using System;
-using System.Collections.Specialized;
-using System.Diagnostics;
-using System.Net.Http.Headers;
-using System.Net.Mime;
-using System.Reactive;
-using System.Reflection;
-using System.Security.AccessControl;
using System.Text;
-using System.Threading.Tasks;
-using System.Timers;
-using static Serein.Library.Api.IFlowEnvironment;
namespace Serein.NodeFlow.Env
{
@@ -296,7 +281,7 @@ namespace Serein.NodeFlow.Env
public void LoadProject(string filePath)
{
string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容
- var FlowProjectData = JsonConvert.DeserializeObject(content);
+ var FlowProjectData = JsonHelper.Deserialize(content);
var FileDataPath = System.IO.Path.GetDirectoryName(filePath)!; // filePath;//
@@ -351,7 +336,7 @@ namespace Serein.NodeFlow.Env
public async Task LoadProjetAsync(string filePath)
{
string content = await System.IO.File.ReadAllTextAsync(filePath); // 读取整个文件内容
- var FlowProjectData = JsonConvert.DeserializeObject(content);
+ var FlowProjectData = JsonHelper.Deserialize(content);
var FileDataPath = System.IO.Path.GetDirectoryName(filePath)!; // filePath;//
if(FlowProjectData is null)
{
@@ -396,9 +381,10 @@ namespace Serein.NodeFlow.Env
/// 远程环境地址
/// 远程环境端口
/// 密码
- public async Task<(bool, RemoteMsgUtil)> ConnectRemoteEnv(string addres, int port, string token)
+ /*public async Task<(bool, RemoteMsgUtil)> ConnectRemoteEnv(string addres, int port, string token)
{
- if (IsControlRemoteEnv)
+ throw new NotImplementedException("远程环境未实现的方法 ConnectRemoteEnv");
+ *//*if (IsControlRemoteEnv)
{
await Console.Out.WriteLineAsync($"当前已经连接远程环境");
return (false, null);
@@ -410,9 +396,9 @@ namespace Serein.NodeFlow.Env
Addres = addres,
Port = port,
Token = token,
- /*ThemeJsonKey = LocalFlowEnvironment.ThemeKey,
+ *//*ThemeJsonKey = LocalFlowEnvironment.ThemeKey,
MsgIdJsonKey = LocalFlowEnvironment.MsgIdKey,
- DataJsonKey = LocalFlowEnvironment.DataKey,*/
+ DataJsonKey = LocalFlowEnvironment.DataKey,*//*
};
var remoteMsgUtil = new RemoteMsgUtil(controlConfiguration);
var result = await remoteMsgUtil.ConnectAsync();
@@ -423,8 +409,8 @@ namespace Serein.NodeFlow.Env
}
await Console.Out.WriteLineAsync("连接成功,开始验证Token");
IsControlRemoteEnv = true;
- return (true, remoteMsgUtil);
- }
+ return (true, remoteMsgUtil);*//*
+ }*/
///
/// 退出远程环境
diff --git a/NodeFlow/Model/Node/NodeModelBaseData.cs b/NodeFlow/Model/Node/NodeModelBaseData.cs
index e4ab624..ffe90e2 100644
--- a/NodeFlow/Model/Node/NodeModelBaseData.cs
+++ b/NodeFlow/Model/Node/NodeModelBaseData.cs
@@ -80,7 +80,7 @@ namespace Serein.NodeFlow.Model
}
- public abstract partial class NodeModelBase : IDynamicFlowNode
+ public abstract partial class NodeModelBase : ISereinFlow
{
///
/// 是否为基础节点
diff --git a/NodeFlow/Model/Node/NodeModelBaseFunc.cs b/NodeFlow/Model/Node/NodeModelBaseFunc.cs
index 650f321..e945d8a 100644
--- a/NodeFlow/Model/Node/NodeModelBaseFunc.cs
+++ b/NodeFlow/Model/Node/NodeModelBaseFunc.cs
@@ -1,22 +1,5 @@
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using Serein.Library;
+using Serein.Library;
using Serein.Library.Api;
-using Serein.Library.Utils;
-using Serein.Library.Utils.SereinExpression;
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.ComponentModel.Design;
-using System.Linq;
-using System.Linq.Expressions;
-using System.Net.Http.Headers;
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Xml.Linq;
namespace Serein.NodeFlow.Model
{
@@ -29,7 +12,7 @@ namespace Serein.NodeFlow.Model
///
/// 节点基类
///
- public abstract partial class NodeModelBase : IDynamicFlowNode
+ public abstract partial class NodeModelBase : ISereinFlow
{
///
/// 实体节点创建完成后调用的方法,调用时间早于 LoadInfo() 方法
diff --git a/NodeFlow/Model/Node/SingleExpOpNode.cs b/NodeFlow/Model/Node/SingleExpOpNode.cs
index 9e4ccf3..9151ca0 100644
--- a/NodeFlow/Model/Node/SingleExpOpNode.cs
+++ b/NodeFlow/Model/Node/SingleExpOpNode.cs
@@ -1,11 +1,7 @@
-using Newtonsoft.Json.Linq;
-using Serein.Library;
+using Serein.Library;
using Serein.Library.Api;
-using Serein.Library.Utils;
using Serein.Library.Utils.SereinExpression;
using System.Dynamic;
-using System.Reactive;
-using System.Reflection.Metadata;
namespace Serein.NodeFlow.Model
{
diff --git a/NodeFlow/Model/Node/SingleFlowCallNode.cs b/NodeFlow/Model/Node/SingleFlowCallNode.cs
index 141215b..a90f060 100644
--- a/NodeFlow/Model/Node/SingleFlowCallNode.cs
+++ b/NodeFlow/Model/Node/SingleFlowCallNode.cs
@@ -1,17 +1,8 @@
-using Newtonsoft.Json.Linq;
-using Serein.Library;
+using Serein.Library;
using Serein.Library.Api;
using Serein.NodeFlow.Services;
-using Serein.Script;
-using System;
-using System.Collections.Generic;
using System.Dynamic;
-using System.Linq;
using System.Reflection;
-using System.Text;
-using System.Threading.Tasks;
-using System.Xml.Linq;
-using static System.Runtime.InteropServices.JavaScript.JSType;
namespace Serein.NodeFlow.Model
{
diff --git a/NodeFlow/Model/Node/SingleGlobalDataNode.cs b/NodeFlow/Model/Node/SingleGlobalDataNode.cs
index 72069a3..1fd7ac7 100644
--- a/NodeFlow/Model/Node/SingleGlobalDataNode.cs
+++ b/NodeFlow/Model/Node/SingleGlobalDataNode.cs
@@ -1,14 +1,6 @@
-using Newtonsoft.Json.Linq;
-using Serein.Library;
+using Serein.Library;
using Serein.Library.Api;
-using Serein.Library.Utils;
-using System;
-using System.Collections.Generic;
using System.Dynamic;
-using System.Linq;
-using System.Linq.Expressions;
-using System.Text;
-using System.Threading.Tasks;
namespace Serein.NodeFlow.Model
{
diff --git a/NodeFlow/Services/FlowCoreGenerateService.cs b/NodeFlow/Services/FlowCoreGenerateService.cs
index ebe9c19..fe337be 100644
--- a/NodeFlow/Services/FlowCoreGenerateService.cs
+++ b/NodeFlow/Services/FlowCoreGenerateService.cs
@@ -1,18 +1,11 @@
-using Newtonsoft.Json.Linq;
-using Serein.Library;
+using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Utils;
-using Serein.NodeFlow.Env;
using Serein.NodeFlow.Model;
using Serein.Script;
-using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
-using System.Threading.Tasks;
-using System.Xml.Linq;
namespace Serein.NodeFlow.Services
{
@@ -233,7 +226,7 @@ namespace Serein.NodeFlow.Services
}
else
{
- var value = pd.DataValue.ToConvert(parameterInfo.ParameterType);
+ var value = pd.DataValue.ToConvertValueType(parameterInfo.ParameterType);
sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = (global::{paramtTypeFullName}){value}; // 获取当前节点的上一节点数据");
}
@@ -603,7 +596,7 @@ namespace Serein.NodeFlow.Services
}
else
{
- var value = pd.DataValue.ToConvert(parameterInfo.ParameterType);
+ var value = pd.DataValue.ToConvertValueType(parameterInfo.ParameterType);
sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = (global::{paramtTypeFullName}){value}; // 获取当前节点的上一节点数据");
}
diff --git a/NodeFlow/Services/FlowModelService.cs b/NodeFlow/Services/FlowModelService.cs
index b2f87df..9a54861 100644
--- a/NodeFlow/Services/FlowModelService.cs
+++ b/NodeFlow/Services/FlowModelService.cs
@@ -1,21 +1,6 @@
-using Microsoft.Extensions.ObjectPool;
-using Newtonsoft.Json.Linq;
-using Serein.Library;
+using Serein.Library;
using Serein.Library.Api;
-using Serein.Library.Utils;
-using Serein.NodeFlow;
using Serein.NodeFlow.Model;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Text;
-using System.Threading.Tasks;
-using System.Xml;
-using System.Xml.Linq;
namespace Serein.NodeFlow.Services
{
diff --git a/Serein.Extend.NewtonsoftJson/NewtonsoftJsonProvider.cs b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonProvider.cs
new file mode 100644
index 0000000..6f01731
--- /dev/null
+++ b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonProvider.cs
@@ -0,0 +1,90 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+using Serein.Library.Api;
+
+namespace Serein.Extend.NewtonsoftJson
+{
+
+ public enum JsonType
+ {
+ Default = 0,
+ Web = 1,
+ }
+ ///
+ /// 基于Newtonsoft.Json的IJsonProvider实现
+ ///
+ public sealed class NewtonsoftJsonProvider : IJsonProvider
+ {
+ private JsonSerializerSettings settings;
+
+ public NewtonsoftJsonProvider()
+ {
+
+ }
+
+ public NewtonsoftJsonProvider(JsonType jsonType)
+ {
+ settings = jsonType switch
+ {
+ JsonType.Web => new JsonSerializerSettings
+ {
+ ContractResolver = new CamelCasePropertyNamesContractResolver(), // 控制首字母小写
+ NullValueHandling = NullValueHandling.Ignore // 可选:忽略 null
+ },
+ _ => new JsonSerializerSettings
+ {
+ },
+ };
+ }
+
+ public NewtonsoftJsonProvider(JsonSerializerSettings settings)
+ {
+ settings = new JsonSerializerSettings
+ {
+ ContractResolver = new CamelCasePropertyNamesContractResolver(), // 控制首字母小写
+ NullValueHandling = NullValueHandling.Ignore // 可选:忽略 null
+ };
+ this.settings = settings;
+ }
+
+ public T? Deserialize(string jsonText)
+ {
+ return JsonConvert.DeserializeObject(jsonText, settings);
+ }
+
+ public object? Deserialize(string jsonText, Type type)
+ {
+ return JsonConvert.DeserializeObject(jsonText, type, settings);
+ }
+
+ public string Serialize(object obj)
+ {
+ return JsonConvert.SerializeObject(obj, settings);
+ }
+
+ public IJsonToken Parse(string json)
+ {
+ var token = JToken.Parse(json);
+ return new NewtonsoftJsonToken(token);
+ }
+
+ public IJsonToken CreateObject(IDictionary values = null)
+ {
+ var jobj = values != null ? JObject.FromObject(values) : new JObject();
+ return new NewtonsoftJsonToken(jobj);
+ }
+
+ public IJsonToken CreateArray(IEnumerable values = null)
+ {
+ var jarr = values != null ? JArray.FromObject(values) : new JArray();
+ return new NewtonsoftJsonToken(jarr);
+ }
+
+ public IJsonToken FromObject(object obj)
+ {
+ var token = JToken.FromObject(obj);
+ return new NewtonsoftJsonToken(token);
+ }
+ }
+}
diff --git a/Serein.Extend.NewtonsoftJson/NewtonsoftJsonToken.cs b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonToken.cs
new file mode 100644
index 0000000..daaf82b
--- /dev/null
+++ b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonToken.cs
@@ -0,0 +1,59 @@
+using Newtonsoft.Json.Linq;
+using Serein.Library.Api;
+
+namespace Serein.Extend.NewtonsoftJson
+{
+ ///
+ /// 基于Newtonsoft.Json的IJsonToken实现
+ ///
+ public sealed class NewtonsoftJsonToken : IJsonToken
+ {
+ private readonly JToken _token;
+
+ public NewtonsoftJsonToken(JToken token)
+ {
+ _token = token ?? throw new ArgumentNullException(nameof(token));
+ }
+
+ public bool TryGetValue(string name, out IJsonToken token)
+ {
+ if (_token is JObject obj && obj.TryGetValue(name, out JToken value))
+ {
+ token = new NewtonsoftJsonToken(value);
+ return true;
+ }
+ token = null;
+ return false;
+ }
+
+ public IJsonToken? GetValue(string name)
+ {
+ if (_token is JObject obj && obj.TryGetValue(name, out JToken value))
+ {
+ return new NewtonsoftJsonToken(value);
+ }
+ return null;
+ }
+
+ public string GetString() => _token.Type == JTokenType.Null ? null : _token.ToString();
+
+ public int GetInt32() => _token.Value();
+
+ public bool GetBoolean() => _token.Value();
+
+ public bool IsNull => _token.Type == JTokenType.Null || _token.Type == JTokenType.Undefined;
+
+ public IEnumerable EnumerateArray()
+ {
+ if (_token is JArray arr)
+ return arr.Select(x => new NewtonsoftJsonToken(x));
+ throw new InvalidOperationException("当前Token不是数组类型。");
+ }
+
+ public T ToObject() => _token.ToObject();
+
+ public object ToObject(Type type) => _token.ToObject(type);
+
+ public override string ToString() => _token.ToString();
+ }
+}
diff --git a/Serein.Extend.NewtonsoftJson/Serein.Extend.NewtonsoftJson.csproj b/Serein.Extend.NewtonsoftJson/Serein.Extend.NewtonsoftJson.csproj
new file mode 100644
index 0000000..a3f1f49
--- /dev/null
+++ b/Serein.Extend.NewtonsoftJson/Serein.Extend.NewtonsoftJson.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0;net462
+ enable
+ latest
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Serein.Proto.Modbus/HexExtensions.cs b/Serein.Proto.Modbus/HexExtensions.cs
new file mode 100644
index 0000000..05b6532
--- /dev/null
+++ b/Serein.Proto.Modbus/HexExtensions.cs
@@ -0,0 +1,11 @@
+namespace Serein.Proto.Modbus
+{
+ public static class HexExtensions
+ {
+ public static string ToHexString(this byte[] data, string separator = " ")
+ {
+ if (data == null) return string.Empty;
+ return BitConverter.ToString(data).Replace("-", separator);
+ }
+ }
+}
diff --git a/Serein.Proto.Modbus/IModbusClient.cs b/Serein.Proto.Modbus/IModbusClient.cs
new file mode 100644
index 0000000..c412c59
--- /dev/null
+++ b/Serein.Proto.Modbus/IModbusClient.cs
@@ -0,0 +1,58 @@
+namespace Serein.Proto.Modbus
+{
+ ///
+ /// Modbus 客户端通用接口 (TCP/RTU 通用)
+ ///
+ public interface IModbusClient : IDisposable
+ {
+ ///
+ /// 报文发送时
+ ///
+ Action OnTx { get; set; }
+
+ ///
+ /// 接收到报文时
+ ///
+ Action OnRx { get; set; }
+
+ ///
+ /// 读取线圈状态 (0x01)
+ ///
+ Task ReadCoils(ushort startAddress, ushort quantity);
+
+ ///
+ /// 读取离散输入状态 (0x02)
+ ///
+ Task ReadDiscreteInputs(ushort startAddress, ushort quantity);
+
+ ///
+ /// 读取保持寄存器 (0x03)
+ ///
+ Task ReadHoldingRegisters(ushort startAddress, ushort quantity);
+
+ ///
+ /// 读取输入寄存器 (0x04)
+ ///
+ Task ReadInputRegisters(ushort startAddress, ushort quantity);
+
+ ///
+ /// 写单个线圈 (0x05)
+ ///
+ Task WriteSingleCoil(ushort address, bool value);
+
+ ///
+ /// 写单个寄存器 (0x06)
+ ///
+ Task WriteSingleRegister(ushort address, ushort value);
+
+ ///
+ /// 写多个线圈 (0x0F)
+ ///
+ Task WriteMultipleCoils(ushort startAddress, bool[] values);
+
+ ///
+ /// 写多个寄存器 (0x10)
+ ///
+ Task WriteMultipleRegisters(ushort startAddress, ushort[] values);
+ }
+}
diff --git a/Serein.Proto.Modbus/ModbusClientFactory.cs b/Serein.Proto.Modbus/ModbusClientFactory.cs
new file mode 100644
index 0000000..9552645
--- /dev/null
+++ b/Serein.Proto.Modbus/ModbusClientFactory.cs
@@ -0,0 +1,114 @@
+using System.IO.Ports;
+
+namespace Serein.Proto.Modbus
+{
+ public static class ModbusClientFactory
+ {
+ private static readonly char[] separator = new[] { ':' };
+
+ ///
+ /// 创建 Modbus 客户端实例
+ ///
+ ///
+ /// 连接字符串格式:
+ /// TCP示例:"tcp:192.168.1.100:502"
+ /// UCP示例:"ucp:192.168.1.100:502"
+ /// RTU示例:"rtu:COM3:9600:1" (格式:rtu:串口名:波特率:从站地址)
+ ///
+ public static IModbusClient Create(string connectionString)
+ {
+ if (string.IsNullOrWhiteSpace(connectionString))
+ throw new ArgumentException("connectionString 不能为空");
+ var parts = connectionString.Split(separator, StringSplitOptions.RemoveEmptyEntries);
+ //var parts = connectionString.Split(':',options: StringSplitOptions.RemoveEmptyEntries);
+ if (parts.Length < 2)
+ throw new ArgumentException("connectionString 格式错误");
+
+ var protocol = parts[0].ToLower();
+
+ if (protocol == "tcp")
+ {
+ // tcp:host:port
+ if (parts.Length < 3)
+ throw new ArgumentException("TCP格式应为 tcp:host:port");
+
+ string host = parts[1];
+ if (!int.TryParse(parts[2], out int port))
+ port = 502; // 默认端口
+
+ return new ModbusTcpClient(host, port);
+ }
+ else if (protocol == "ucp")
+ {
+ // ucp:host:port
+ if (parts.Length < 3)
+ throw new ArgumentException("TCP格式应为 tcp:host:port");
+
+ string host = parts[1];
+ if (!int.TryParse(parts[2], out int port))
+ port = 502; // 默认端口
+
+ return new ModbusUdpClient(host, port);
+ }
+ else if (protocol == "rtu")
+ {
+ // rtu:portName:baudRate:slaveId
+ if (parts.Length < 4)
+ throw new ArgumentException("RTU格式应为 rtu:portName:baudRate:slaveId");
+
+ string portName = parts[1];
+ if (!int.TryParse(parts[2], out int baudRate))
+ baudRate = 9600;
+
+ if (!byte.TryParse(parts[3], out byte slaveId))
+ slaveId = 1;
+
+ return new ModbusRtuClient(portName, baudRate, slaveId: slaveId);
+ }
+ else
+ {
+ throw new NotSupportedException($"不支持的协议类型: {protocol}");
+ }
+ }
+
+
+ ///
+ /// 创建 Modbus TCP 客户端
+ ///
+ /// 服务器地址
+ /// 端口,默认502
+ public static ModbusTcpClient CreateTcpClient(string host, int port = 502)
+ {
+ return new ModbusTcpClient(host, port);
+ }
+
+ ///
+ /// 创建 Modbus TCP 客户端
+ ///
+ /// 服务器地址
+ /// 端口,默认502
+ public static ModbusUdpClient CreateUdpClient(string host, int port = 502)
+ {
+ return new ModbusUdpClient(host, port);
+ }
+
+ ///
+ /// 创建 Modbus RTU 客户端
+ ///
+ /// 串口名,比如 "COM3"
+ /// 波特率,默认9600
+ /// 校验,默认None
+ /// 数据位,默认8
+ /// 停止位,默认1
+ /// 从站地址,默认1
+ public static ModbusRtuClient CreateRtuClient(string portName,
+ int baudRate = 9600,
+ Parity parity = Parity.None,
+ int dataBits = 8,
+ StopBits stopBits = StopBits.One,
+ byte slaveId = 1)
+ {
+ return new ModbusRtuClient(portName, baudRate, parity, dataBits, stopBits, slaveId);
+ }
+ }
+}
diff --git a/Serein.Proto.Modbus/ModbusException.cs b/Serein.Proto.Modbus/ModbusException.cs
new file mode 100644
index 0000000..94e6f78
--- /dev/null
+++ b/Serein.Proto.Modbus/ModbusException.cs
@@ -0,0 +1,29 @@
+namespace Serein.Proto.Modbus
+{
+ public class ModbusException : Exception
+ {
+ public byte FunctionCode { get; }
+ public byte ExceptionCode { get; }
+
+ public ModbusException(byte functionCode, byte exceptionCode)
+ : base($"Modbus异常码=0x{functionCode:X2},0x{exceptionCode:X2}({GetExceptionMessage(exceptionCode)})")
+ {
+ FunctionCode = functionCode;
+ ExceptionCode = exceptionCode;
+ }
+
+ private static string GetExceptionMessage(byte code) => code switch
+ {
+ 0x01 => "非法功能。确认功能码是否被目标设备支持;检查设备固件版本是否过低;修改主站请求为设备支持的功能码", // 功能码错误
+ 0x02 => "非法数据地址。检查主站请求的寄存器地址和长度是否越界;确保设备配置的寄存器数量正确", // 数据地址错误
+ 0x03 => "非法数据值。检查写入的数值是否在设备支持的范围内;核对协议文档中对应寄存器的取值要求", // 数据值错误
+ 0x04 => "从站设备故障。检查设备运行状态和日志;尝试重启设备;排查硬件或内部程序错误", // 从设备故障
+ 0x05 => "确认。主站需通过轮询或延时机制等待处理完成,再次查询结果", // 确认
+ 0x06 => "从站设备忙。增加请求重试延时;避免高频率发送编程指令", // 从设备忙
+ 0x08 => "存储奇偶性差错。尝试重新发送请求;如错误持续出现,检查存储器硬件或文件一致性", // 内存奇偶校验错误
+ 0x0A => "不可用网关路径。检查网关配置和负载;确认目标设备的网络连接可用性", // 网关路径不可用
+ 0x0B => "网关目标设备响应失败。检查目标设备是否在线;检查网关的路由配置与网络连接", // 网关目标设备未响应
+ _ => $"未知错误" // 未知错误
+ };
+ }
+}
diff --git a/Serein.Proto.Modbus/ModbusFunctionCode.cs b/Serein.Proto.Modbus/ModbusFunctionCode.cs
new file mode 100644
index 0000000..babd5c7
--- /dev/null
+++ b/Serein.Proto.Modbus/ModbusFunctionCode.cs
@@ -0,0 +1,41 @@
+namespace Serein.Proto.Modbus
+{
+ ///
+ /// Modbus 功能码枚举
+ ///
+ public enum ModbusFunctionCode
+ {
+ ///
+ /// 读线圈
+ ///
+ ReadCoils = 0x01,
+ ///
+ /// 读离散输入
+ ///
+ ReadDiscreteInputs = 0x02,
+ ///
+ /// 读保持寄存器
+ ///
+ ReadHoldingRegisters = 0x03,
+ ///
+ /// 读输入寄存器
+ ///
+ ReadInputRegisters = 0x04,
+ ///
+ /// 写单个线圈
+ ///
+ WriteSingleCoil = 0x05,
+ ///
+ /// 写单个寄存器
+ ///
+ WriteSingleRegister = 0x06,
+ ///
+ /// 写多个线圈
+ ///
+ WriteMultipleCoils = 0x0F,
+ ///
+ /// 写多个寄存器
+ ///
+ WriteMultipleRegister = 0x10,
+ }
+}
diff --git a/Serein.Proto.Modbus/ModbusRequest.cs b/Serein.Proto.Modbus/ModbusRequest.cs
new file mode 100644
index 0000000..9b79e74
--- /dev/null
+++ b/Serein.Proto.Modbus/ModbusRequest.cs
@@ -0,0 +1,21 @@
+namespace Serein.Proto.Modbus
+{
+ public class ModbusRequest
+ {
+ ///
+ /// 功能码
+ ///
+ public ModbusFunctionCode FunctionCode { get; set; }
+
+ ///
+ /// PDU (Protocol Data Unit) 数据,不包括从站地址和CRC
+ ///
+ public byte[] PDU { get; set; }
+
+ ///
+ /// 异步任务完成源,用于等待响应
+ ///
+ public TaskCompletionSource Completion { get; set; }
+ }
+
+}
diff --git a/Serein.Proto.Modbus/ModbusRtuClient.cs b/Serein.Proto.Modbus/ModbusRtuClient.cs
new file mode 100644
index 0000000..85025de
--- /dev/null
+++ b/Serein.Proto.Modbus/ModbusRtuClient.cs
@@ -0,0 +1,322 @@
+using System.Buffers.Binary;
+using System.IO.Ports;
+
+namespace Serein.Proto.Modbus
+{
+
+
+ public class ModbusRtuClient : IModbusClient
+ {
+ ///
+ /// 消息发送时触发的事件
+ ///
+ public Action OnTx { get; set; }
+
+ ///
+ /// 接收到消息时触发的事件
+ ///
+ public Action OnRx { get; set; }
+
+
+ private readonly SerialPort _serialPort;
+ private readonly SemaphoreSlim _requestLock = new SemaphoreSlim(1, 1);
+ private readonly byte _slaveId;
+
+ private readonly CancellationTokenSource _cts = new();
+
+ public ModbusRtuClient(string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One, byte slaveId = 1)
+ {
+ _slaveId = slaveId;
+ _serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits)
+ {
+ ReadTimeout = 1000,
+ WriteTimeout = 1000
+ };
+ _serialPort.Open();
+
+ }
+
+ #region 功能码封装
+
+ public async Task ReadCoils(ushort startAddress, ushort quantity)
+ {
+ var pdu = BuildReadPdu(startAddress, quantity);
+ var response = await SendAsync(ModbusFunctionCode.ReadCoils, pdu);
+ return ParseDiscreteBits(response, quantity);
+ }
+
+ public async Task ReadDiscreteInputs(ushort startAddress, ushort quantity)
+ {
+ var pdu = BuildReadPdu(startAddress, quantity);
+ var response = await SendAsync(ModbusFunctionCode.ReadDiscreteInputs, pdu);
+ return ParseDiscreteBits(response, quantity);
+ }
+
+ public async Task ReadHoldingRegisters(ushort startAddress, ushort quantity)
+ {
+ var pdu = BuildReadPdu(startAddress, quantity);
+ var response = await SendAsync(ModbusFunctionCode.ReadHoldingRegisters, pdu);
+ return ParseRegisters(response, quantity);
+ }
+
+ public async Task ReadInputRegisters(ushort startAddress, ushort quantity)
+ {
+ var pdu = BuildReadPdu(startAddress, quantity);
+ var response = await SendAsync(ModbusFunctionCode.ReadInputRegisters, pdu);
+ return ParseRegisters(response, quantity);
+ }
+
+ public async Task WriteSingleCoil(ushort address, bool value)
+ {
+ var pdu = new byte[]
+ {
+ (byte)(address >> 8),
+ (byte)(address & 0xFF),
+ value ? (byte)0xFF : (byte)0x00,
+ 0x00
+ };
+ await SendAsync(ModbusFunctionCode.WriteSingleCoil, pdu);
+ }
+
+ public async Task WriteSingleRegister(ushort address, ushort value)
+ {
+ var pdu = new byte[]
+ {
+ (byte)(address >> 8),
+ (byte)(address & 0xFF),
+ (byte)(value >> 8),
+ (byte)(value & 0xFF)
+ };
+ await SendAsync(ModbusFunctionCode.WriteSingleRegister, pdu);
+ }
+
+
+ public async Task WriteMultipleCoils(ushort startAddress, bool[] values)
+ {
+ if (values == null || values.Length == 0)
+ throw new ArgumentException("values 不能为空");
+
+ int byteCount = (values.Length + 7) / 8; // 需要多少字节
+ byte[] coilData = new byte[byteCount];
+
+ for (int i = 0; i < values.Length; i++)
+ {
+ if (values[i])
+ coilData[i / 8] |= (byte)(1 << (i % 8)); // 设置对应位
+ }
+
+ var pdu = new List
+ {
+ (byte)(startAddress >> 8), // 起始地址高字节
+ (byte)(startAddress & 0xFF), // 起始地址低字节
+ (byte)(values.Length >> 8), // 数量高字节
+ (byte)(values.Length & 0xFF), // 数量低字节
+ (byte)coilData.Length // 数据字节数
+ };
+ pdu.AddRange(coilData);
+
+ await SendAsync(ModbusFunctionCode.WriteMultipleCoils, pdu.ToArray());
+ }
+
+ public async Task WriteMultipleRegisters(ushort startAddress, ushort[] values)
+ {
+ if (values == null || values.Length == 0)
+ throw new ArgumentException("values 不能为空");
+
+ var arrlen = 5 + values.Length * 2;
+ var pdu = new byte[arrlen];
+
+ pdu[0] = (byte)(startAddress >> 8); // 起始地址高字节
+ pdu[1] = (byte)(startAddress & 0xFF); // 起始地址低字节
+ pdu[2] = (byte)(values.Length >> 8); // 寄存器数量高字节
+ pdu[3] = (byte)(values.Length & 0xFF); // 寄存器数量低字节
+ pdu[4] = (byte)(values.Length * 2); // 数据字节数
+
+ // 添加寄存器数据(每个寄存器 2 字节:高字节在前)
+ var index = 5;
+ foreach(var val in values)
+ {
+ pdu[index++] = (byte)(val >> 8);
+ pdu[index++] = (byte)(val & 0xFF);
+
+ }
+
+ /* var pdu = new List
+ {
+ (byte)(startAddress >> 8), // 起始地址高字节
+ (byte)(startAddress & 0xFF), // 起始地址低字节
+ (byte)(values.Length >> 8), // 寄存器数量高字节
+ (byte)(values.Length & 0xFF), // 寄存器数量低字节
+ (byte)(values.Length * 2) // 数据字节数
+ };
+
+
+ foreach (var val in values)
+ {
+ pdu.Add((byte)(val >> 8));
+ pdu.Add((byte)(val & 0xFF));
+ }*/
+
+ await SendAsync(ModbusFunctionCode.WriteMultipleRegister, pdu);
+ }
+
+ #endregion
+
+ #region 核心通信
+
+ public async Task SendAsync(ModbusFunctionCode functionCode, byte[] pdu)
+ {
+ await _requestLock.WaitAsync();
+ try
+ {
+ // 构造 RTU 帧
+ byte[] frame = BuildFrame(_slaveId, (byte)functionCode, pdu);
+ OnTx?.Invoke(frame); // 触发发送日志
+ await _serialPort.BaseStream.WriteAsync(frame, 0, frame.Length, _cts.Token);
+ await _serialPort.BaseStream.FlushAsync(_cts.Token);
+
+ // 接收响应
+ var response = await ReceiveResponseAsync();
+ OnRx?.Invoke(response); // 触发接收日志
+ // 检查功能码是否异常响应
+ if ((response[1] & 0x80) != 0)
+ {
+ byte exceptionCode = response[2];
+ throw new ModbusException(response[1], exceptionCode);
+ }
+
+
+ return response;
+ }
+ finally
+ {
+ _requestLock.Release();
+ }
+ }
+
+
+
+ ///
+ /// 接收响应
+ ///
+ private async Task ReceiveResponseAsync()
+ {
+ var buffer = new byte[256];
+ int offset = 0;
+
+ while (true)
+ {
+ int read = await _serialPort.BaseStream.ReadAsync(buffer, offset, buffer.Length - offset, _cts.Token);
+ offset += read;
+
+ // 最小RTU帧:地址(1) + 功能码(1) + 数据(N) + CRC(2)
+ if (offset >= 5)
+ {
+ int frameLength = offset;
+ if (!ValidateCrc(buffer, 0, frameLength))
+ throw new IOException("CRC 校验失败");
+
+ byte[] response = new byte[frameLength - 2];
+ Array.Copy(buffer, 0, response, 0, frameLength - 2);
+ return response;
+ }
+ }
+ }
+
+ private byte[] BuildFrame(byte slaveAddr, byte functionCode, byte[] pdu)
+ {
+ var frame = new byte[2 + pdu.Length + 2]; // 地址 + 功能码 + PDU + CRC
+ frame[0] = slaveAddr;
+ frame[1] = functionCode;
+ Array.Copy(pdu, 0, frame, 2, pdu.Length);
+ ushort crc = Crc16(frame, 0, frame.Length - 2);
+ frame[frame.Length - 2] = (byte)(crc & 0xFF);
+ frame[frame.Length - 1] = (byte)(crc >> 8);
+ return frame;
+ }
+
+ private static bool ValidateCrc(byte[] buffer, int offset, int length)
+ {
+ ushort crcCalc = Crc16(buffer, offset, length - 2);
+ ushort crcRecv = (ushort)(buffer[length - 2] | (buffer[length - 1] << 8));
+ return crcCalc == crcRecv;
+ }
+
+ #endregion
+
+ #region PDU与解析
+
+ private byte[] BuildReadPdu(ushort startAddress, ushort quantity)
+ {
+
+ byte[] buffer = new byte[4];
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(0, 2), startAddress); // 起始地址高低字节
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(2, 2), quantity); // 读取数量高低字节
+ return buffer;
+ }
+
+ private bool[] ParseDiscreteBits(byte[] pdu, ushort count)
+ {
+ int byteCount = pdu[2]; // 第2字节是后续的字节数量
+ int dataIndex = 3; // 数据从第3字节开始(0-based)
+
+ var result = new bool[count];
+
+ for (int i = 0, bytePos = 0, bitPos = 0; i < count; i++, bitPos++)
+ {
+ if (bitPos == 8)
+ {
+ bitPos = 0;
+ bytePos++;
+ }
+ result[i] = ((pdu[dataIndex + bytePos] >> bitPos) & 0x01) != 0;
+ }
+ return result;
+ }
+
+ private ushort[] ParseRegisters(byte[] pdu, ushort count)
+ {
+ var result = new ushort[count];
+ int dataStart = 3; // 数据从第3字节开始
+
+ for (int i = 0; i < count; i++)
+ {
+ int offset = dataStart + i * 2;
+ result[i] = (ushort)((pdu[offset] << 8) | pdu[offset + 1]);
+ }
+
+ return result;
+ }
+
+ #endregion
+
+ #region CRC16
+ private static ushort Crc16(byte[] data, int offset, int length)
+ {
+ const ushort polynomial = 0xA001;
+ ushort crc = 0xFFFF;
+
+ for (int i = offset; i < offset + length; i++)
+ {
+ crc ^= data[i];
+ for (int j = 0; j < 8; j++)
+ {
+ if ((crc & 0x0001) != 0)
+ crc = (ushort)((crc >> 1) ^ polynomial);
+ else
+ crc >>= 1;
+ }
+ }
+ return crc;
+ }
+ #endregion
+
+ public void Dispose()
+ {
+ _cts.Cancel();
+ _serialPort?.Close();
+ }
+
+
+ }
+}
diff --git a/Serein.Proto.Modbus/ModbusRtuRequest.cs b/Serein.Proto.Modbus/ModbusRtuRequest.cs
new file mode 100644
index 0000000..b6f4638
--- /dev/null
+++ b/Serein.Proto.Modbus/ModbusRtuRequest.cs
@@ -0,0 +1,15 @@
+namespace Serein.Proto.Modbus
+{
+ ///
+ /// Modbus RTU 请求实体(串口模式下无效)
+ ///
+ public sealed class ModbusRtuRequest : ModbusRequest
+ {
+ ///
+ /// 从站地址(1~247)
+ ///
+ public byte SlaveAddress { get; set; }
+ }
+
+}
+
diff --git a/Serein.Proto.Modbus/ModbusTcpClient.cs b/Serein.Proto.Modbus/ModbusTcpClient.cs
new file mode 100644
index 0000000..e162ff7
--- /dev/null
+++ b/Serein.Proto.Modbus/ModbusTcpClient.cs
@@ -0,0 +1,428 @@
+using System.Buffers.Binary;
+using System.Collections.Concurrent;
+using System.Net.Sockets;
+using System.Threading.Channels;
+
+
+namespace Serein.Proto.Modbus
+{
+ ///
+ /// Modbus TCP 客户端
+ ///
+ public class ModbusTcpClient : IModbusClient
+ {
+ ///
+ /// 消息发送时触发的事件
+ ///
+ public Action OnTx { get; set; }
+
+ ///
+ /// 接收到消息时触发的事件
+ ///
+ public Action OnRx { get; set; }
+
+ ///
+ /// 消息通道
+ ///
+ private readonly Channel _channel = Channel.CreateUnbounded();
+ ///
+ /// TCP客户端
+ ///
+ private readonly TcpClient _tcpClient;
+ ///
+ /// TCP客户端的网络流,用于发送和接收数据
+ ///
+ private readonly NetworkStream _stream;
+ ///
+ /// 存储未完成请求的字典,键为事务ID,值为任务完成源
+ ///
+ private readonly ConcurrentDictionary> _pendingRequests = new();
+ ///
+ /// 事务ID计数器,用于生成唯一的事务ID
+ ///
+ private int _transactionId = 0;
+
+ public ModbusTcpClient(string host, int port = 502)
+ {
+ _tcpClient = new TcpClient();
+ _tcpClient.Connect(host, port);
+ _stream = _tcpClient.GetStream();
+
+ _ = ProcessQueueAsync(); // 启动后台消费者
+ _ = ReceiveLoopAsync(); // 启动接收响应线程
+ }
+
+ #region 功能码封装
+
+ ///
+ /// 读取线圈状态
+ ///
+ ///
+ ///
+ ///
+ public async Task ReadCoils(ushort startAddress, ushort quantity)
+ {
+ var pdu = BuildReadPdu(startAddress, quantity);
+ var responsePdu = await SendAsync(ModbusFunctionCode.ReadCoils, pdu);
+ return ParseDiscreteBits(responsePdu, quantity);
+ }
+
+ ///
+ /// 读取离散输入状态
+ ///
+ ///
+ ///
+ ///
+ public async Task ReadDiscreteInputs(ushort startAddress, ushort quantity)
+ {
+ var pdu = BuildReadPdu(startAddress, quantity);
+ var responsePdu = await SendAsync(ModbusFunctionCode.ReadDiscreteInputs, pdu);
+ return ParseDiscreteBits(responsePdu, quantity);
+ }
+
+ ///
+ /// 读取保持寄存器
+ ///
+ ///
+ ///
+ ///
+ public async Task ReadHoldingRegisters(ushort startAddress, ushort quantity)
+ {
+ var pdu = BuildReadPdu(startAddress, quantity);
+ var responsePdu = await SendAsync(ModbusFunctionCode.ReadHoldingRegisters, pdu);
+ return ParseRegisters(responsePdu, quantity);
+ }
+
+ ///
+ /// 读取输入寄存器
+ ///
+ ///
+ ///
+ ///
+ public async Task ReadInputRegisters(ushort startAddress, ushort quantity)
+ {
+ var pdu = BuildReadPdu(startAddress, quantity);
+ var responsePdu = await SendAsync(ModbusFunctionCode.ReadInputRegisters, pdu);
+ return ParseRegisters(responsePdu, quantity);
+ }
+
+ ///
+ /// 写单个线圈
+ ///
+ ///
+ ///
+ ///
+ public async Task WriteSingleCoil(ushort address, bool value)
+ {
+ var pdu = new byte[]
+ {
+ (byte)(address >> 8), // 地址高字节
+ (byte)(address & 0xFF), // 地址低字节
+ value ? (byte)0xFF : (byte)0x00, // 线圈值高字节,高电平为0xFF,低电平为00
+ 0x00 // 线圈值低字节
+ };
+ await SendAsync(ModbusFunctionCode.WriteSingleCoil, pdu);
+ }
+
+ ///
+ /// 写单个寄存器
+ ///
+ ///
+ ///
+ ///
+ public async Task WriteSingleRegister(ushort address, ushort value)
+ {
+ var pdu = new byte[]
+ {
+ (byte)(address >> 8), // 地址高字节
+ (byte)(address & 0xFF), // 地址低字节
+ (byte)(value >> 8), // 寄存器值高字节
+ (byte)(value & 0xFF) // 寄存器值低字节
+ };
+ await SendAsync(ModbusFunctionCode.WriteSingleRegister, pdu);
+ }
+
+ ///
+ /// 写多个线圈
+ ///
+ ///
+ ///
+ ///
+ public async Task WriteMultipleCoils(ushort startAddress, bool[] values)
+ {
+ int byteCount = (values.Length + 7) / 8; // 计算需要的字节数
+ byte[] data = new byte[byteCount];
+
+ for (int i = 0; i < values.Length; i++)
+ {
+ if (values[i])
+ data[i / 8] |= (byte)(1 << (i % 8)); // 设置对应位
+ }
+
+ var pdu = new List
+ {
+ (byte)(startAddress >> 8), // 地址高字节
+ (byte)(startAddress & 0xFF), // 地址低字节
+ (byte)(values.Length >> 8), // 数量高字节
+ (byte)(values.Length & 0xFF), // 数量低字节
+ (byte)data.Length // 字节数
+ };
+
+ pdu.AddRange(data);
+
+ await SendAsync(ModbusFunctionCode.WriteMultipleCoils, pdu.ToArray());
+ }
+
+ ///
+ /// 写多个寄存器
+ ///
+ ///
+ ///
+ ///
+ public async Task WriteMultipleRegisters(ushort startAddress, ushort[] values)
+ {
+ var pdu = new List
+ {
+ (byte)(startAddress >> 8), // 地址高字节
+ (byte)(startAddress & 0xFF), // 地址低字节
+ (byte)(values.Length >> 8), // 数量高字节
+ (byte)(values.Length & 0xFF), // 数量低字节
+ (byte)(values.Length * 2) // 字节数
+ };
+
+ foreach (var val in values)
+ {
+ pdu.Add((byte)(val >> 8)); // 寄存器值高字节
+ pdu.Add((byte)(val & 0xFF)); // 寄存器值低字节
+ }
+
+ await SendAsync(ModbusFunctionCode.WriteMultipleRegister, pdu.ToArray());
+ }
+
+ ///
+ /// 构建读取PDU数据
+ ///
+ ///
+ ///
+ ///
+ private byte[] BuildReadPdu(ushort startAddress, ushort quantity)
+ {
+ byte[] buffer = new byte[4];
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(0, 2), startAddress); // 起始地址高低字节
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(2, 2), quantity); // 读取数量高低字节
+ return buffer;
+ }
+
+
+ ///
+ /// 解析离散位数据
+ ///
+ ///
+ ///
+ ///
+ private bool[] ParseDiscreteBits(byte[] pdu, ushort count)
+ {
+ int byteCount = pdu[2]; // 第2字节是后续的字节数量
+ int dataIndex = 3; // 数据从第3字节开始(0-based)
+
+ var result = new bool[count];
+
+ for (int i = 0, bytePos = 0, bitPos = 0; i < count; i++, bitPos++)
+ {
+ if (bitPos == 8)
+ {
+ bitPos = 0;
+ bytePos++;
+ }
+ result[i] = ((pdu[dataIndex + bytePos] >> bitPos) & 0x01) != 0;
+ }
+ return result;
+ }
+
+ ///
+ /// 解析寄存器数据
+ ///
+ ///
+ ///
+ ///
+ private ushort[] ParseRegisters(byte[] pdu, ushort count)
+ {
+ var result = new ushort[count];
+ int dataStart = 3; // 数据从第3字节开始
+
+ for (int i = 0; i < count; i++)
+ {
+ int offset = dataStart + i * 2;
+ result[i] = (ushort)((pdu[offset] << 8) | pdu[offset + 1]);
+ }
+
+ return result;
+ }
+
+
+ #endregion
+
+ ///
+ /// 处理消息队列,发送请求到服务器
+ ///
+ ///
+ private async Task ProcessQueueAsync()
+ {
+ while (_tcpClient.Connected)
+ {
+ var request = await _channel.Reader.ReadAsync();
+ byte[] packet = BuildPacket(request.TransactionId, 0x01, (byte)request.FunctionCode, request.PDU);
+ OnTx?.Invoke(packet); // 触发发送日志
+ await _stream.WriteAsync(packet, 0, packet.Length);
+ await _stream.FlushAsync();
+ }
+
+ }
+
+ ///
+ /// 发送请求并等待响应
+ ///
+ /// 功能码
+ /// 内容
+ /// 超时时间
+ /// 最大重发次数
+ ///
+ ///
+ public Task SendAsync(ModbusFunctionCode functionCode, byte[] pdu)
+ {
+ int id = Interlocked.Increment(ref _transactionId);
+ var transactionId = (ushort)(id % ushort.MaxValue); // 0~65535 循环
+ var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ var request = new ModbusTcpRequest
+ {
+ TransactionId = transactionId,
+ FunctionCode = functionCode,
+ PDU = pdu,
+ Completion = tcs
+ };
+ _pendingRequests[transactionId] = tcs;
+ _channel.Writer.TryWrite(request);
+
+ return tcs.Task;
+ }
+
+ ///
+ /// 接收数据循环
+ ///
+ ///
+ private async Task ReceiveLoopAsync()
+ {
+ var buffer = new byte[1024];
+ while (true)
+ {
+ int len = await _stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
+ //int len = await _stream.ReadAsync(buffer.AsMemory(0, buffer.Length)).ConfigureAwait(false);
+ if (len == 0) return; // 连接关闭
+
+ if (len < 6)
+ {
+ Console.WriteLine("接收到的数据长度不足");
+ return;
+ }
+
+
+
+ ushort protocolId = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(2, 2));
+ if (protocolId != 0x0000)
+ {
+ Console.WriteLine($"协议不匹配: {protocolId:X4}");
+ return;
+ }
+
+ ushort dataLength = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(4, 2));
+ // 检查数据长度是否合法
+ if (dataLength > 253 || len < 6 + dataLength)
+ {
+ Console.WriteLine($"数据长度异常: dataLength={dataLength}, 实际接收={len}");
+ return;
+ }
+
+ ushort transactionId = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(0, 2));
+ if (_pendingRequests.TryRemove(transactionId, out var tcs))
+ {
+ var responsePdu = new ReadOnlySpan(buffer, 6, dataLength).ToArray();
+ if (OnRx is not null)
+ {
+ var packet = new ReadOnlySpan(buffer, 0, 6 + dataLength).ToArray();
+ OnRx?.Invoke(packet); // 触发接收日志
+ }
+
+ // 检查是否异常响应
+ if ((responsePdu[1] & 0x80) != 0)
+ {
+ byte exceptionCode = responsePdu[2];
+ tcs.SetException(new ModbusException(responsePdu[1], exceptionCode));
+ }
+ else
+ {
+ tcs.SetResult(responsePdu);
+ }
+ }
+ else
+ {
+ Console.WriteLine($"未匹配到 TransactionId={transactionId} 的请求");
+ }
+
+ }
+ }
+
+ ///
+ /// 构造 Modbus Tcp 报文
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private byte[] BuildPacket(ushort transactionId, byte unitId, byte functionCode, byte[] pduData)
+ {
+ int pduLength = 1 + pduData.Length; // PDU 长度 = 功能码1字节 + 数据长度
+ int totalLength = 7 + pduLength; // MBAP头长度 = 7字节 + PDU长度
+
+ Span packet = totalLength <= 256 ? stackalloc byte[totalLength] : new byte[totalLength];
+
+ // 写入事务ID(大端序)
+ packet[0] = (byte)(transactionId >> 8);
+ packet[1] = (byte)(transactionId);
+
+ // 协议ID(固定0x0000)
+ packet[2] = 0;
+ packet[3] = 0;
+
+ // 长度(PDU长度 + 1字节UnitID)
+ ushort length = (ushort)(pduLength + 1);
+ packet[4] = (byte)(length >> 8);
+ packet[5] = (byte)(length);
+
+ // UnitID & 功能码
+ packet[6] = unitId;
+ packet[7] = functionCode;
+
+ // 复制PDU数据
+ pduData.AsSpan().CopyTo(packet.Slice(8));
+
+ return packet.ToArray();
+ }
+
+
+
+ public void Dispose()
+ {
+ var tcs = _pendingRequests.Values.ToArray();
+ foreach (var pending in tcs)
+ {
+ pending.TrySetCanceled();
+ }
+ _stream?.Close();
+ _tcpClient?.Close();
+ }
+ }
+
+
+}
diff --git a/Serein.Proto.Modbus/ModbusTcpRequest.cs b/Serein.Proto.Modbus/ModbusTcpRequest.cs
new file mode 100644
index 0000000..3f1600f
--- /dev/null
+++ b/Serein.Proto.Modbus/ModbusTcpRequest.cs
@@ -0,0 +1,12 @@
+
+namespace Serein.Proto.Modbus
+{
+ ///
+ /// Modbus TCP 请求实体
+ ///
+ public class ModbusTcpRequest : ModbusRequest
+ {
+ public ushort TransactionId { get; set; }
+ }
+
+}
\ No newline at end of file
diff --git a/Serein.Proto.Modbus/ModbusUdpClient.cs b/Serein.Proto.Modbus/ModbusUdpClient.cs
new file mode 100644
index 0000000..9b0deb1
--- /dev/null
+++ b/Serein.Proto.Modbus/ModbusUdpClient.cs
@@ -0,0 +1,261 @@
+using System.Buffers.Binary;
+using System.Collections.Concurrent;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading.Channels;
+
+namespace Serein.Proto.Modbus
+{
+ public class ModbusUdpClient : IModbusClient
+ {
+ ///
+ /// 消息发送时触发的事件
+ ///
+ public Action OnTx { get; set; }
+
+ ///
+ /// 接收到消息时触发的事件
+ ///
+ public Action OnRx { get; set; }
+
+ private readonly Channel _channel = Channel.CreateUnbounded();
+ private readonly UdpClient _udpClient;
+ private readonly IPEndPoint _remoteEndPoint;
+ private readonly ConcurrentDictionary> _pendingRequests = new();
+ private int _transactionId = 0;
+
+ public ModbusUdpClient(string host, int port = 502)
+ {
+ _remoteEndPoint = new IPEndPoint(IPAddress.Parse(host), port);
+ _udpClient = new UdpClient();
+ _udpClient.Connect(_remoteEndPoint);
+
+ _ = ProcessQueueAsync();
+ _ = ReceiveLoopAsync();
+ }
+
+ #region 功能码封装
+ public async Task ReadCoils(ushort startAddress, ushort quantity)
+ {
+ var pdu = BuildReadPdu(startAddress, quantity);
+ var responsePdu = await SendAsync(ModbusFunctionCode.ReadCoils, pdu);
+ return ParseDiscreteBits(responsePdu, quantity);
+ }
+
+ public async Task ReadDiscreteInputs(ushort startAddress, ushort quantity)
+ {
+ var pdu = BuildReadPdu(startAddress, quantity);
+ var responsePdu = await SendAsync(ModbusFunctionCode.ReadDiscreteInputs, pdu);
+ return ParseDiscreteBits(responsePdu, quantity);
+ }
+
+ public async Task ReadHoldingRegisters(ushort startAddress, ushort quantity)
+ {
+ var pdu = BuildReadPdu(startAddress, quantity);
+ var responsePdu = await SendAsync(ModbusFunctionCode.ReadHoldingRegisters, pdu);
+ return ParseRegisters(responsePdu, quantity);
+ }
+
+ public async Task ReadInputRegisters(ushort startAddress, ushort quantity)
+ {
+ var pdu = BuildReadPdu(startAddress, quantity);
+ var responsePdu = await SendAsync(ModbusFunctionCode.ReadInputRegisters, pdu);
+ return ParseRegisters(responsePdu, quantity);
+ }
+
+ public async Task WriteSingleCoil(ushort address, bool value)
+ {
+ var pdu = new byte[]
+ {
+ (byte)(address >> 8),
+ (byte)(address & 0xFF),
+ value ? (byte)0xFF : (byte)0x00,
+ 0x00
+ };
+ await SendAsync(ModbusFunctionCode.WriteSingleCoil, pdu);
+ }
+
+ public async Task WriteSingleRegister(ushort address, ushort value)
+ {
+ var pdu = new byte[]
+ {
+ (byte)(address >> 8),
+ (byte)(address & 0xFF),
+ (byte)(value >> 8),
+ (byte)(value & 0xFF)
+ };
+ await SendAsync(ModbusFunctionCode.WriteSingleRegister, pdu);
+ }
+
+ public async Task WriteMultipleCoils(ushort startAddress, bool[] values)
+ {
+ int byteCount = (values.Length + 7) / 8;
+ byte[] data = new byte[byteCount];
+
+ for (int i = 0; i < values.Length; i++)
+ {
+ if (values[i])
+ data[i / 8] |= (byte)(1 << (i % 8));
+ }
+
+ var pdu = new List
+ {
+ (byte)(startAddress >> 8),
+ (byte)(startAddress & 0xFF),
+ (byte)(values.Length >> 8),
+ (byte)(values.Length & 0xFF),
+ (byte)data.Length
+ };
+ pdu.AddRange(data);
+
+ await SendAsync(ModbusFunctionCode.WriteMultipleCoils, pdu.ToArray());
+ }
+
+ public async Task WriteMultipleRegisters(ushort startAddress, ushort[] values)
+ {
+ var pdu = new List
+ {
+ (byte)(startAddress >> 8),
+ (byte)(startAddress & 0xFF),
+ (byte)(values.Length >> 8),
+ (byte)(values.Length & 0xFF),
+ (byte)(values.Length * 2)
+ };
+
+ foreach (var val in values)
+ {
+ pdu.Add((byte)(val >> 8));
+ pdu.Add((byte)(val & 0xFF));
+ }
+
+ await SendAsync(ModbusFunctionCode.WriteMultipleRegister, pdu.ToArray());
+ }
+ #endregion
+
+ #region 核心通信
+
+ public Task SendAsync(ModbusFunctionCode functionCode, byte[] pdu)
+ {
+ int id = Interlocked.Increment(ref _transactionId);
+ var transactionId = (ushort)(id % ushort.MaxValue);
+ var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ var request = new ModbusTcpRequest
+ {
+ TransactionId = transactionId,
+ FunctionCode = functionCode,
+ PDU = pdu,
+ Completion = tcs
+ };
+
+ _pendingRequests[transactionId] = tcs;
+ _channel.Writer.TryWrite(request);
+ return tcs.Task;
+ }
+
+ private async Task ProcessQueueAsync()
+ {
+ while (true)
+ {
+ var request = await _channel.Reader.ReadAsync();
+ byte[] packet = BuildPacket(request.TransactionId, 0x01, (byte)request.FunctionCode, request.PDU);
+ OnTx?.Invoke(packet);
+ await _udpClient.SendAsync(packet, packet.Length);
+ }
+ }
+
+ private async Task ReceiveLoopAsync()
+ {
+ while (true)
+ {
+ UdpReceiveResult result = await _udpClient.ReceiveAsync();
+ var buffer = result.Buffer;
+
+ if (buffer.Length < 6) continue;
+
+ ushort transactionId = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(0, 2));
+ if (_pendingRequests.TryRemove(transactionId, out var tcs))
+ {
+ OnRx?.Invoke(buffer);
+ var responsePdu = new ReadOnlySpan(buffer, 6, buffer.Length - 6).ToArray();
+
+ if ((responsePdu[1] & 0x80) != 0)
+ {
+ byte exceptionCode = responsePdu[2];
+ tcs.SetException(new ModbusException(responsePdu[1], exceptionCode));
+ }
+ else
+ {
+ tcs.SetResult(responsePdu);
+ }
+ }
+ }
+ }
+
+ private byte[] BuildPacket(ushort transactionId, byte unitId, byte functionCode, byte[] pduData)
+ {
+ int pduLength = 1 + pduData.Length;
+ int totalLength = 7 + pduLength;
+
+ Span packet = totalLength <= 256 ? stackalloc byte[totalLength] : new byte[totalLength];
+ packet[0] = (byte)(transactionId >> 8);
+ packet[1] = (byte)(transactionId);
+ packet[2] = 0; packet[3] = 0;
+ ushort length = (ushort)(pduLength + 1);
+ packet[4] = (byte)(length >> 8);
+ packet[5] = (byte)(length);
+ packet[6] = unitId;
+ packet[7] = functionCode;
+ pduData.AsSpan().CopyTo(packet.Slice(8));
+ return packet.ToArray();
+ }
+ #endregion
+
+ private byte[] BuildReadPdu(ushort startAddress, ushort quantity)
+ {
+ byte[] buffer = new byte[4];
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(0, 2), startAddress);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(2, 2), quantity);
+ return buffer;
+ }
+
+ private bool[] ParseDiscreteBits(byte[] pdu, ushort count)
+ {
+ var result = new bool[count];
+ int byteCount = pdu[2];
+ int dataIndex = 3;
+
+ for (int i = 0, bytePos = 0, bitPos = 0; i < count; i++, bitPos++)
+ {
+ if (bitPos == 8)
+ {
+ bitPos = 0;
+ bytePos++;
+ }
+ result[i] = ((pdu[dataIndex + bytePos] >> bitPos) & 0x01) != 0;
+ }
+ return result;
+ }
+
+ private ushort[] ParseRegisters(byte[] pdu, ushort count)
+ {
+ var result = new ushort[count];
+ int dataStart = 3;
+
+ for (int i = 0; i < count; i++)
+ {
+ int offset = dataStart + i * 2;
+ result[i] = (ushort)((pdu[offset] << 8) | pdu[offset + 1]);
+ }
+ return result;
+ }
+
+ public void Dispose()
+ {
+ foreach (var tcs in _pendingRequests.Values)
+ tcs.TrySetCanceled();
+
+ _udpClient?.Dispose();
+ }
+ }
+}
diff --git a/Serein.Proto.Modbus/Serein.Proto.Modbus.csproj b/Serein.Proto.Modbus/Serein.Proto.Modbus.csproj
new file mode 100644
index 0000000..937442f
--- /dev/null
+++ b/Serein.Proto.Modbus/Serein.Proto.Modbus.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0;
+ enable
+ latest
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Serein.Proto.WebSocket/Attributes/AutoSocketHandleAttribute.cs b/Serein.Proto.WebSocket/Attributes/AutoSocketHandleAttribute.cs
new file mode 100644
index 0000000..96aeadf
--- /dev/null
+++ b/Serein.Proto.WebSocket/Attributes/AutoSocketHandleAttribute.cs
@@ -0,0 +1,46 @@
+namespace Serein.Proto.WebSocket.Attributes
+{
+ ///
+ /// 作用:WebSocket中处理Json时,将通过Json中ThemeKey 对应的内容(ThemeValue)自动路由到相应方法进行处理,同时要求Data中必须存在对应入参。
+ /// 如果没有显式设置 ThemeValue,将默认使用方法名称作为ThemeValue。
+ /// 如果没有显式设置 IsReturnValue 标记为 false ,当方法顺利完成(没有抛出异常,且返回对象非null),会自动转为json文本发送回去
+ /// 如果没有显式设置 ArgNotNull 标记为 false ,当外部尝试调用时,若 Json Data 不包含响应的数据,将会被忽略此次调用
+ /// 如果返回类型为Task或Task<TResult>,将会自动等待异步完成并获取结果(无法处理Task<Task<TResult>>的情况)。
+ /// 如果返回了值类型,会自动装箱为引用对象。
+ /// 如果有方法执行过程中发送消息的需求,请在入参中声明以下类型的成员,调用时将传入发送消息的委托。
+ /// Action<string> : 发送文本内容。
+ /// Action<object> : 会自动将对象解析为Json字符串,发送文本内容。
+ /// Action<dynamic> : 会自动将对象解析为Json字符串,发送文本内容。
+ /// Func<string,Task> : 异步发送文本内容。
+ /// Func<object,Task> : 会自动将对象解析为Json字符串,异步发送文本内容。
+ /// Func<dynamic,Task> : 会自动将对象解析为Json字符串,异步发送文本内容。
+ ///
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class AutoSocketHandleAttribute : Attribute
+ {
+ ///
+ /// 描述Json业务字段,如果不设置,将默认使用方法名称。
+ ///
+ public string ThemeValue = string.Empty;
+ ///
+ /// 标记方法执行完成后是否需要将结果发送。
+ /// 注意以下返回值,返回的 json 中将不会新建 DataKey 字段:
+ /// 1.返回类型为 void
+ /// 2.返回类型为 Task
+ /// 2.返回类型为 Unit
+ /// 补充:如果返回类型是Task<TResult>
+ /// 会进行异步等待,当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/Attributes/AutoSocketModuleAttribute.cs b/Serein.Proto.WebSocket/Attributes/AutoSocketModuleAttribute.cs
new file mode 100644
index 0000000..7015a1b
--- /dev/null
+++ b/Serein.Proto.WebSocket/Attributes/AutoSocketModuleAttribute.cs
@@ -0,0 +1,43 @@
+namespace Serein.Proto.WebSocket.Attributes
+{
+ ///
+ /// 标记该类是处理模板,需要获取WebSocketServer/WebSocketClient了实例后,使用(Server/Client).MsgHandleHelper.AddModule()进行添加。
+ /// 处理模板需要继承 ISocketHandleModule 接口,否则WebSocket接受到数据时,将无法进行调用相应的处理模板。
+ /// 使用方式:
+ /// [AutoSocketModule(ThemeKey = "theme", DataKey = "data")]
+ /// public class PlcSocketService : ISocketHandleModule
+ /// 类中方法示例:void AddUser(string name,int age)
+ /// Json示例:{ "theme":"AddUser", //【ThemeKey】
+ /// "data": { // 【DataKey】
+ /// "name":"张三",
+ /// "age":35, } }
+ /// WebSocket中收到以上该Json时,通过ThemeKey获取到"AddUser",然后找到AddUser()方法
+ /// 然后根据方法入参名称,从data对应的json数据中取出"name""age"对应的数据作为入参进行调用。AddUser("张三",35)
+ ///
+ ///
+ [AttributeUsage(AttributeTargets.Class)]
+ public sealed class AutoSocketModuleAttribute : Attribute
+ {
+ ///
+ /// 业务标识
+ ///
+ public string ThemeKey;
+ ///
+ /// 数据标识
+ ///
+ public string DataKey;
+ ///
+ /// ID标识
+ ///
+ public string MsgIdKey;
+
+ ///
+ /// 指示应答数据回复方法返回值
+ ///
+ public bool IsResponseUseReturn = true;
+ }
+
+
+
+
+}
diff --git a/Serein.Proto.WebSocket/Attributes/UseDataAttribute.cs b/Serein.Proto.WebSocket/Attributes/UseDataAttribute.cs
new file mode 100644
index 0000000..bc7a7e9
--- /dev/null
+++ b/Serein.Proto.WebSocket/Attributes/UseDataAttribute.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using System.Threading;
+using System.Net.WebSockets;
+using Serein.Library;
+
+
+
+namespace Serein.Proto.WebSocket.Attributes
+{
+
+ ///
+ /// 使用 DataKey 整体数据
+ ///
+ [AttributeUsage(AttributeTargets.Parameter)]
+ public sealed class UseDataAttribute : Attribute
+ {
+ }
+
+
+}
diff --git a/Serein.Proto.WebSocket/Attributes/UseMsgIdAttribute.cs b/Serein.Proto.WebSocket/Attributes/UseMsgIdAttribute.cs
new file mode 100644
index 0000000..ecbba7b
--- /dev/null
+++ b/Serein.Proto.WebSocket/Attributes/UseMsgIdAttribute.cs
@@ -0,0 +1,14 @@
+namespace Serein.Proto.WebSocket.Attributes
+{
+ ///
+ /// 使用 MsgIdKey 整体数据
+ ///
+ [AttributeUsage(AttributeTargets.Parameter)]
+ public sealed class UseMsgIdAttribute : Attribute
+ {
+ }
+
+
+
+
+}
diff --git a/Serein.Proto.WebSocket/Attributes/UseRequestAttribute.cs b/Serein.Proto.WebSocket/Attributes/UseRequestAttribute.cs
new file mode 100644
index 0000000..cbb2367
--- /dev/null
+++ b/Serein.Proto.WebSocket/Attributes/UseRequestAttribute.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Serein.Proto.WebSocket.Attributes
+{
+ internal sealed class UseRequestAttribute : Attribute
+ {
+ }
+}
diff --git a/Serein.Proto.WebSocket/Handle/Attribute.cs b/Serein.Proto.WebSocket/Handle/Attribute.cs
new file mode 100644
index 0000000..70275d4
--- /dev/null
+++ b/Serein.Proto.WebSocket/Handle/Attribute.cs
@@ -0,0 +1,14 @@
+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/HandleConfiguration.cs
new file mode 100644
index 0000000..fdc279b
--- /dev/null
+++ b/Serein.Proto.WebSocket/Handle/HandleConfiguration.cs
@@ -0,0 +1,73 @@
+using Serein.Library;
+
+
+
+namespace Serein.Proto.WebSocket.Handle
+{
+ ///
+ /// socket模块处理数据配置
+ ///
+
+ public class HandleConfiguration
+ {
+ ///
+ /// Emit委托
+ ///
+ public DelegateDetails DelegateDetails { get; set; }
+
+ ///
+ /// 未捕获的异常跟踪
+ ///
+ public Action> OnExceptionTracking { get; set; }
+
+ ///
+ /// 所使用的实例
+ ///
+ public Func InstanceFactory { get; set; }
+
+ ///
+ /// 是否需要返回
+ ///
+ public bool IsReturnValue { get; set; } = true;
+
+ ///
+ /// 是否要求必须不为null
+ ///
+ public bool ArgNotNull { get; set; } = true;
+
+ ///
+ /// 是否使用Data整体内容作为入参参数
+ ///
+ public bool[] UseData { get; set; }
+
+ ///
+ /// 是否使用Request整体内容作为入参参数
+ ///
+ public bool[] UseRequest { get; set; }
+
+ ///
+ /// 是否使用消息ID作为入参参数
+ ///
+ public bool[] UseMsgId { get; set; }
+
+ ///
+ /// 参数名称
+ ///
+ public string[] ParameterName { get; set; }
+
+ ///
+ /// 参数类型
+ ///
+ public Type[] ParameterType { get; set; }
+
+ ///
+ /// 是否检查变量为空
+ ///
+ public bool[] IsCheckArgNotNull { get; set; }
+
+ }
+
+
+
+
+}
diff --git a/Serein.Proto.WebSocket/Handle/WebSocketHandleConfiguration.cs b/Serein.Proto.WebSocket/Handle/WebSocketHandleConfiguration.cs
new file mode 100644
index 0000000..f3bc066
--- /dev/null
+++ b/Serein.Proto.WebSocket/Handle/WebSocketHandleConfiguration.cs
@@ -0,0 +1,14 @@
+namespace Serein.Proto.WebSocket.Handle
+{
+ internal class WebSocketHandleConfiguration : HandleConfiguration
+ {
+ ///
+ /// 主题
+ ///
+ public string ThemeValue { get; set; } = string.Empty;
+ }
+
+
+
+
+}
diff --git a/Library/Network/WebSocket/Handle/WebSocketHandleModule.cs b/Serein.Proto.WebSocket/Handle/WebSocketHandleModule.cs
similarity index 90%
rename from Library/Network/WebSocket/Handle/WebSocketHandleModule.cs
rename to Serein.Proto.WebSocket/Handle/WebSocketHandleModule.cs
index 431a53f..af4274c 100644
--- a/Library/Network/WebSocket/Handle/WebSocketHandleModule.cs
+++ b/Serein.Proto.WebSocket/Handle/WebSocketHandleModule.cs
@@ -1,12 +1,11 @@
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
+using Serein.Library;
using Serein.Library.Utils;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
-namespace Serein.Library.Network.WebSocketCommunication.Handle
+namespace Serein.Proto.WebSocket.Handle
{
///
@@ -19,7 +18,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
///
public WebSocketHandleModule(WebSocketHandleModuleConfig config)
{
- this.moduleConfig = config;
+ moduleConfig = config;
}
///
@@ -64,10 +63,8 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
foreach (var kv in MyHandleConfigs.ToArray())
{
var config = kv.Value;
- if (config.Instance.HandleGuid.Equals(socketControlBase.HandleGuid))
- {
- MyHandleConfigs.TryRemove(kv.Key, out _);
- }
+ MyHandleConfigs.TryRemove(kv.Key, out _);
+
}
return MyHandleConfigs.Count == 0;
}
@@ -88,7 +85,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
///
public async Task HandleAsync(WebSocketMsgContext context)
{
- var jsonObject = context.JsonObject; // 获取到消息
+ var jsonObject = context.MsgRequest; // 获取到消息
string theme = jsonObject.GetValue(moduleConfig.ThemeJsonKey)?.ToString();
if (!MyHandleConfigs.TryGetValue(theme, out var handldConfig))
{
@@ -106,11 +103,11 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
try
{
- var dataObj = jsonObject.GetValue(moduleConfig.DataJsonKey)?.ToObject();
+ var dataObj = jsonObject.GetValue(moduleConfig.DataJsonKey);
context.MsgData = dataObj; // 添加消息
- if (WebSocketHandleModule.TryGetParameters(handldConfig, context, out var args))
+ if (TryGetParameters(handldConfig, context, out var args))
{
- var result = await WebSocketHandleModule.HandleAsync(handldConfig, args);
+ var result = await HandleAsync(handldConfig, args);
if (handldConfig.IsReturnValue)
{
await context.RepliedAsync(moduleConfig, context, result);
@@ -136,7 +133,8 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
///
public static async Task HandleAsync(HandleConfiguration config, object[] args)
{
- var result = await config.DelegateDetails.InvokeAsync(config.Instance, args);
+ var instance = config.InstanceFactory.Invoke();
+ var result = await config.DelegateDetails.InvokeAsync(instance, args);
return result;
}
@@ -155,6 +153,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
for (int i = 0; i < config.ParameterType.Length; i++)
{
+
var type = config.ParameterType[i]; // 入参变量类型
var argName = config.ParameterName[i]; // 入参参数名称
#region 传递消息ID
@@ -164,6 +163,12 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
}
#endregion
#region DATA JSON数据
+ else if (config.UseRequest[i])
+ {
+ args[i] = context.MsgRequest.ToObject(type);
+ }
+ #endregion
+ #region DATA JSON数据
else if (config.UseData[i])
{
args[i] = context.MsgData.ToObject(type);
@@ -222,7 +227,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
{
args[i] = new Func(async data =>
{
- var jsonText = JsonConvert.SerializeObject(data);
+ var jsonText = JsonHelper.Serialize(data);
await context.SendAsync(jsonText);
});
}
@@ -237,7 +242,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
{
args[i] = new Action(async data =>
{
- var jsonText = JsonConvert.SerializeObject(data);
+ var jsonText = JsonHelper.Serialize(data);
await context.SendAsync(jsonText);
});
}
@@ -245,7 +250,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
{
args[i] = new Action(async data =>
{
- var jsonText = JsonConvert.SerializeObject(data);
+ var jsonText = JsonHelper.Serialize(data);
await context.SendAsync(jsonText);
});
}
diff --git a/Library/Network/WebSocket/Handle/WebSocketHandleModuleConfig.cs b/Serein.Proto.WebSocket/Handle/WebSocketHandleModuleConfig.cs
similarity index 74%
rename from Library/Network/WebSocket/Handle/WebSocketHandleModuleConfig.cs
rename to Serein.Proto.WebSocket/Handle/WebSocketHandleModuleConfig.cs
index 577cf0b..9bc574b 100644
--- a/Library/Network/WebSocket/Handle/WebSocketHandleModuleConfig.cs
+++ b/Serein.Proto.WebSocket/Handle/WebSocketHandleModuleConfig.cs
@@ -1,4 +1,4 @@
-namespace Serein.Library.Network.WebSocketCommunication.Handle
+namespace Serein.Proto.WebSocket.Handle
{
///
/// 远程环境配置
@@ -17,6 +17,10 @@
/// 有关数据的 Json Key
///
public string DataJsonKey { get; set; }
+ ///
+ /// 使用怎么样的数据
+ ///
+ public bool IsResponseUseReturn { get; set; }
}
}
diff --git a/Library/Network/WebSocket/Handle/WebSocketMsgContext.cs b/Serein.Proto.WebSocket/Handle/WebSocketMsgContext.cs
similarity index 60%
rename from Library/Network/WebSocket/Handle/WebSocketMsgContext.cs
rename to Serein.Proto.WebSocket/Handle/WebSocketMsgContext.cs
index f297182..b6dfba2 100644
--- a/Library/Network/WebSocket/Handle/WebSocketMsgContext.cs
+++ b/Serein.Proto.WebSocket/Handle/WebSocketMsgContext.cs
@@ -1,30 +1,31 @@
-using Newtonsoft.Json.Linq;
+using Serein.Library.Api;
+using Serein.Library.Utils;
using System;
using System.Threading.Tasks;
-namespace Serein.Library.Network.WebSocketCommunication.Handle
+namespace Serein.Proto.WebSocket.Handle
{
///
/// 消息处理上下文
///
- public class WebSocketMsgContext /*: IDisposable*/
+ public class WebSocketMsgContext : IDisposable
{
public WebSocketMsgContext(Func sendAsync)
{
- this._sendAsync = sendAsync;
+ _sendAsync = sendAsync;
}
-
public void Dispose()
{
- JsonObject = null;
+ MsgRequest = null;
MsgTheme = null;
MsgId = null;
MsgData = null;
MsgData = null;
_sendAsync = null;
}
+
///
/// 标记是否已经处理,如果是,则提前退出
///
@@ -35,12 +36,13 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
_handle = value;
}
} }
+
public bool _handle = false;
///
- /// 消息本体(JObject)
+ /// 消息本体(IJsonToken)
///
- public JObject JsonObject { get; set; }
+ public IJsonToken MsgRequest { get; set; }
///
/// 此次消息请求的主题
@@ -55,7 +57,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
///
/// 此次消息的数据
///
- public JObject MsgData { get; set; }
+ public IJsonToken MsgData { get; set; }
private Func _sendAsync;
@@ -82,38 +84,29 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
WebSocketMsgContext context,
object data)
{
- JObject jsonData;
-
- if (data is null)
+ if (moduleConfig.IsResponseUseReturn)
{
- jsonData = new JObject()
- {
- [moduleConfig.MsgIdJsonKey] = context.MsgId,
- [moduleConfig.ThemeJsonKey] = context.MsgTheme,
- };
+ var responseContent = JsonHelper.Serialize(data);
+ await SendAsync(responseContent);
}
else
{
- JToken dataToken;
- if (data is System.Collections.IEnumerable || data is Array)
- {
- dataToken = JArray.FromObject(data);
- }
- else
- {
- dataToken = JObject.FromObject(data);
- }
- jsonData = new JObject()
+ IJsonToken jsonData;
+
+ jsonData = JsonHelper.Object(obj =>
{
- [moduleConfig.MsgIdJsonKey] = context.MsgId,
- [moduleConfig.ThemeJsonKey] = context.MsgTheme,
- [moduleConfig.DataJsonKey] = dataToken
- };
+ 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);
}
- var msg = jsonData.ToString();
- //Console.WriteLine($"[{msgId}] => {theme}");
- await SendAsync(msg);
+
}
diff --git a/Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs b/Serein.Proto.WebSocket/Handle/WebSocketMsgHandleHelper.cs
similarity index 92%
rename from Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs
rename to Serein.Proto.WebSocket/Handle/WebSocketMsgHandleHelper.cs
index ce8679f..89be00a 100644
--- a/Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs
+++ b/Serein.Proto.WebSocket/Handle/WebSocketMsgHandleHelper.cs
@@ -1,11 +1,10 @@
-using Serein.Library.Utils;
-using System;
+using Serein.Library;
+using Serein.Proto.WebSocket.Attributes;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
-using System.Linq;
using System.Reflection;
-namespace Serein.Library.Network.WebSocketCommunication.Handle
+namespace Serein.Proto.WebSocket.Handle
{
///
/// 适用于Json数据格式的WebSocket消息处理类
@@ -69,9 +68,10 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
///
///
///
- public void AddModule(ISocketHandleModule socketControlBase, Action> onExceptionTracking)
+ public void AddModule(Func instanceFactory, Action> onExceptionTracking)
+ where T : ISocketHandleModule
{
- var type = socketControlBase.GetType();
+ var type = typeof(T);
var moduleAttribute = type.GetCustomAttribute();
if (moduleAttribute is null)
{
@@ -81,11 +81,13 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
var themeKey = moduleAttribute.ThemeKey;
var dataKey = moduleAttribute.DataKey;
var msgIdKey = moduleAttribute.MsgIdKey;
+ var isResponseUseReturn = moduleAttribute.IsResponseUseReturn;
var moduleConfig = new WebSocketHandleModuleConfig()
{
ThemeJsonKey = themeKey,
DataJsonKey = dataKey,
MsgIdJsonKey = msgIdKey,
+ IsResponseUseReturn = isResponseUseReturn,
};
var handleModule = AddMyHandleModule(moduleConfig);
@@ -130,10 +132,11 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
var parameterInfos = methodInfo.GetParameters();
config.DelegateDetails = new DelegateDetails(methodInfo); // 对应theme的emit构造委托调用工具类
- config.Instance = socketControlBase; // 调用emit委托时的实例
+ config.InstanceFactory = instanceFactory; // 调用emit委托时的实例
config.OnExceptionTracking = onExceptionTracking; // 异常追踪
config.ParameterType = parameterInfos.Select(t => t.ParameterType).ToArray(); // 入参参数类型
config.ParameterName = parameterInfos.Select(t => t.Name).ToArray(); // 入参参数名称
+ config.UseRequest = parameterInfos.Select(p => p.GetCustomAttribute() != null).ToArray(); // 是否使用整体data数据
config.UseData = parameterInfos.Select(p => p.GetCustomAttribute() != null).ToArray(); // 是否使用整体data数据
config.UseMsgId = parameterInfos.Select(p => p.GetCustomAttribute() != null).ToArray(); // 是否使用消息ID
#if NET5_0_OR_GREATER
diff --git a/Serein.Proto.WebSocket/ISocketHandleModule.cs b/Serein.Proto.WebSocket/ISocketHandleModule.cs
new file mode 100644
index 0000000..26a7ba2
--- /dev/null
+++ b/Serein.Proto.WebSocket/ISocketHandleModule.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Serein.Proto.WebSocket
+{
+ public interface ISocketHandleModule
+ {
+ }
+
+
+}
diff --git a/Serein.Proto.WebSocket/Serein.Proto.WebSocket.csproj b/Serein.Proto.WebSocket/Serein.Proto.WebSocket.csproj
new file mode 100644
index 0000000..3e33792
--- /dev/null
+++ b/Serein.Proto.WebSocket/Serein.Proto.WebSocket.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net8.0;net462
+ enable
+ latest
+ enable
+
+
+
+
+
+
+
diff --git a/Library/Network/WebSocket/TestExtension.cs b/Serein.Proto.WebSocket/TestExtension.cs
similarity index 94%
rename from Library/Network/WebSocket/TestExtension.cs
rename to Serein.Proto.WebSocket/TestExtension.cs
index 1434b48..a3bbc91 100644
--- a/Library/Network/WebSocket/TestExtension.cs
+++ b/Serein.Proto.WebSocket/TestExtension.cs
@@ -9,7 +9,7 @@ using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
-namespace Serein.Library.Network.WebSocketCommunication
+namespace Serein.Proto.WebSocket
{
///
/// 消息处理工具
@@ -84,7 +84,7 @@ namespace Serein.Library.Network.WebSocketCommunication
///
///
///
- public static async Task SendAsync(WebSocket webSocket, string message)
+ 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/Library/Network/WebSocket/WebSocketClient.cs b/Serein.Proto.WebSocket/WebSocketClient.cs
similarity index 93%
rename from Library/Network/WebSocket/WebSocketClient.cs
rename to Serein.Proto.WebSocket/WebSocketClient.cs
index f3b98c6..f86bdce 100644
--- a/Library/Network/WebSocket/WebSocketClient.cs
+++ b/Serein.Proto.WebSocket/WebSocketClient.cs
@@ -1,16 +1,13 @@
-using Serein.Library.Network.WebSocketCommunication.Handle;
+using Serein.Library.Utils;
+using Serein.Proto.WebSocket.Handle;
using System;
using System.Diagnostics;
-using System.IO.Compression;
-using System.IO;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using static System.Net.Mime.MediaTypeNames;
-using Newtonsoft.Json.Linq;
-namespace Serein.Library.Network.WebSocketCommunication
+namespace Serein.Proto.WebSocket
{
///
@@ -60,7 +57,7 @@ namespace Serein.Library.Network.WebSocketCommunication
///
public async Task SendAsync(string message)
{
- await SocketExtension.SendAsync(this._client, message); // 回复客户端
+ await SocketExtension.SendAsync(_client, message); // 回复客户端
}
///
@@ -120,7 +117,7 @@ namespace Serein.Library.Network.WebSocketCommunication
}
- public async Task HandleMsgAsync(WebSocket webSocket, MsgHandleUtil msgQueueUtil)
+ public async Task HandleMsgAsync(System.Net.WebSockets.WebSocket webSocket, MsgHandleUtil msgQueueUtil)
{
async Task sendasync(string text)
{
@@ -130,7 +127,7 @@ namespace Serein.Library.Network.WebSocketCommunication
{
var message = await msgQueueUtil.WaitMsgAsync(); // 有消息时通知
var context = new WebSocketMsgContext(sendasync);
- context.JsonObject = JObject.Parse(message);
+ context.MsgRequest = JsonHelper.Parse(message);
MsgHandleHelper.Handle(context); // 处理消息
//using (var context = new WebSocketMsgContext(sendasync))
diff --git a/Library/Network/WebSocket/WebSocketServer.cs b/Serein.Proto.WebSocket/WebSocketServer.cs
similarity index 89%
rename from Library/Network/WebSocket/WebSocketServer.cs
rename to Serein.Proto.WebSocket/WebSocketServer.cs
index dca9ee3..bd68713 100644
--- a/Library/Network/WebSocket/WebSocketServer.cs
+++ b/Serein.Proto.WebSocket/WebSocketServer.cs
@@ -1,5 +1,7 @@
-using Newtonsoft.Json.Linq;
-using Serein.Library.Network.WebSocketCommunication.Handle;
+using Serein.Library;
+using Serein.Library.Api;
+using Serein.Library.Utils;
+using Serein.Proto.WebSocket.Handle;
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
@@ -9,7 +11,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
-namespace Serein.Library.Network.WebSocketCommunication
+namespace Serein.Proto.WebSocket
{
///
/// WebSocket JSON 消息授权管理
@@ -21,9 +23,9 @@ namespace Serein.Library.Network.WebSocketCommunication
///
public WebSocketAuthorizedHelper(string addresPort,string token, Func> inspectionAuthorizedFunc)
{
- this.AddresPort = addresPort;
- this.TokenKey = token;
- this.InspectionAuthorizedFunc = inspectionAuthorizedFunc;
+ AddresPort = addresPort;
+ TokenKey = token;
+ InspectionAuthorizedFunc = inspectionAuthorizedFunc;
}
///
@@ -52,7 +54,7 @@ namespace Serein.Library.Network.WebSocketCommunication
{
await semaphoreSlim.WaitAsync(1);
bool isAuthorized = false;
- JObject json = JObject.Parse(message);
+ IJsonToken json = JsonHelper.Parse(message);
if(json.TryGetValue(TokenKey,out var token))
{
// 交给之前定义的授权方法进行判断
@@ -86,9 +88,9 @@ namespace Serein.Library.Network.WebSocketCommunication
///
public WebSocketServer()
{
- this.AuthorizedClients = new ConcurrentDictionary();
- this.InspectionAuthorizedFunc = (tokenObj) => Task.FromResult(true);
- this.IsCheckToken = false;
+ AuthorizedClients = new ConcurrentDictionary();
+ InspectionAuthorizedFunc = (tokenObj) => Task.FromResult(true);
+ IsCheckToken = false;
}
///
@@ -98,10 +100,10 @@ namespace Serein.Library.Network.WebSocketCommunication
/// 验证token的方法
public WebSocketServer(string tokenKey, Func> inspectionAuthorizedFunc)
{
- this.TokenKey = tokenKey;
- this.AuthorizedClients = new ConcurrentDictionary();
- this.InspectionAuthorizedFunc = inspectionAuthorizedFunc;
- this.IsCheckToken = true;
+ TokenKey = tokenKey;
+ AuthorizedClients = new ConcurrentDictionary();
+ InspectionAuthorizedFunc = inspectionAuthorizedFunc;
+ IsCheckToken = true;
}
///
@@ -171,7 +173,7 @@ namespace Serein.Library.Network.WebSocketCommunication
listener?.Stop();
}
- private async Task HandleWebSocketAsync(WebSocket webSocket, WebSocketAuthorizedHelper authorizedHelper)
+ private async Task HandleWebSocketAsync(System.Net.WebSockets.WebSocket webSocket, WebSocketAuthorizedHelper authorizedHelper)
{
// 需要授权,却没有成功创建授权类,关闭连接
if (IsCheckToken && authorizedHelper is null)
@@ -230,7 +232,7 @@ namespace Serein.Library.Network.WebSocketCommunication
}
- public async Task HandleMsgAsync(WebSocket webSocket,
+ public async Task HandleMsgAsync(System.Net.WebSockets.WebSocket webSocket,
MsgHandleUtil msgQueueUtil,
WebSocketAuthorizedHelper authorizedHelper)
{
@@ -255,7 +257,7 @@ namespace Serein.Library.Network.WebSocketCommunication
}
}
var context = new WebSocketMsgContext(sendasync);
- context.JsonObject = JObject.Parse(message);
+ context.MsgRequest = JsonHelper.Parse(message);
MsgHandleHelper.Handle(context); // 处理消息
//using (var context = new WebSocketMsgContext(sendasync))
diff --git a/Serein.Script/SereinScriptInterpreter.cs b/Serein.Script/SereinScriptInterpreter.cs
index 51b5145..f2bd5b4 100644
--- a/Serein.Script/SereinScriptInterpreter.cs
+++ b/Serein.Script/SereinScriptInterpreter.cs
@@ -1,16 +1,9 @@
-using Newtonsoft.Json.Linq;
-using Serein.Library;
+using Serein.Library;
using Serein.Library.Utils;
using Serein.Script.Node;
using Serein.Script.Node.FlowControl;
-using System;
-using System.ComponentModel.Design;
-using System.Reactive;
using System.Reflection;
-using System.Reflection.Metadata.Ecma335;
using System.Runtime.CompilerServices;
-using System.Threading.Tasks;
-using System.Xml.Linq;
namespace Serein.Script
{
diff --git a/Serein.Script/SereinScriptLexer.cs b/Serein.Script/SereinScriptLexer.cs
index 862ceca..92a6efb 100644
--- a/Serein.Script/SereinScriptLexer.cs
+++ b/Serein.Script/SereinScriptLexer.cs
@@ -1,10 +1,4 @@
-using Newtonsoft.Json.Linq;
-using System.Net.Http.Headers;
-using System.Runtime.CompilerServices;
-using System.Xml.Linq;
-using static System.Net.Mime.MediaTypeNames;
-
-namespace Serein.Script
+namespace Serein.Script
{
///
/// Serein脚本词法分析器的Token类型
diff --git a/Serein.Script/SereinScriptParser.cs b/Serein.Script/SereinScriptParser.cs
index 31ea8b1..308d6b2 100644
--- a/Serein.Script/SereinScriptParser.cs
+++ b/Serein.Script/SereinScriptParser.cs
@@ -1,15 +1,6 @@
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Newtonsoft.Json.Linq;
-using Serein.Library;
-using Serein.Library.Utils;
+using Serein.Library.Utils;
using Serein.Script.Node;
using Serein.Script.Node.FlowControl;
-using System.Collections.Generic;
-using System.Linq;
-using System.Linq.Expressions;
-using System.Reflection;
-using System.Text;
-using System.Xml.Linq;
namespace Serein.Script
{
diff --git a/Serein.Script/SereinScriptToCsharpScript.cs b/Serein.Script/SereinScriptToCsharpScript.cs
index a1051e2..cad4935 100644
--- a/Serein.Script/SereinScriptToCsharpScript.cs
+++ b/Serein.Script/SereinScriptToCsharpScript.cs
@@ -1,17 +1,6 @@
-using Newtonsoft.Json.Linq;
-using Serein.Library;
-using Serein.Script.Node;
+using Serein.Script.Node;
using Serein.Script.Node.FlowControl;
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Diagnostics;
-using System.Linq;
-using System.Net.Security;
-using System.Security.Cryptography;
using System.Text;
-using System.Threading.Tasks;
-using System.Xml.Linq;
namespace Serein.Script
{
diff --git a/SereinFlow.sln b/SereinFlow.sln
index aaa40ec..3687c74 100644
--- a/SereinFlow.sln
+++ b/SereinFlow.sln
@@ -28,6 +28,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Workbench.Avalonia.D
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serein.CollaborationSync", "Serein.CollaborationSync\Serein.CollaborationSync.csproj", "{913AAB34-7383-4F9D-A7DF-A5E6191DDB3D}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serein.Extend.NewtonsoftJson", "Serein.Extend.NewtonsoftJson\Serein.Extend.NewtonsoftJson.csproj", "{1961CF3C-29FC-4850-91DE-1DD571D9514D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serein.Proto.WebSocket", "Serein.Proto.WebSocket\Serein.Proto.WebSocket.csproj", "{CF98EBDA-A2E1-08D1-3FED-B8F3F61293FC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serein.Proto.Modbus", "Serein.Proto.Modbus\Serein.Proto.Modbus.csproj", "{EF9E51C0-CDDB-4B02-A304-87FFC31E61E0}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -74,6 +80,18 @@ Global
{913AAB34-7383-4F9D-A7DF-A5E6191DDB3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{913AAB34-7383-4F9D-A7DF-A5E6191DDB3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{913AAB34-7383-4F9D-A7DF-A5E6191DDB3D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1961CF3C-29FC-4850-91DE-1DD571D9514D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1961CF3C-29FC-4850-91DE-1DD571D9514D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1961CF3C-29FC-4850-91DE-1DD571D9514D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1961CF3C-29FC-4850-91DE-1DD571D9514D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CF98EBDA-A2E1-08D1-3FED-B8F3F61293FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CF98EBDA-A2E1-08D1-3FED-B8F3F61293FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CF98EBDA-A2E1-08D1-3FED-B8F3F61293FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CF98EBDA-A2E1-08D1-3FED-B8F3F61293FC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EF9E51C0-CDDB-4B02-A304-87FFC31E61E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EF9E51C0-CDDB-4B02-A304-87FFC31E61E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EF9E51C0-CDDB-4B02-A304-87FFC31E61E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EF9E51C0-CDDB-4B02-A304-87FFC31E61E0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Workbench/ViewModels/MainMenuBarViewModel.cs b/Workbench/ViewModels/MainMenuBarViewModel.cs
index a6cbc41..cf3a065 100644
--- a/Workbench/ViewModels/MainMenuBarViewModel.cs
+++ b/Workbench/ViewModels/MainMenuBarViewModel.cs
@@ -146,7 +146,7 @@ namespace Serein.Workbench.ViewModels
{
Debug.WriteLine(ex.Message);
}
- flowEnvironment.StartRemoteServerAsync();
+ //flowEnvironment.StartRemoteServerAsync();
}
}