mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-02 15:50:47 +08:00
从Serein.Library分离了WebSocket/Modbus;新增了Json门户类,用于未来的Http、WebSocket、Mqtt、gRPC、QUIC扩展。
This commit is contained in:
@@ -929,7 +929,7 @@ namespace Serein.Library.Api
|
||||
|
||||
|
||||
#region 远程相关
|
||||
/// <summary>
|
||||
/*/// <summary>
|
||||
/// 启动远程服务
|
||||
/// </summary>
|
||||
Task StartRemoteServerAsync(int port = 7525);
|
||||
@@ -957,7 +957,7 @@ namespace Serein.Library.Api
|
||||
/// 退出远程环境
|
||||
/// </summary>
|
||||
void ExitRemoteEnv();
|
||||
|
||||
*/
|
||||
/// <summary>
|
||||
/// (用于远程)通知节点属性变更
|
||||
/// </summary>
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Serein.Library.Api
|
||||
/// <summary>
|
||||
/// 流程节点
|
||||
/// </summary>
|
||||
public interface IFlowNode : INotifyPropertyChanged, IDynamicFlowNode
|
||||
public interface IFlowNode : INotifyPropertyChanged, ISereinFlow
|
||||
{
|
||||
/// <summary>
|
||||
/// 节点持有的运行环境
|
||||
|
||||
285
Library/Api/IJsonProvider.cs
Normal file
285
Library/Api/IJsonProvider.cs
Normal file
@@ -0,0 +1,285 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON数据交互的Token接口,允许使用不同的JSON库进行数据处理。
|
||||
/// </summary>
|
||||
public interface IJsonToken
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取指定名称的属性,如果存在则返回true,并通过out参数返回对应的IJsonToken对象。
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
bool TryGetValue(string name, out IJsonToken token);
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定名称的属性值,如果不存在则返回null。
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
#nullable enable
|
||||
IJsonToken? GetValue(string name);
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前Token的字符串值,如果是null则返回null。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string GetString();
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前Token的整数值,如果是null则抛出异常。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
int GetInt32();
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前Token的布尔值,如果是null则抛出异常。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool GetBoolean();
|
||||
|
||||
/// <summary>
|
||||
/// 判断当前Token是否为null。
|
||||
/// </summary>
|
||||
bool IsNull { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 枚举当前Token作为数组时的所有元素,返回一个IEnumerable<IJsonTokens>。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IEnumerable<IJsonToken> EnumerateArray();
|
||||
|
||||
/// <summary>
|
||||
/// 将当前Token转换为指定类型的对象。
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
T ToObject<T>();
|
||||
|
||||
/// <summary>
|
||||
/// 将当前Token转换为指定类型的对象。
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
object ToObject(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// 将当前Token转换为字符串表示形式。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string ToString();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 使用Json进行数据交互的门户,允许外部使用第三方JSON库进行数据处理。
|
||||
/// </summary>
|
||||
public interface IJsonProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON文本转为指定类型
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="jsonText"></param>
|
||||
/// <returns></returns>
|
||||
T Deserialize<T>(string jsonText);
|
||||
|
||||
/// <summary>
|
||||
/// JSON文本转为指定类型
|
||||
/// </summary>
|
||||
/// <param name="jsonText"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
object Deserialize(string jsonText, Type type);
|
||||
|
||||
/// <summary>
|
||||
/// 从对象转换为JSON文本
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
string Serialize(object obj);
|
||||
|
||||
/// <summary>
|
||||
/// 解析为Token
|
||||
/// </summary>
|
||||
/// <param name="json"></param>
|
||||
/// <returns></returns>
|
||||
IJsonToken Parse(string json);
|
||||
|
||||
/// <summary>
|
||||
/// 创建对象
|
||||
/// </summary>
|
||||
/// <param name="values"></param>
|
||||
/// <returns></returns>
|
||||
IJsonToken CreateObject(IDictionary<string, object> values = null);
|
||||
|
||||
/// <summary>
|
||||
/// 创建数组
|
||||
/// </summary>
|
||||
/// <param name="values"></param>
|
||||
/// <returns></returns>
|
||||
IJsonToken CreateArray(IEnumerable<object> values = null);
|
||||
|
||||
/// <summary>
|
||||
/// 将对象转换为JSON Token,自动转换为 JObject/JArray。
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
IJsonToken FromObject(object obj);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
using System.Text.Json;
|
||||
|
||||
public class SystemTextJsonToken : IJsonToken
|
||||
{
|
||||
private readonly JsonElement _element;
|
||||
|
||||
public SystemTextJsonToken(JsonElement element) => _element = element;
|
||||
|
||||
public bool TryGetProperty(string name, out IJsonToken token)
|
||||
{
|
||||
if (_element.TryGetProperty(name, out var property))
|
||||
{
|
||||
token = new SystemTextJsonToken(property);
|
||||
return true;
|
||||
}
|
||||
token = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetString() => _element.ValueKind == JsonValueKind.Null ? null : _element.GetString();
|
||||
public int GetInt32() => _element.GetInt32();
|
||||
public bool GetBoolean() => _element.GetBoolean();
|
||||
public bool IsNull => _element.ValueKind == JsonValueKind.Null;
|
||||
|
||||
public IEnumerable<IJsonToken> EnumerateArray()
|
||||
{
|
||||
if (_element.ValueKind == JsonValueKind.Array)
|
||||
return _element.EnumerateArray().Select(e => new SystemTextJsonToken(e));
|
||||
return Enumerable.Empty<IJsonToken>();
|
||||
}
|
||||
}
|
||||
|
||||
public class SystemTextJsonProvider : IJsonProvider
|
||||
{
|
||||
public string Serialize(object obj) => JsonSerializer.Serialize(obj);
|
||||
public T Deserialize<T>(string json) => JsonSerializer.Deserialize<T>(json);
|
||||
|
||||
public IJsonToken Parse(string json) => new SystemTextJsonToken(JsonDocument.Parse(json).RootElement);
|
||||
}
|
||||
|
||||
public T ToObject<T>()
|
||||
{
|
||||
// JsonElement -> string -> T
|
||||
return JsonSerializer.Deserialize<T>(_element.GetRawText());
|
||||
}
|
||||
|
||||
public IJsonToken CreateObject(IDictionary<string, object> values = null)
|
||||
{
|
||||
string json = values == null
|
||||
? "{}"
|
||||
: JsonSerializer.Serialize(values);
|
||||
return Parse(json);
|
||||
}
|
||||
|
||||
public IJsonToken CreateArray(IEnumerable<object> values = null)
|
||||
{
|
||||
string json = values == null
|
||||
? "[]"
|
||||
: JsonSerializer.Serialize(values);
|
||||
return Parse(json);
|
||||
}
|
||||
|
||||
public IJsonToken FromObject(object obj)
|
||||
{
|
||||
string json = JsonSerializer.Serialize(obj);
|
||||
return Parse(json);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
public class NewtonsoftJsonToken : IJsonToken
|
||||
{
|
||||
private readonly JToken _token;
|
||||
|
||||
public NewtonsoftJsonToken(JToken token) => _token = token;
|
||||
|
||||
public bool TryGetProperty(string name, out IJsonToken token)
|
||||
{
|
||||
if (_token is JObject obj && obj.TryGetValue(name, out var value))
|
||||
{
|
||||
token = new NewtonsoftJsonToken(value);
|
||||
return true;
|
||||
}
|
||||
token = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetString() => _token.Type == JTokenType.Null ? null : _token.ToString();
|
||||
public int GetInt32() => _token.Value<int>();
|
||||
public bool GetBoolean() => _token.Value<bool>();
|
||||
public bool IsNull => _token.Type == JTokenType.Null;
|
||||
|
||||
public IEnumerable<IJsonToken> EnumerateArray()
|
||||
{
|
||||
if (_token is JArray array)
|
||||
return array.Select(x => new NewtonsoftJsonToken(x));
|
||||
return Enumerable.Empty<IJsonToken>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class NewtonsoftJsonProvider : IJsonProvider
|
||||
{
|
||||
public string Serialize(object obj) => JsonConvert.SerializeObject(obj);
|
||||
public T Deserialize<T>(string json) => JsonConvert.DeserializeObject<T>(json);
|
||||
|
||||
public IJsonToken Parse(string json) => new NewtonsoftJsonToken(JToken.Parse(json));
|
||||
}
|
||||
|
||||
public T ToObject<T>()
|
||||
{
|
||||
return _token.ToObject<T>();
|
||||
}
|
||||
public IJsonToken CreateObject(IDictionary<string, object> values = null)
|
||||
{
|
||||
var obj = new JObject();
|
||||
if (values != null)
|
||||
{
|
||||
foreach (var kvp in values)
|
||||
{
|
||||
obj[kvp.Key] = kvp.Value == null ? JValue.CreateNull() : JToken.FromObject(kvp.Value);
|
||||
}
|
||||
}
|
||||
return new NewtonsoftJsonToken(obj);
|
||||
}
|
||||
|
||||
public IJsonToken CreateArray(IEnumerable<object> values = null)
|
||||
{
|
||||
var array = values != null ? new JArray(values.Select(JToken.FromObject)) : new JArray();
|
||||
return new NewtonsoftJsonToken(array);
|
||||
}
|
||||
|
||||
public IJsonToken FromObject(object obj)
|
||||
{
|
||||
if (obj == null) return new NewtonsoftJsonToken(JValue.CreateNull());
|
||||
if (obj is System.Collections.IEnumerable && !(obj is string))
|
||||
return new NewtonsoftJsonToken(JArray.FromObject(obj));
|
||||
return new NewtonsoftJsonToken(JObject.FromObject(obj));
|
||||
}
|
||||
*/
|
||||
@@ -10,9 +10,9 @@ namespace Serein.Library.Api
|
||||
/// <summary>
|
||||
/// 空接口
|
||||
/// </summary>
|
||||
public interface IDynamicFlowNode
|
||||
public interface ISereinFlow
|
||||
{
|
||||
Task<FlowResult> ExecutingAsync(IFlowContext context, CancellationToken token);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +1,8 @@
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Microsoft.VisualBasic;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -648,10 +642,10 @@ namespace Serein.Library
|
||||
|
||||
public UIContextOperation UIContextOperation => throw new NotImplementedException();
|
||||
|
||||
public Task<(bool, RemoteMsgUtil)> ConnectRemoteEnv(string addres, int port, string token)
|
||||
/* public Task<(bool, RemoteMsgUtil)> ConnectRemoteEnv(string addres, int port, string token)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}*/
|
||||
|
||||
public void ExitRemoteEnv()
|
||||
{
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using Serein.Library.Api;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library
|
||||
|
||||
@@ -222,7 +222,7 @@ namespace Serein.Library
|
||||
|
||||
// 3. 显式常量参数
|
||||
if (IsExplicitData && !DataValue.StartsWith("@", StringComparison.OrdinalIgnoreCase))
|
||||
return DataValue.ToConvert(DataType);
|
||||
return DataValue.ToConvertValueType(DataType);
|
||||
|
||||
// 4. 来自其他节点
|
||||
object inputParameter = null;
|
||||
@@ -319,7 +319,7 @@ namespace Serein.Library
|
||||
// 显式设置的参数
|
||||
if (IsExplicitData && !DataValue.StartsWith("@", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return DataValue.ToConvert(DataType); // 并非表达式,同时是显式设置的参数
|
||||
return DataValue.ToConvertValueType(DataType); // 并非表达式,同时是显式设置的参数
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serein.Library.Api;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library
|
||||
{
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using Serein.Library.Web;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Serein.Library.Web;
|
||||
|
||||
namespace Serein.Library.Network
|
||||
{
|
||||
@@ -15,6 +14,7 @@ namespace Serein.Library.Network
|
||||
/// </summary>
|
||||
public class ApiHandleConfig
|
||||
{
|
||||
private readonly IJsonPortal jsonPortal;
|
||||
private readonly DelegateDetails delegateDetails;
|
||||
|
||||
/// <summary>
|
||||
@@ -39,8 +39,9 @@ namespace Serein.Library.Network
|
||||
/// <summary>
|
||||
/// 添加处理配置
|
||||
/// </summary>
|
||||
/// <param name="jsonPortal"></param>
|
||||
/// <param name="methodInfo"></param>
|
||||
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<string, string> routeData, JObject jsonObject)
|
||||
public object[] GetArgsOfPost(Dictionary<string, string> 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;
|
||||
|
||||
@@ -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门户
|
||||
|
||||
/// <summary>
|
||||
/// 控制器实例对象的类型,每次调用都会重新实例化,[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<string, ApiHandleConfig>();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>标记该类是处理模板,需要获取WebSocketServer/WebSocketClient了实例后,使用(Server/Client).MsgHandleHelper.AddModule()进行添加。</para>
|
||||
/// <para>处理模板需要继承 ISocketHandleModule 接口,否则WebSocket接受到数据时,将无法进行调用相应的处理模板。</para>
|
||||
/// <para>使用方式:</para>
|
||||
/// <para>[AutoSocketModule(ThemeKey = "theme", DataKey = "data")]</para>
|
||||
/// <para>public class PlcSocketService : ISocketHandleModule</para>
|
||||
/// <para>类中方法示例:void AddUser(string name,int age)</para>
|
||||
/// <para>Json示例:{ "theme":"AddUser", //【ThemeKey】 </para>
|
||||
/// <para> "data": { // 【DataKey】 </para>
|
||||
/// <para> "name":"张三", </para>
|
||||
/// <para> "age":35, } } </para>
|
||||
/// <para>WebSocket中收到以上该Json时,通过ThemeKey获取到"AddUser",然后找到AddUser()方法</para>
|
||||
/// <para>然后根据方法入参名称,从data对应的json数据中取出"name""age"对应的数据作为入参进行调用。AddUser("张三",35)</para>
|
||||
/// <para></para>
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public sealed class AutoSocketModuleAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 业务标识
|
||||
/// </summary>
|
||||
public string ThemeKey;
|
||||
/// <summary>
|
||||
/// 数据标识
|
||||
/// </summary>
|
||||
public string DataKey;
|
||||
/// <summary>
|
||||
/// ID标识
|
||||
/// </summary>
|
||||
public string MsgIdKey;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <para>作用:WebSocket中处理Json时,将通过Json中ThemeKey 对应的内容(ThemeValue)自动路由到相应方法进行处理,同时要求Data中必须存在对应入参。</para>
|
||||
/// <para>如果没有显式设置 ThemeValue,将默认使用方法名称作为ThemeValue。</para>
|
||||
/// <para>如果没有显式设置 IsReturnValue 标记为 false ,当方法顺利完成(没有抛出异常,且返回对象非null),会自动转为json文本发送回去</para>
|
||||
/// <para>如果没有显式设置 ArgNotNull 标记为 false ,当外部尝试调用时,若 Json Data 不包含响应的数据,将会被忽略此次调用</para>
|
||||
/// <para>如果返回类型为Task或Task<TResult>,将会自动等待异步完成并获取结果(无法处理Task<Task<TResult>>的情况)。</para>
|
||||
/// <para>如果返回了值类型,会自动装箱为引用对象。</para>
|
||||
/// <para>如果有方法执行过程中发送消息的需求,请在入参中声明以下类型的成员,调用时将传入发送消息的委托。</para>
|
||||
/// <para>Action<string> : 发送文本内容。</para>
|
||||
/// <para>Action<object> : 会自动将对象解析为Json字符串,发送文本内容。</para>
|
||||
/// <para>Action<dynamic> : 会自动将对象解析为Json字符串,发送文本内容。</para>
|
||||
/// <para>Func<string,Task> : 异步发送文本内容。</para>
|
||||
/// <para>Func<object,Task> : 会自动将对象解析为Json字符串,异步发送文本内容。</para>
|
||||
/// <para>Func<dynamic,Task> : 会自动将对象解析为Json字符串,异步发送文本内容。</para>
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public sealed class AutoSocketHandleAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 描述Json业务字段,如果不设置,将默认使用方法名称。
|
||||
/// </summary>
|
||||
public string ThemeValue = string.Empty;
|
||||
/// <summary>
|
||||
/// <para>标记方法执行完成后是否需要将结果发送。</para>
|
||||
/// <para>注意以下返回值,返回的 json 中将不会新建 DataKey 字段:</para>
|
||||
/// <para>1.返回类型为 void </para>
|
||||
/// <para>2.返回类型为 Task </para>
|
||||
/// <para>2.返回类型为 Unit </para>
|
||||
/// <para>补充:如果返回类型是Task<TResult></para>
|
||||
/// <para>会进行异步等待,当Task结束后,自动获取TResult进行发送(请避免Task<Task<TResult>>诸如此类的Task泛型嵌套)</para>
|
||||
/// </summary>
|
||||
public bool IsReturnValue = true;
|
||||
/// <summary>
|
||||
/// <para>表示该方法所有入参不能为空(所需的参数在请求Json的Data不存在)</para>
|
||||
/// <para>若有一个参数无法从data获取,则不会进行调用该方法</para>
|
||||
/// <para>如果设置该属性为 false ,但某些入参不能为空,而不希望在代码中进行检查,请为入参添加[NotNull]/[Needful]特性</para>
|
||||
/// </summary>
|
||||
public bool ArgNotNull = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 DataKey 整体数据
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public sealed class UseDataAttribute : Attribute
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// 使用 MsgIdKey 整体数据
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public sealed class UseMsgIdAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
internal class WebSocketHandleConfiguration : HandleConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// 主题
|
||||
/// </summary>
|
||||
public string ThemeValue { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// socket模块处理数据配置
|
||||
/// </summary>
|
||||
|
||||
public class HandleConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Emit委托
|
||||
/// </summary>
|
||||
public DelegateDetails DelegateDetails { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 未捕获的异常跟踪
|
||||
/// </summary>
|
||||
public Action<Exception, Action<object>> OnExceptionTracking { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 所使用的实例
|
||||
/// </summary>
|
||||
public ISocketHandleModule Instance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否需要返回
|
||||
/// </summary>
|
||||
public bool IsReturnValue { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否要求必须不为null
|
||||
/// </summary>
|
||||
public bool ArgNotNull { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否使Data整体内容作为入参参数
|
||||
/// </summary>
|
||||
public bool[] UseData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用消息ID作为入参参数
|
||||
/// </summary>
|
||||
public bool[] UseMsgId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参数名称
|
||||
/// </summary>
|
||||
public string[] ParameterName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参数类型
|
||||
/// </summary>
|
||||
public Type[] ParameterType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否检查变量为空
|
||||
/// </summary>
|
||||
public bool[] IsCheckArgNotNull { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示参数不能为空(Net462不能使用NutNull的情况)
|
||||
/// </summary>
|
||||
public sealed class NeedfulAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Serein.Library.Network.WebSocketCommunication
|
||||
{
|
||||
public interface ISocketHandleModule
|
||||
{
|
||||
Guid HandleGuid { get; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -24,13 +24,13 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Http\**" />
|
||||
<Compile Remove="Network\Socket\**" />
|
||||
<Compile Remove="Network\**" />
|
||||
<Compile Remove="Utils\SerinExpression\**" />
|
||||
<EmbeddedResource Remove="Http\**" />
|
||||
<EmbeddedResource Remove="Network\Socket\**" />
|
||||
<EmbeddedResource Remove="Network\**" />
|
||||
<EmbeddedResource Remove="Utils\SerinExpression\**" />
|
||||
<None Remove="Http\**" />
|
||||
<None Remove="Network\Socket\**" />
|
||||
<None Remove="Network\**" />
|
||||
<None Remove="Utils\SerinExpression\**" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
<Compile Remove="FlowNode\NodeModelBaseFunc.cs" />
|
||||
<Compile Remove="FlowNode\ScriptFlowApi.cs" />
|
||||
<Compile Remove="Utils\NativeDllHelper.cs" />
|
||||
<Compile Remove="Utils\RemoteMsgUtil.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -47,10 +48,10 @@
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="9.0.0" />
|
||||
<PackageReference Include="System.IO.Ports" Version="9.0.7" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="System.Reactive" Version="6.0.1" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.3.0" />
|
||||
<!--<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />-->
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 对象转JSON文本
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToJsonText(this object obj)
|
||||
{
|
||||
var jsonText = JsonConvert.SerializeObject(obj, Formatting.Indented);
|
||||
return jsonText;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JSON文本转对象
|
||||
/// </summary>
|
||||
/// <typeparam name="T">转换类型</typeparam>
|
||||
/// <param name="json">JSON文本</param>
|
||||
/// <returns></returns>
|
||||
public static T ToJsonObject<T>(this string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(json);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 对象转换为对应类型
|
||||
/// </summary>
|
||||
@@ -162,18 +126,18 @@ namespace Serein.Library.Utils
|
||||
{
|
||||
return default;
|
||||
}
|
||||
return (TResult)data.ToConvert(type);
|
||||
return (TResult)data.ToConvertValueType(type);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 对象转换(好像没啥用)
|
||||
/// 对象转换
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static object ToConvert(this object data, Type type)
|
||||
public static object ToConvertValueType(this object data, Type type)
|
||||
{
|
||||
if (type.IsValueType)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
70
Library/Utils/JsonHelper.cs
Normal file
70
Library/Utils/JsonHelper.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using Serein.Library.Api;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Serein.Library.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Json门户类,需要你提供实现
|
||||
/// </summary>
|
||||
public static class JsonHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Json门户类,需要你提供实现
|
||||
/// </summary>
|
||||
private static IJsonProvider provider;
|
||||
|
||||
/// <summary>
|
||||
/// 使用第三方包进行解析
|
||||
/// </summary>
|
||||
/// <param name="jsonPortal"></param>
|
||||
public static void UseJsonLibrary(IJsonProvider jsonPortal)
|
||||
{
|
||||
JsonHelper.provider = jsonPortal;
|
||||
}
|
||||
|
||||
|
||||
public static T Deserialize<T>(string jsonText)
|
||||
{
|
||||
return provider.Deserialize<T>(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<Dictionary<string, object>> init)
|
||||
{
|
||||
var dict = new Dictionary<string, object>();
|
||||
init(dict);
|
||||
return provider.CreateObject(dict);
|
||||
}
|
||||
|
||||
public static IJsonToken Array(IEnumerable<object> 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<string, object>);
|
||||
return provider.CreateArray(obj as IEnumerable<object>);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -12,6 +12,9 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Serein.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// 全局运行环境
|
||||
/// </summary>
|
||||
public static class SereinEnv
|
||||
{
|
||||
private static IFlowEnvironment environment;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -152,7 +152,7 @@ namespace Serein.NodeFlow.Env
|
||||
currentFlowEnvironment.FlowControl.ActivateFlipflopNode(nodeGuid);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/* /// <inheritdoc/>
|
||||
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);
|
||||
}
|
||||
}*/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<bool> ExitFlowAsync()
|
||||
@@ -172,7 +172,7 @@ namespace Serein.NodeFlow.Env
|
||||
return await currentFlowEnvironment.FlowControl.ExitFlowAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/* /// <inheritdoc/>
|
||||
public void ExitRemoteEnv()
|
||||
{
|
||||
currentFlowEnvironment.ExitRemoteEnv();
|
||||
@@ -183,7 +183,7 @@ namespace Serein.NodeFlow.Env
|
||||
public async Task<FlowEnvInfo> GetEnvInfoAsync()
|
||||
{
|
||||
return await currentFlowEnvironment.GetEnvInfoAsync();
|
||||
}
|
||||
}*/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<SereinProjectData> GetProjectInfoAsync()
|
||||
@@ -288,7 +288,7 @@ namespace Serein.NodeFlow.Env
|
||||
return await currentFlowEnvironment.FlowControl.StartFlowAsync<TResult>(startNodeGuid);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/* /// <inheritdoc/>
|
||||
public async Task StartRemoteServerAsync(int port = 7525)
|
||||
{
|
||||
await currentFlowEnvironment.StartRemoteServerAsync(port);
|
||||
@@ -298,7 +298,7 @@ namespace Serein.NodeFlow.Env
|
||||
public void StopRemoteServer()
|
||||
{
|
||||
currentFlowEnvironment.StopRemoteServer();
|
||||
}
|
||||
}*/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void TerminateFlipflopNode(string nodeGuid)
|
||||
|
||||
@@ -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<SereinProjectData>(content);
|
||||
var FlowProjectData = JsonHelper.Deserialize<SereinProjectData>(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<SereinProjectData>(content);
|
||||
var FlowProjectData = JsonHelper.Deserialize<SereinProjectData>(content);
|
||||
var FileDataPath = System.IO.Path.GetDirectoryName(filePath)!; // filePath;//
|
||||
if(FlowProjectData is null)
|
||||
{
|
||||
@@ -396,9 +381,10 @@ namespace Serein.NodeFlow.Env
|
||||
/// <param name="addres">远程环境地址</param>
|
||||
/// <param name="port">远程环境端口</param>
|
||||
/// <param name="token">密码</param>
|
||||
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);*//*
|
||||
}*/
|
||||
|
||||
/// <summary>
|
||||
/// 退出远程环境
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Serein.NodeFlow.Model
|
||||
}
|
||||
|
||||
|
||||
public abstract partial class NodeModelBase : IDynamicFlowNode
|
||||
public abstract partial class NodeModelBase : ISereinFlow
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否为基础节点
|
||||
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// 节点基类
|
||||
/// </summary>
|
||||
public abstract partial class NodeModelBase : IDynamicFlowNode
|
||||
public abstract partial class NodeModelBase : ISereinFlow
|
||||
{
|
||||
/// <summary>
|
||||
/// 实体节点创建完成后调用的方法,调用时间早于 LoadInfo() 方法
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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}; // 获取当前节点的上一节点数据");
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
90
Serein.Extend.NewtonsoftJson/NewtonsoftJsonProvider.cs
Normal file
90
Serein.Extend.NewtonsoftJson/NewtonsoftJsonProvider.cs
Normal file
@@ -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,
|
||||
}
|
||||
/// <summary>
|
||||
/// 基于Newtonsoft.Json的IJsonProvider实现
|
||||
/// </summary>
|
||||
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<T>(string jsonText)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(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<string, object> values = null)
|
||||
{
|
||||
var jobj = values != null ? JObject.FromObject(values) : new JObject();
|
||||
return new NewtonsoftJsonToken(jobj);
|
||||
}
|
||||
|
||||
public IJsonToken CreateArray(IEnumerable<object> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Serein.Extend.NewtonsoftJson/NewtonsoftJsonToken.cs
Normal file
59
Serein.Extend.NewtonsoftJson/NewtonsoftJsonToken.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serein.Library.Api;
|
||||
|
||||
namespace Serein.Extend.NewtonsoftJson
|
||||
{
|
||||
/// <summary>
|
||||
/// 基于Newtonsoft.Json的IJsonToken实现
|
||||
/// </summary>
|
||||
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<int>();
|
||||
|
||||
public bool GetBoolean() => _token.Value<bool>();
|
||||
|
||||
public bool IsNull => _token.Type == JTokenType.Null || _token.Type == JTokenType.Undefined;
|
||||
|
||||
public IEnumerable<IJsonToken> EnumerateArray()
|
||||
{
|
||||
if (_token is JArray arr)
|
||||
return arr.Select(x => new NewtonsoftJsonToken(x));
|
||||
throw new InvalidOperationException("当前Token不是数组类型。");
|
||||
}
|
||||
|
||||
public T ToObject<T>() => _token.ToObject<T>();
|
||||
|
||||
public object ToObject(Type type) => _token.ToObject(type);
|
||||
|
||||
public override string ToString() => _token.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;net462</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Library\Serein.Library.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
11
Serein.Proto.Modbus/HexExtensions.cs
Normal file
11
Serein.Proto.Modbus/HexExtensions.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
Serein.Proto.Modbus/IModbusClient.cs
Normal file
58
Serein.Proto.Modbus/IModbusClient.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
namespace Serein.Proto.Modbus
|
||||
{
|
||||
/// <summary>
|
||||
/// Modbus 客户端通用接口 (TCP/RTU 通用)
|
||||
/// </summary>
|
||||
public interface IModbusClient : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 报文发送时
|
||||
/// </summary>
|
||||
Action<byte[]> OnTx { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 接收到报文时
|
||||
/// </summary>
|
||||
Action<byte[]> OnRx { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 读取线圈状态 (0x01)
|
||||
/// </summary>
|
||||
Task<bool[]> ReadCoils(ushort startAddress, ushort quantity);
|
||||
|
||||
/// <summary>
|
||||
/// 读取离散输入状态 (0x02)
|
||||
/// </summary>
|
||||
Task<bool[]> ReadDiscreteInputs(ushort startAddress, ushort quantity);
|
||||
|
||||
/// <summary>
|
||||
/// 读取保持寄存器 (0x03)
|
||||
/// </summary>
|
||||
Task<ushort[]> ReadHoldingRegisters(ushort startAddress, ushort quantity);
|
||||
|
||||
/// <summary>
|
||||
/// 读取输入寄存器 (0x04)
|
||||
/// </summary>
|
||||
Task<ushort[]> ReadInputRegisters(ushort startAddress, ushort quantity);
|
||||
|
||||
/// <summary>
|
||||
/// 写单个线圈 (0x05)
|
||||
/// </summary>
|
||||
Task WriteSingleCoil(ushort address, bool value);
|
||||
|
||||
/// <summary>
|
||||
/// 写单个寄存器 (0x06)
|
||||
/// </summary>
|
||||
Task WriteSingleRegister(ushort address, ushort value);
|
||||
|
||||
/// <summary>
|
||||
/// 写多个线圈 (0x0F)
|
||||
/// </summary>
|
||||
Task WriteMultipleCoils(ushort startAddress, bool[] values);
|
||||
|
||||
/// <summary>
|
||||
/// 写多个寄存器 (0x10)
|
||||
/// </summary>
|
||||
Task WriteMultipleRegisters(ushort startAddress, ushort[] values);
|
||||
}
|
||||
}
|
||||
114
Serein.Proto.Modbus/ModbusClientFactory.cs
Normal file
114
Serein.Proto.Modbus/ModbusClientFactory.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System.IO.Ports;
|
||||
|
||||
namespace Serein.Proto.Modbus
|
||||
{
|
||||
public static class ModbusClientFactory
|
||||
{
|
||||
private static readonly char[] separator = new[] { ':' };
|
||||
|
||||
/// <summary>
|
||||
/// 创建 Modbus 客户端实例
|
||||
/// </summary>
|
||||
/// <param name="connectionString">
|
||||
/// 连接字符串格式:
|
||||
/// TCP示例:"tcp:192.168.1.100:502"
|
||||
/// UCP示例:"ucp:192.168.1.100:502"
|
||||
/// RTU示例:"rtu:COM3:9600:1" (格式:rtu:串口名:波特率:从站地址)
|
||||
/// </param>
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 创建 Modbus TCP 客户端
|
||||
/// </summary>
|
||||
/// <param name="host">服务器地址</param>
|
||||
/// <param name="port">端口,默认502</param>
|
||||
public static ModbusTcpClient CreateTcpClient(string host, int port = 502)
|
||||
{
|
||||
return new ModbusTcpClient(host, port);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建 Modbus TCP 客户端
|
||||
/// </summary>
|
||||
/// <param name="host">服务器地址</param>
|
||||
/// <param name="port">端口,默认502</param>
|
||||
public static ModbusUdpClient CreateUdpClient(string host, int port = 502)
|
||||
{
|
||||
return new ModbusUdpClient(host, port);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建 Modbus RTU 客户端
|
||||
/// </summary>
|
||||
/// <param name="portName">串口名,比如 "COM3"</param>
|
||||
/// <param name="baudRate">波特率,默认9600</param>
|
||||
/// <param name="parity">校验,默认None</param>
|
||||
/// <param name="dataBits">数据位,默认8</param>
|
||||
/// <param name="stopBits">停止位,默认1</param>
|
||||
/// <param name="slaveId">从站地址,默认1</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Serein.Proto.Modbus/ModbusException.cs
Normal file
29
Serein.Proto.Modbus/ModbusException.cs
Normal file
@@ -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 => "网关目标设备响应失败。检查目标设备是否在线;检查网关的路由配置与网络连接", // 网关目标设备未响应
|
||||
_ => $"未知错误" // 未知错误
|
||||
};
|
||||
}
|
||||
}
|
||||
41
Serein.Proto.Modbus/ModbusFunctionCode.cs
Normal file
41
Serein.Proto.Modbus/ModbusFunctionCode.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
namespace Serein.Proto.Modbus
|
||||
{
|
||||
/// <summary>
|
||||
/// Modbus 功能码枚举
|
||||
/// </summary>
|
||||
public enum ModbusFunctionCode
|
||||
{
|
||||
/// <summary>
|
||||
/// 读线圈
|
||||
/// </summary>
|
||||
ReadCoils = 0x01,
|
||||
/// <summary>
|
||||
/// 读离散输入
|
||||
/// </summary>
|
||||
ReadDiscreteInputs = 0x02,
|
||||
/// <summary>
|
||||
/// 读保持寄存器
|
||||
/// </summary>
|
||||
ReadHoldingRegisters = 0x03,
|
||||
/// <summary>
|
||||
/// 读输入寄存器
|
||||
/// </summary>
|
||||
ReadInputRegisters = 0x04,
|
||||
/// <summary>
|
||||
/// 写单个线圈
|
||||
/// </summary>
|
||||
WriteSingleCoil = 0x05,
|
||||
/// <summary>
|
||||
/// 写单个寄存器
|
||||
/// </summary>
|
||||
WriteSingleRegister = 0x06,
|
||||
/// <summary>
|
||||
/// 写多个线圈
|
||||
/// </summary>
|
||||
WriteMultipleCoils = 0x0F,
|
||||
/// <summary>
|
||||
/// 写多个寄存器
|
||||
/// </summary>
|
||||
WriteMultipleRegister = 0x10,
|
||||
}
|
||||
}
|
||||
21
Serein.Proto.Modbus/ModbusRequest.cs
Normal file
21
Serein.Proto.Modbus/ModbusRequest.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace Serein.Proto.Modbus
|
||||
{
|
||||
public class ModbusRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 功能码
|
||||
/// </summary>
|
||||
public ModbusFunctionCode FunctionCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// PDU (Protocol Data Unit) 数据,不包括从站地址和CRC
|
||||
/// </summary>
|
||||
public byte[] PDU { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 异步任务完成源,用于等待响应
|
||||
/// </summary>
|
||||
public TaskCompletionSource<byte[]> Completion { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
322
Serein.Proto.Modbus/ModbusRtuClient.cs
Normal file
322
Serein.Proto.Modbus/ModbusRtuClient.cs
Normal file
@@ -0,0 +1,322 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.IO.Ports;
|
||||
|
||||
namespace Serein.Proto.Modbus
|
||||
{
|
||||
|
||||
|
||||
public class ModbusRtuClient : IModbusClient
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息发送时触发的事件
|
||||
/// </summary>
|
||||
public Action<byte[]> OnTx { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 接收到消息时触发的事件
|
||||
/// </summary>
|
||||
public Action<byte[]> 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<bool[]> ReadCoils(ushort startAddress, ushort quantity)
|
||||
{
|
||||
var pdu = BuildReadPdu(startAddress, quantity);
|
||||
var response = await SendAsync(ModbusFunctionCode.ReadCoils, pdu);
|
||||
return ParseDiscreteBits(response, quantity);
|
||||
}
|
||||
|
||||
public async Task<bool[]> ReadDiscreteInputs(ushort startAddress, ushort quantity)
|
||||
{
|
||||
var pdu = BuildReadPdu(startAddress, quantity);
|
||||
var response = await SendAsync(ModbusFunctionCode.ReadDiscreteInputs, pdu);
|
||||
return ParseDiscreteBits(response, quantity);
|
||||
}
|
||||
|
||||
public async Task<ushort[]> ReadHoldingRegisters(ushort startAddress, ushort quantity)
|
||||
{
|
||||
var pdu = BuildReadPdu(startAddress, quantity);
|
||||
var response = await SendAsync(ModbusFunctionCode.ReadHoldingRegisters, pdu);
|
||||
return ParseRegisters(response, quantity);
|
||||
}
|
||||
|
||||
public async Task<ushort[]> 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>
|
||||
{
|
||||
(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>
|
||||
{
|
||||
(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<byte[]> 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 接收响应
|
||||
/// </summary>
|
||||
private async Task<byte[]> 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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
15
Serein.Proto.Modbus/ModbusRtuRequest.cs
Normal file
15
Serein.Proto.Modbus/ModbusRtuRequest.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Serein.Proto.Modbus
|
||||
{
|
||||
/// <summary>
|
||||
/// Modbus RTU 请求实体(串口模式下无效)
|
||||
/// </summary>
|
||||
public sealed class ModbusRtuRequest : ModbusRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 从站地址(1~247)
|
||||
/// </summary>
|
||||
public byte SlaveAddress { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
428
Serein.Proto.Modbus/ModbusTcpClient.cs
Normal file
428
Serein.Proto.Modbus/ModbusTcpClient.cs
Normal file
@@ -0,0 +1,428 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Channels;
|
||||
|
||||
|
||||
namespace Serein.Proto.Modbus
|
||||
{
|
||||
/// <summary>
|
||||
/// Modbus TCP 客户端
|
||||
/// </summary>
|
||||
public class ModbusTcpClient : IModbusClient
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息发送时触发的事件
|
||||
/// </summary>
|
||||
public Action<byte[]> OnTx { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 接收到消息时触发的事件
|
||||
/// </summary>
|
||||
public Action<byte[]> OnRx { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息通道
|
||||
/// </summary>
|
||||
private readonly Channel<ModbusTcpRequest> _channel = Channel.CreateUnbounded<ModbusTcpRequest>();
|
||||
/// <summary>
|
||||
/// TCP客户端
|
||||
/// </summary>
|
||||
private readonly TcpClient _tcpClient;
|
||||
/// <summary>
|
||||
/// TCP客户端的网络流,用于发送和接收数据
|
||||
/// </summary>
|
||||
private readonly NetworkStream _stream;
|
||||
/// <summary>
|
||||
/// 存储未完成请求的字典,键为事务ID,值为任务完成源
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<ushort, TaskCompletionSource<byte[]>> _pendingRequests = new();
|
||||
/// <summary>
|
||||
/// 事务ID计数器,用于生成唯一的事务ID
|
||||
/// </summary>
|
||||
private int _transactionId = 0;
|
||||
|
||||
public ModbusTcpClient(string host, int port = 502)
|
||||
{
|
||||
_tcpClient = new TcpClient();
|
||||
_tcpClient.Connect(host, port);
|
||||
_stream = _tcpClient.GetStream();
|
||||
|
||||
_ = ProcessQueueAsync(); // 启动后台消费者
|
||||
_ = ReceiveLoopAsync(); // 启动接收响应线程
|
||||
}
|
||||
|
||||
#region 功能码封装
|
||||
|
||||
/// <summary>
|
||||
/// 读取线圈状态
|
||||
/// </summary>
|
||||
/// <param name="startAddress"></param>
|
||||
/// <param name="quantity"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool[]> ReadCoils(ushort startAddress, ushort quantity)
|
||||
{
|
||||
var pdu = BuildReadPdu(startAddress, quantity);
|
||||
var responsePdu = await SendAsync(ModbusFunctionCode.ReadCoils, pdu);
|
||||
return ParseDiscreteBits(responsePdu, quantity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取离散输入状态
|
||||
/// </summary>
|
||||
/// <param name="startAddress"></param>
|
||||
/// <param name="quantity"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool[]> ReadDiscreteInputs(ushort startAddress, ushort quantity)
|
||||
{
|
||||
var pdu = BuildReadPdu(startAddress, quantity);
|
||||
var responsePdu = await SendAsync(ModbusFunctionCode.ReadDiscreteInputs, pdu);
|
||||
return ParseDiscreteBits(responsePdu, quantity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取保持寄存器
|
||||
/// </summary>
|
||||
/// <param name="startAddress"></param>
|
||||
/// <param name="quantity"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<ushort[]> ReadHoldingRegisters(ushort startAddress, ushort quantity)
|
||||
{
|
||||
var pdu = BuildReadPdu(startAddress, quantity);
|
||||
var responsePdu = await SendAsync(ModbusFunctionCode.ReadHoldingRegisters, pdu);
|
||||
return ParseRegisters(responsePdu, quantity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取输入寄存器
|
||||
/// </summary>
|
||||
/// <param name="startAddress"></param>
|
||||
/// <param name="quantity"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<ushort[]> ReadInputRegisters(ushort startAddress, ushort quantity)
|
||||
{
|
||||
var pdu = BuildReadPdu(startAddress, quantity);
|
||||
var responsePdu = await SendAsync(ModbusFunctionCode.ReadInputRegisters, pdu);
|
||||
return ParseRegisters(responsePdu, quantity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写单个线圈
|
||||
/// </summary>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写单个寄存器
|
||||
/// </summary>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写多个线圈
|
||||
/// </summary>
|
||||
/// <param name="startAddress"></param>
|
||||
/// <param name="values"></param>
|
||||
/// <returns></returns>
|
||||
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>
|
||||
{
|
||||
(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());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写多个寄存器
|
||||
/// </summary>
|
||||
/// <param name="startAddress"></param>
|
||||
/// <param name="values"></param>
|
||||
/// <returns></returns>
|
||||
public async Task WriteMultipleRegisters(ushort startAddress, ushort[] values)
|
||||
{
|
||||
var pdu = new List<byte>
|
||||
{
|
||||
(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());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建读取PDU数据
|
||||
/// </summary>
|
||||
/// <param name="startAddress"></param>
|
||||
/// <param name="quantity"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 解析离散位数据
|
||||
/// </summary>
|
||||
/// <param name="pdu"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析寄存器数据
|
||||
/// </summary>
|
||||
/// <param name="pdu"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <returns></returns>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// 处理消息队列,发送请求到服务器
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送请求并等待响应
|
||||
/// </summary>
|
||||
/// <param name="functionCode">功能码</param>
|
||||
/// <param name="pdu">内容</param>
|
||||
/// <param name="timeout">超时时间</param>
|
||||
/// <param name="maxRetries">最大重发次数</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="TimeoutException"></exception>
|
||||
public Task<byte[]> SendAsync(ModbusFunctionCode functionCode, byte[] pdu)
|
||||
{
|
||||
int id = Interlocked.Increment(ref _transactionId);
|
||||
var transactionId = (ushort)(id % ushort.MaxValue); // 0~65535 循环
|
||||
var tcs = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
var request = new ModbusTcpRequest
|
||||
{
|
||||
TransactionId = transactionId,
|
||||
FunctionCode = functionCode,
|
||||
PDU = pdu,
|
||||
Completion = tcs
|
||||
};
|
||||
_pendingRequests[transactionId] = tcs;
|
||||
_channel.Writer.TryWrite(request);
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 接收数据循环
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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<byte>(buffer, 6, dataLength).ToArray();
|
||||
if (OnRx is not null)
|
||||
{
|
||||
var packet = new ReadOnlySpan<byte>(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} 的请求");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造 Modbus Tcp 报文
|
||||
/// </summary>
|
||||
/// <param name="transactionId"></param>
|
||||
/// <param name="unitId"></param>
|
||||
/// <param name="functionCode"></param>
|
||||
/// <param name="pduData"></param>
|
||||
/// <returns></returns>
|
||||
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<byte> 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
12
Serein.Proto.Modbus/ModbusTcpRequest.cs
Normal file
12
Serein.Proto.Modbus/ModbusTcpRequest.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
namespace Serein.Proto.Modbus
|
||||
{
|
||||
/// <summary>
|
||||
/// Modbus TCP 请求实体
|
||||
/// </summary>
|
||||
public class ModbusTcpRequest : ModbusRequest
|
||||
{
|
||||
public ushort TransactionId { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
261
Serein.Proto.Modbus/ModbusUdpClient.cs
Normal file
261
Serein.Proto.Modbus/ModbusUdpClient.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息发送时触发的事件
|
||||
/// </summary>
|
||||
public Action<byte[]> OnTx { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 接收到消息时触发的事件
|
||||
/// </summary>
|
||||
public Action<byte[]> OnRx { get; set; }
|
||||
|
||||
private readonly Channel<ModbusTcpRequest> _channel = Channel.CreateUnbounded<ModbusTcpRequest>();
|
||||
private readonly UdpClient _udpClient;
|
||||
private readonly IPEndPoint _remoteEndPoint;
|
||||
private readonly ConcurrentDictionary<ushort, TaskCompletionSource<byte[]>> _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<bool[]> ReadCoils(ushort startAddress, ushort quantity)
|
||||
{
|
||||
var pdu = BuildReadPdu(startAddress, quantity);
|
||||
var responsePdu = await SendAsync(ModbusFunctionCode.ReadCoils, pdu);
|
||||
return ParseDiscreteBits(responsePdu, quantity);
|
||||
}
|
||||
|
||||
public async Task<bool[]> ReadDiscreteInputs(ushort startAddress, ushort quantity)
|
||||
{
|
||||
var pdu = BuildReadPdu(startAddress, quantity);
|
||||
var responsePdu = await SendAsync(ModbusFunctionCode.ReadDiscreteInputs, pdu);
|
||||
return ParseDiscreteBits(responsePdu, quantity);
|
||||
}
|
||||
|
||||
public async Task<ushort[]> ReadHoldingRegisters(ushort startAddress, ushort quantity)
|
||||
{
|
||||
var pdu = BuildReadPdu(startAddress, quantity);
|
||||
var responsePdu = await SendAsync(ModbusFunctionCode.ReadHoldingRegisters, pdu);
|
||||
return ParseRegisters(responsePdu, quantity);
|
||||
}
|
||||
|
||||
public async Task<ushort[]> 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>
|
||||
{
|
||||
(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>
|
||||
{
|
||||
(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<byte[]> SendAsync(ModbusFunctionCode functionCode, byte[] pdu)
|
||||
{
|
||||
int id = Interlocked.Increment(ref _transactionId);
|
||||
var transactionId = (ushort)(id % ushort.MaxValue);
|
||||
var tcs = new TaskCompletionSource<byte[]>(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<byte>(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<byte> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Serein.Proto.Modbus/Serein.Proto.Modbus.csproj
Normal file
18
Serein.Proto.Modbus/Serein.Proto.Modbus.csproj
Normal file
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
<PackageReference Include="System.IO.Ports" Version="9.0.7" />
|
||||
<PackageReference Include="System.Reactive" Version="6.0.1" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.3.0" />
|
||||
<!--<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />-->
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,46 @@
|
||||
namespace Serein.Proto.WebSocket.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>作用:WebSocket中处理Json时,将通过Json中ThemeKey 对应的内容(ThemeValue)自动路由到相应方法进行处理,同时要求Data中必须存在对应入参。</para>
|
||||
/// <para>如果没有显式设置 ThemeValue,将默认使用方法名称作为ThemeValue。</para>
|
||||
/// <para>如果没有显式设置 IsReturnValue 标记为 false ,当方法顺利完成(没有抛出异常,且返回对象非null),会自动转为json文本发送回去</para>
|
||||
/// <para>如果没有显式设置 ArgNotNull 标记为 false ,当外部尝试调用时,若 Json Data 不包含响应的数据,将会被忽略此次调用</para>
|
||||
/// <para>如果返回类型为Task或Task<TResult>,将会自动等待异步完成并获取结果(无法处理Task<Task<TResult>>的情况)。</para>
|
||||
/// <para>如果返回了值类型,会自动装箱为引用对象。</para>
|
||||
/// <para>如果有方法执行过程中发送消息的需求,请在入参中声明以下类型的成员,调用时将传入发送消息的委托。</para>
|
||||
/// <para>Action<string> : 发送文本内容。</para>
|
||||
/// <para>Action<object> : 会自动将对象解析为Json字符串,发送文本内容。</para>
|
||||
/// <para>Action<dynamic> : 会自动将对象解析为Json字符串,发送文本内容。</para>
|
||||
/// <para>Func<string,Task> : 异步发送文本内容。</para>
|
||||
/// <para>Func<object,Task> : 会自动将对象解析为Json字符串,异步发送文本内容。</para>
|
||||
/// <para>Func<dynamic,Task> : 会自动将对象解析为Json字符串,异步发送文本内容。</para>
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public sealed class AutoSocketHandleAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 描述Json业务字段,如果不设置,将默认使用方法名称。
|
||||
/// </summary>
|
||||
public string ThemeValue = string.Empty;
|
||||
/// <summary>
|
||||
/// <para>标记方法执行完成后是否需要将结果发送。</para>
|
||||
/// <para>注意以下返回值,返回的 json 中将不会新建 DataKey 字段:</para>
|
||||
/// <para>1.返回类型为 void </para>
|
||||
/// <para>2.返回类型为 Task </para>
|
||||
/// <para>2.返回类型为 Unit </para>
|
||||
/// <para>补充:如果返回类型是Task<TResult></para>
|
||||
/// <para>会进行异步等待,当Task结束后,自动获取TResult进行发送(请避免Task<Task<TResult>>诸如此类的Task泛型嵌套)</para>
|
||||
/// </summary>
|
||||
public bool IsReturnValue = true;
|
||||
/// <summary>
|
||||
/// <para>表示该方法所有入参不能为空(所需的参数在请求Json的Data不存在)</para>
|
||||
/// <para>若有一个参数无法从data获取,则不会进行调用该方法</para>
|
||||
/// <para>如果设置该属性为 false ,但某些入参不能为空,而不希望在代码中进行检查,请为入参添加[NotNull]/[Needful]特性</para>
|
||||
/// </summary>
|
||||
public bool ArgNotNull = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
namespace Serein.Proto.WebSocket.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>标记该类是处理模板,需要获取WebSocketServer/WebSocketClient了实例后,使用(Server/Client).MsgHandleHelper.AddModule()进行添加。</para>
|
||||
/// <para>处理模板需要继承 ISocketHandleModule 接口,否则WebSocket接受到数据时,将无法进行调用相应的处理模板。</para>
|
||||
/// <para>使用方式:</para>
|
||||
/// <para>[AutoSocketModule(ThemeKey = "theme", DataKey = "data")]</para>
|
||||
/// <para>public class PlcSocketService : ISocketHandleModule</para>
|
||||
/// <para>类中方法示例:void AddUser(string name,int age)</para>
|
||||
/// <para>Json示例:{ "theme":"AddUser", //【ThemeKey】 </para>
|
||||
/// <para> "data": { // 【DataKey】 </para>
|
||||
/// <para> "name":"张三", </para>
|
||||
/// <para> "age":35, } } </para>
|
||||
/// <para>WebSocket中收到以上该Json时,通过ThemeKey获取到"AddUser",然后找到AddUser()方法</para>
|
||||
/// <para>然后根据方法入参名称,从data对应的json数据中取出"name""age"对应的数据作为入参进行调用。AddUser("张三",35)</para>
|
||||
/// <para></para>
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public sealed class AutoSocketModuleAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 业务标识
|
||||
/// </summary>
|
||||
public string ThemeKey;
|
||||
/// <summary>
|
||||
/// 数据标识
|
||||
/// </summary>
|
||||
public string DataKey;
|
||||
/// <summary>
|
||||
/// ID标识
|
||||
/// </summary>
|
||||
public string MsgIdKey;
|
||||
|
||||
/// <summary>
|
||||
/// 指示应答数据回复方法返回值
|
||||
/// </summary>
|
||||
public bool IsResponseUseReturn = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
23
Serein.Proto.WebSocket/Attributes/UseDataAttribute.cs
Normal file
23
Serein.Proto.WebSocket/Attributes/UseDataAttribute.cs
Normal file
@@ -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
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 使用 DataKey 整体数据
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public sealed class UseDataAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
14
Serein.Proto.WebSocket/Attributes/UseMsgIdAttribute.cs
Normal file
14
Serein.Proto.WebSocket/Attributes/UseMsgIdAttribute.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Serein.Proto.WebSocket.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用 MsgIdKey 整体数据
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public sealed class UseMsgIdAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
12
Serein.Proto.WebSocket/Attributes/UseRequestAttribute.cs
Normal file
12
Serein.Proto.WebSocket/Attributes/UseRequestAttribute.cs
Normal file
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
14
Serein.Proto.WebSocket/Handle/Attribute.cs
Normal file
14
Serein.Proto.WebSocket/Handle/Attribute.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Serein.Proto.WebSocket.Handle
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示参数不能为空(Net462不能使用NutNull的情况)
|
||||
/// </summary>
|
||||
public sealed class NeedfulAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
73
Serein.Proto.WebSocket/Handle/HandleConfiguration.cs
Normal file
73
Serein.Proto.WebSocket/Handle/HandleConfiguration.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using Serein.Library;
|
||||
|
||||
|
||||
|
||||
namespace Serein.Proto.WebSocket.Handle
|
||||
{
|
||||
/// <summary>
|
||||
/// socket模块处理数据配置
|
||||
/// </summary>
|
||||
|
||||
public class HandleConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Emit委托
|
||||
/// </summary>
|
||||
public DelegateDetails DelegateDetails { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 未捕获的异常跟踪
|
||||
/// </summary>
|
||||
public Action<Exception, Action<object>> OnExceptionTracking { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 所使用的实例
|
||||
/// </summary>
|
||||
public Func<ISocketHandleModule> InstanceFactory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否需要返回
|
||||
/// </summary>
|
||||
public bool IsReturnValue { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否要求必须不为null
|
||||
/// </summary>
|
||||
public bool ArgNotNull { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用Data整体内容作为入参参数
|
||||
/// </summary>
|
||||
public bool[] UseData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用Request整体内容作为入参参数
|
||||
/// </summary>
|
||||
public bool[] UseRequest { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用消息ID作为入参参数
|
||||
/// </summary>
|
||||
public bool[] UseMsgId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参数名称
|
||||
/// </summary>
|
||||
public string[] ParameterName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参数类型
|
||||
/// </summary>
|
||||
public Type[] ParameterType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否检查变量为空
|
||||
/// </summary>
|
||||
public bool[] IsCheckArgNotNull { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Serein.Proto.WebSocket.Handle
|
||||
{
|
||||
internal class WebSocketHandleConfiguration : HandleConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// 主题
|
||||
/// </summary>
|
||||
public string ThemeValue { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
@@ -19,7 +18,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
|
||||
/// </summary>
|
||||
public WebSocketHandleModule(WebSocketHandleModuleConfig config)
|
||||
{
|
||||
this.moduleConfig = config;
|
||||
moduleConfig = config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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
|
||||
/// </summary>
|
||||
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<JObject>();
|
||||
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
|
||||
/// <returns></returns>
|
||||
public static async Task<object> 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<object, Task>(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<object>(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<string>(async data =>
|
||||
{
|
||||
var jsonText = JsonConvert.SerializeObject(data);
|
||||
var jsonText = JsonHelper.Serialize(data);
|
||||
await context.SendAsync(jsonText);
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Serein.Library.Network.WebSocketCommunication.Handle
|
||||
namespace Serein.Proto.WebSocket.Handle
|
||||
{
|
||||
/// <summary>
|
||||
/// 远程环境配置
|
||||
@@ -17,6 +17,10 @@
|
||||
/// 有关数据的 Json Key
|
||||
/// </summary>
|
||||
public string DataJsonKey { get; set; }
|
||||
/// <summary>
|
||||
/// 使用怎么样的数据
|
||||
/// </summary>
|
||||
public bool IsResponseUseReturn { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 消息处理上下文
|
||||
/// </summary>
|
||||
public class WebSocketMsgContext /*: IDisposable*/
|
||||
public class WebSocketMsgContext : IDisposable
|
||||
{
|
||||
public WebSocketMsgContext(Func<string, Task> sendAsync)
|
||||
{
|
||||
this._sendAsync = sendAsync;
|
||||
_sendAsync = sendAsync;
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
JsonObject = null;
|
||||
MsgRequest = null;
|
||||
MsgTheme = null;
|
||||
MsgId = null;
|
||||
MsgData = null;
|
||||
MsgData = null;
|
||||
_sendAsync = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标记是否已经处理,如果是,则提前退出
|
||||
/// </summary>
|
||||
@@ -35,12 +36,13 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
|
||||
_handle = value;
|
||||
}
|
||||
} }
|
||||
|
||||
public bool _handle = false;
|
||||
|
||||
/// <summary>
|
||||
/// 消息本体(JObject)
|
||||
/// 消息本体(IJsonToken)
|
||||
/// </summary>
|
||||
public JObject JsonObject { get; set; }
|
||||
public IJsonToken MsgRequest { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 此次消息请求的主题
|
||||
@@ -55,7 +57,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
|
||||
/// <summary>
|
||||
/// 此次消息的数据
|
||||
/// </summary>
|
||||
public JObject MsgData { get; set; }
|
||||
public IJsonToken MsgData { get; set; }
|
||||
|
||||
|
||||
private Func<string, Task> _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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 适用于Json数据格式的WebSocket消息处理类
|
||||
@@ -69,9 +68,10 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
|
||||
/// </summary>
|
||||
/// <param name="socketControlBase"></param>
|
||||
/// <param name="onExceptionTracking"></param>
|
||||
public void AddModule(ISocketHandleModule socketControlBase, Action<Exception, Action<object>> onExceptionTracking)
|
||||
public void AddModule<T>(Func<ISocketHandleModule> instanceFactory, Action<Exception, Action<object>> onExceptionTracking)
|
||||
where T : ISocketHandleModule
|
||||
{
|
||||
var type = socketControlBase.GetType();
|
||||
var type = typeof(T);
|
||||
var moduleAttribute = type.GetCustomAttribute<AutoSocketModuleAttribute>();
|
||||
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<UseRequestAttribute>() != null).ToArray(); // 是否使用整体data数据
|
||||
config.UseData = parameterInfos.Select(p => p.GetCustomAttribute<UseDataAttribute>() != null).ToArray(); // 是否使用整体data数据
|
||||
config.UseMsgId = parameterInfos.Select(p => p.GetCustomAttribute<UseMsgIdAttribute>() != null).ToArray(); // 是否使用消息ID
|
||||
#if NET5_0_OR_GREATER
|
||||
10
Serein.Proto.WebSocket/ISocketHandleModule.cs
Normal file
10
Serein.Proto.WebSocket/ISocketHandleModule.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Serein.Proto.WebSocket
|
||||
{
|
||||
public interface ISocketHandleModule
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
14
Serein.Proto.WebSocket/Serein.Proto.WebSocket.csproj
Normal file
14
Serein.Proto.WebSocket/Serein.Proto.WebSocket.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;net462</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Library\Serein.Library.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -9,7 +9,7 @@ using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library.Network.WebSocketCommunication
|
||||
namespace Serein.Proto.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息处理工具
|
||||
@@ -84,7 +84,7 @@ namespace Serein.Library.Network.WebSocketCommunication
|
||||
/// <param name="webSocket"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <returns></returns>
|
||||
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<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
|
||||
@@ -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
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
@@ -60,7 +57,7 @@ namespace Serein.Library.Network.WebSocketCommunication
|
||||
/// <returns></returns>
|
||||
public async Task SendAsync(string message)
|
||||
{
|
||||
await SocketExtension.SendAsync(this._client, message); // 回复客户端
|
||||
await SocketExtension.SendAsync(_client, message); // 回复客户端
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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))
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// WebSocket JSON 消息授权管理
|
||||
@@ -21,9 +23,9 @@ namespace Serein.Library.Network.WebSocketCommunication
|
||||
/// </summary>
|
||||
public WebSocketAuthorizedHelper(string addresPort,string token, Func<dynamic, Task<bool>> inspectionAuthorizedFunc)
|
||||
{
|
||||
this.AddresPort = addresPort;
|
||||
this.TokenKey = token;
|
||||
this.InspectionAuthorizedFunc = inspectionAuthorizedFunc;
|
||||
AddresPort = addresPort;
|
||||
TokenKey = token;
|
||||
InspectionAuthorizedFunc = inspectionAuthorizedFunc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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
|
||||
/// </summary>
|
||||
public WebSocketServer()
|
||||
{
|
||||
this.AuthorizedClients = new ConcurrentDictionary<string, WebSocketAuthorizedHelper>();
|
||||
this.InspectionAuthorizedFunc = (tokenObj) => Task.FromResult(true);
|
||||
this.IsCheckToken = false;
|
||||
AuthorizedClients = new ConcurrentDictionary<string, WebSocketAuthorizedHelper>();
|
||||
InspectionAuthorizedFunc = (tokenObj) => Task.FromResult(true);
|
||||
IsCheckToken = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -98,10 +100,10 @@ namespace Serein.Library.Network.WebSocketCommunication
|
||||
/// <param name="inspectionAuthorizedFunc">验证token的方法</param>
|
||||
public WebSocketServer(string tokenKey, Func<dynamic, Task<bool>> inspectionAuthorizedFunc)
|
||||
{
|
||||
this.TokenKey = tokenKey;
|
||||
this.AuthorizedClients = new ConcurrentDictionary<string, WebSocketAuthorizedHelper>();
|
||||
this.InspectionAuthorizedFunc = inspectionAuthorizedFunc;
|
||||
this.IsCheckToken = true;
|
||||
TokenKey = tokenKey;
|
||||
AuthorizedClients = new ConcurrentDictionary<string, WebSocketAuthorizedHelper>();
|
||||
InspectionAuthorizedFunc = inspectionAuthorizedFunc;
|
||||
IsCheckToken = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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))
|
||||
@@ -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
|
||||
{
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Serein脚本词法分析器的Token类型
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -146,7 +146,7 @@ namespace Serein.Workbench.ViewModels
|
||||
{
|
||||
Debug.WriteLine(ex.Message);
|
||||
}
|
||||
flowEnvironment.StartRemoteServerAsync();
|
||||
//flowEnvironment.StartRemoteServerAsync();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user