From 4e20e816aef39fdd6c98b144acf10f5fdb374f53 Mon Sep 17 00:00:00 2001
From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com>
Date: Wed, 23 Jul 2025 15:57:57 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86http=E6=9C=8D?=
=?UTF-8?q?=E5=8A=A1=E5=99=A8=E6=97=A0=E6=B3=95=E6=AD=A3=E7=A1=AE=E5=A4=84?=
=?UTF-8?q?=E7=90=86post=E8=AF=B7=E6=B1=82=E5=85=A5=E5=8F=82=EF=BC=9B?=
=?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86modbus=20tcp=E5=AE=A2=E6=88=B7?=
=?UTF-8?q?=E7=AB=AF=E6=94=AF=E6=8C=81=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Library/Network/Http/ApiHandleConfig.cs | 2 +-
Library/Network/Http/Attribute.cs | 4 +-
Library/Network/Http/Router.cs | 4 +-
Library/Network/Http/WebApiServer.cs | 14 +-
Library/Network/Modbus/ModbusFunctionCode.cs | 47 +
Library/Network/Modbus/ModbusTcpClient.cs | 403 ++++++
Library/Network/Modbus/ModbusTcpRequest.cs | 32 +
Library/ScriptBaseFunc.cs | 110 ++
Library/Serein.Library.csproj | 2 +-
.../Utils/FlowTrigger/ChannelFlowInterrupt.cs | 23 -
Net462DllTest/Enums/FromValue.cs | 7 +-
Net462DllTest/Web/FlowController.cs | 2 +-
NodeFlow/Model/Node/SingleScriptNode.cs | 134 +-
NodeFlow/Serein.NodeFlow.csproj | 2 +-
NodeFlow/Services/FlowCoreGenerateService.cs | 13 +
.../Serein.Library.NodeGenerator.csproj | 2 +-
Serein.Script/Node/CollectionIndexNode.cs | 2 +-
Serein.Script/Serein.Script.csproj | 4 +
Serein.Script/SereinScript.cs | 52 +-
Serein.Script/SereinScriptExtension.cs | 38 +
Serein.Script/SereinScriptILCompiler.cs | 1215 +++++++++++++++++
Serein.Script/SereinScriptInterpreter.cs | 15 +-
Serein.Script/SereinScriptToCsharpScript.cs | 464 +++++++
Serein.Script/SereinScriptTypeAnalysis.cs | 64 +-
24 files changed, 2466 insertions(+), 189 deletions(-)
create mode 100644 Library/Network/Modbus/ModbusFunctionCode.cs
create mode 100644 Library/Network/Modbus/ModbusTcpClient.cs
create mode 100644 Library/Network/Modbus/ModbusTcpRequest.cs
create mode 100644 Library/ScriptBaseFunc.cs
delete mode 100644 Library/Utils/FlowTrigger/ChannelFlowInterrupt.cs
create mode 100644 Serein.Script/SereinScriptExtension.cs
create mode 100644 Serein.Script/SereinScriptILCompiler.cs
create mode 100644 Serein.Script/SereinScriptToCsharpScript.cs
diff --git a/Library/Network/Http/ApiHandleConfig.cs b/Library/Network/Http/ApiHandleConfig.cs
index c621fe5..099fe8d 100644
--- a/Library/Network/Http/ApiHandleConfig.cs
+++ b/Library/Network/Http/ApiHandleConfig.cs
@@ -130,7 +130,7 @@ namespace Serein.Library.Network
}
else if (jsonObject != null && PostArgTypes[i] == PostArgType.IsBobyData)
{
- args[i] = jsonObject;
+ args[i] = jsonObject.ToObject(type);
}
else if (jsonObject != null)
{
diff --git a/Library/Network/Http/Attribute.cs b/Library/Network/Http/Attribute.cs
index 485b3f3..4c618ec 100644
--- a/Library/Network/Http/Attribute.cs
+++ b/Library/Network/Http/Attribute.cs
@@ -36,10 +36,10 @@ namespace Serein.Library.Web
///
/// 标记该类为 Web Api 处理类
///
- public class AutoHostingAttribute : Attribute
+ public class WebApiControllerAttribute : Attribute
{
public string Url { get; }
- public AutoHostingAttribute(string url = "")
+ public WebApiControllerAttribute(string url = "")
{
this.Url = url;
}
diff --git a/Library/Network/Http/Router.cs b/Library/Network/Http/Router.cs
index 515189e..632e059 100644
--- a/Library/Network/Http/Router.cs
+++ b/Library/Network/Http/Router.cs
@@ -92,7 +92,7 @@ namespace Serein.Library.Web
{
if (!controllerType.IsClass || controllerType.IsAbstract) return; // 如果不是类或者是抽象类,则直接返回
- var autoHostingAttribute = controllerType.GetCustomAttribute();
+ var autoHostingAttribute = controllerType.GetCustomAttribute();
var methods = controllerType.GetMethods().Where(m => m.GetCustomAttribute() != null).ToArray();
@@ -264,7 +264,7 @@ namespace Serein.Library.Web
/// 控制器类型
/// 方法信息
/// 方法对应的urk
- private string AddRoutesUrl(AutoHostingAttribute autoHostingAttribute, WebApiAttribute webAttribute, Type controllerType, MethodInfo method)
+ private string AddRoutesUrl(WebApiControllerAttribute autoHostingAttribute, WebApiAttribute webAttribute, Type controllerType, MethodInfo method)
{
string controllerName;
if (string.IsNullOrWhiteSpace(autoHostingAttribute.Url))
diff --git a/Library/Network/Http/WebApiServer.cs b/Library/Network/Http/WebApiServer.cs
index 24e70ed..6303d49 100644
--- a/Library/Network/Http/WebApiServer.cs
+++ b/Library/Network/Http/WebApiServer.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Net;
using System.Threading.Tasks;
@@ -47,8 +48,15 @@ namespace Serein.Library.Web
{
while (listener.IsListening)
{
- var context = await listener.GetContextAsync(); // 获取请求上下文
- ProcessRequestAsync(context); // 处理请求
+ try
+ {
+ var context = await listener.GetContextAsync(); // 获取请求上下文
+ await ProcessRequestAsync(context); // 处理请求
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine(ex.Message);
+ }
}
});
}
@@ -84,7 +92,7 @@ namespace Serein.Library.Web
///
///
///
- private async void ProcessRequestAsync(HttpListenerContext context)
+ private async Task ProcessRequestAsync(HttpListenerContext context)
{
// 添加CORS头部
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
diff --git a/Library/Network/Modbus/ModbusFunctionCode.cs b/Library/Network/Modbus/ModbusFunctionCode.cs
new file mode 100644
index 0000000..7320644
--- /dev/null
+++ b/Library/Network/Modbus/ModbusFunctionCode.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Serein.Library.Network.Modbus
+{
+ ///
+ /// Modbus 功能码枚举
+ ///
+ public enum ModbusFunctionCode
+ {
+ ///
+ /// 读线圈
+ ///
+ ReadCoils = 0x01,
+ ///
+ /// 读离散输入
+ ///
+ ReadDiscreteInputs = 0x02,
+ ///
+ /// 读保持寄存器
+ ///
+ ReadHoldingRegisters = 0x03,
+ ///
+ /// 读输入寄存器
+ ///
+ ReadInputRegisters = 0x04,
+ ///
+ /// 写单个线圈
+ ///
+ WriteSingleCoil = 0x05,
+ ///
+ /// 写单个寄存器
+ ///
+ WriteSingleRegister = 0x06,
+ ///
+ /// 写多个线圈
+ ///
+ WriteMultipleCoils = 0x0F,
+ ///
+ /// 写多个寄存器
+ ///
+ WriteMultipleRegister = 0x10,
+ }
+}
diff --git a/Library/Network/Modbus/ModbusTcpClient.cs b/Library/Network/Modbus/ModbusTcpClient.cs
new file mode 100644
index 0000000..d0a4d23
--- /dev/null
+++ b/Library/Network/Modbus/ModbusTcpClient.cs
@@ -0,0 +1,403 @@
+using System;
+using System.Buffers.Binary;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading.Channels;
+using System.Threading;
+using System.Threading.Tasks;
+using System.ComponentModel.DataAnnotations;
+
+namespace Serein.Library.Network.Modbus
+{
+ ///
+ /// Modbus TCP 客户端
+ ///
+ public class ModbusTcpClient : IDisposable
+ {
+ ///
+ /// 消息通道
+ ///
+ private readonly Channel _channel = Channel.CreateUnbounded();
+ ///
+ /// TCP客户端
+ ///
+ private readonly TcpClient _tcpClient;
+ ///
+ /// TCP客户端的网络流,用于发送和接收数据
+ ///
+ private readonly NetworkStream _stream;
+ ///
+ /// 存储未完成请求的字典,键为事务ID,值为任务完成源
+ ///
+ private readonly ConcurrentDictionary> _pendingRequests = new();
+ ///
+ /// 事务ID计数器,用于生成唯一的事务ID
+ ///
+ private int _transactionId = 0;
+
+ public ModbusTcpClient(string host, int port = 502)
+ {
+ _tcpClient = new TcpClient();
+ _tcpClient.Connect(host, port);
+ _stream = _tcpClient.GetStream();
+
+ _ = ProcessQueueAsync(); // 启动后台消费者
+ _ = ReceiveLoopAsync(); // 启动接收响应线程
+ }
+
+ #region 功能码封装
+
+ ///
+ /// 读取线圈状态
+ ///
+ ///
+ ///
+ ///
+ public async Task ReadCoils(ushort startAddress, ushort quantity)
+ {
+ var pdu = BuildReadPdu(startAddress, quantity);
+ var responsePdu = await SendAsync(ModbusFunctionCode.ReadCoils, pdu);
+ return ParseDiscreteBits(responsePdu, quantity);
+ }
+
+ ///
+ /// 读取离散输入状态
+ ///
+ ///
+ ///
+ ///
+ public async Task ReadDiscreteInputs(ushort startAddress, ushort quantity)
+ {
+ var pdu = BuildReadPdu(startAddress, quantity);
+ var responsePdu = await SendAsync(ModbusFunctionCode.ReadDiscreteInputs, pdu);
+ return ParseDiscreteBits(responsePdu, quantity);
+ }
+
+ ///
+ /// 读取保持寄存器
+ ///
+ ///
+ ///
+ ///
+ public async Task ReadHoldingRegisters(ushort startAddress, ushort quantity)
+ {
+ var pdu = BuildReadPdu(startAddress, quantity);
+ var responsePdu = await SendAsync(ModbusFunctionCode.ReadHoldingRegisters, pdu);
+ return ParseRegisters(responsePdu, quantity);
+ }
+
+ ///
+ /// 读取输入寄存器
+ ///
+ ///
+ ///
+ ///
+ public async Task ReadInputRegisters(ushort startAddress, ushort quantity)
+ {
+ var pdu = BuildReadPdu(startAddress, quantity);
+ var responsePdu = await SendAsync(ModbusFunctionCode.ReadInputRegisters, pdu);
+ return ParseRegisters(responsePdu, quantity);
+ }
+
+ ///
+ /// 写单个线圈
+ ///
+ ///
+ ///
+ ///
+ public async Task WriteSingleCoil(ushort address, bool value)
+ {
+ var pdu = new byte[]
+ {
+ (byte)(address >> 8), // 地址高字节
+ (byte)(address & 0xFF), // 地址低字节
+ value ? (byte)0xFF : (byte)0x00, // 线圈值高字节,高电平为0xFF,低电平为00
+ 0x00 // 线圈值低字节
+ };
+ await SendAsync(ModbusFunctionCode.WriteSingleCoil, pdu);
+ }
+
+ ///
+ /// 写单个寄存器
+ ///
+ ///
+ ///
+ ///
+ public async Task WriteSingleRegister(ushort address, ushort value)
+ {
+ var pdu = new byte[]
+ {
+ (byte)(address >> 8), // 地址高字节
+ (byte)(address & 0xFF), // 地址低字节
+ (byte)(value >> 8), // 寄存器值高字节
+ (byte)(value & 0xFF) // 寄存器值低字节
+ };
+ await SendAsync(ModbusFunctionCode.WriteSingleRegister, pdu);
+ }
+
+ ///
+ /// 写多个线圈
+ ///
+ ///
+ ///
+ ///
+ public async Task WriteMultipleCoils(ushort startAddress, bool[] values)
+ {
+ int byteCount = (values.Length + 7) / 8; // 计算需要的字节数
+ byte[] data = new byte[byteCount];
+
+ for (int i = 0; i < values.Length; i++)
+ {
+ if (values[i])
+ data[i / 8] |= (byte)(1 << (i % 8)); // 设置对应位
+ }
+
+ var pdu = new List
+ {
+ (byte)(startAddress >> 8), // 地址高字节
+ (byte)(startAddress & 0xFF), // 地址低字节
+ (byte)(values.Length >> 8), // 数量高字节
+ (byte)(values.Length & 0xFF), // 数量低字节
+ (byte)data.Length // 字节数
+ };
+
+ pdu.AddRange(data);
+
+ await SendAsync(ModbusFunctionCode.WriteMultipleCoils, pdu.ToArray());
+ }
+
+ ///
+ /// 写多个寄存器
+ ///
+ ///
+ ///
+ ///
+ public async Task WriteMultipleRegisters(ushort startAddress, ushort[] values)
+ {
+ var pdu = new List
+ {
+ (byte)(startAddress >> 8), // 地址高字节
+ (byte)(startAddress & 0xFF), // 地址低字节
+ (byte)(values.Length >> 8), // 数量高字节
+ (byte)(values.Length & 0xFF), // 数量低字节
+ (byte)(values.Length * 2) // 字节数
+ };
+
+ foreach (var val in values)
+ {
+ pdu.Add((byte)(val >> 8)); // 寄存器值高字节
+ pdu.Add((byte)(val & 0xFF)); // 寄存器值低字节
+ }
+
+ await SendAsync(ModbusFunctionCode.WriteMultipleRegister, pdu.ToArray());
+ }
+
+ ///
+ /// 构建读取PDU数据
+ ///
+ ///
+ ///
+ ///
+ private byte[] BuildReadPdu(ushort startAddress, ushort quantity)
+ {
+ byte[] buffer = new byte[4];
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(0, 2), startAddress); // 起始地址高低字节
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(2, 2), quantity); // 读取数量高低字节
+ return buffer;
+ }
+
+
+ ///
+ /// 解析离散位数据
+ ///
+ ///
+ ///
+ ///
+ private bool[] ParseDiscreteBits(byte[] pdu, ushort count)
+ {
+ int byteCount = pdu[2]; // 第2字节是后续的字节数量
+ int dataIndex = 3; // 数据从第3字节开始(0-based)
+
+ var result = new bool[count];
+
+ for (int i = 0, bytePos = 0, bitPos = 0; i < count; i++, bitPos++)
+ {
+ if (bitPos == 8)
+ {
+ bitPos = 0;
+ bytePos++;
+ }
+ result[i] = ((pdu[dataIndex + bytePos] >> bitPos) & 0x01) != 0;
+ }
+ return result;
+ }
+
+ ///
+ /// 解析寄存器数据
+ ///
+ ///
+ ///
+ ///
+ private ushort[] ParseRegisters(byte[] pdu, ushort count)
+ {
+ var result = new ushort[count];
+ int dataStart = 3; // 数据从第3字节开始
+
+ for (int i = 0; i < count; i++)
+ {
+ int offset = dataStart + i * 2;
+ result[i] = (ushort)((pdu[offset] << 8) | pdu[offset + 1]);
+ }
+
+ return result;
+ }
+
+
+ #endregion
+
+ ///
+ /// 处理消息队列,发送请求到服务器
+ ///
+ ///
+ private async Task ProcessQueueAsync()
+ {
+ var request = await _channel.Reader.ReadAsync();
+ byte[] packet = BuildPacket(request.TransactionId, 0x01, (byte)request.FunctionCode, request.PDU);
+ await _stream.WriteAsync(packet, 0, packet.Length);
+ await _stream.FlushAsync();
+ }
+
+ ///
+ /// 发送请求并等待响应
+ ///
+ /// 功能码
+ /// 内容
+ ///
+ ///
+ public Task SendAsync(ModbusFunctionCode functionCode, byte[] pdu)
+ {
+ int id = Interlocked.Increment(ref _transactionId);
+ var transactionId = (ushort)(id % ushort.MaxValue); // 0~65535 循环
+ var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ var request = new ModbusTcpRequest
+ {
+ TransactionId = transactionId,
+ FunctionCode = functionCode,
+ PDU = pdu,
+ Completion = tcs
+ };
+ _pendingRequests[transactionId] = tcs;
+ _channel.Writer.TryWrite(request);
+
+ return tcs.Task;
+ }
+
+ ///
+ /// 接收数据循环
+ ///
+ ///
+ private async Task ReceiveLoopAsync()
+ {
+ var buffer = new byte[1024];
+ while (true)
+ {
+#if NET462
+ int len = await _stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
+#else
+ int len = await _stream.ReadAsync(buffer.AsMemory(0, buffer.Length)).ConfigureAwait(false);
+#endif
+
+ if (len == 0) return; // 连接关闭
+
+ if (len < 6)
+ {
+ Console.WriteLine("接收到的数据长度不足");
+ return;
+ }
+
+
+ ushort protocolId = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(2, 2));
+ if (protocolId != 0x0000)
+ {
+ Console.WriteLine($"协议不匹配: {protocolId:X4}");
+ return;
+ }
+
+ ushort dataLength = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(4, 2));
+ // 检查数据长度是否合法
+ if (dataLength > 253 || len < 6 + dataLength)
+ {
+ Console.WriteLine($"数据长度异常: dataLength={dataLength}, 实际接收={len}");
+ return;
+ }
+
+ ushort transactionId = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(0, 2));
+ if (_pendingRequests.TryRemove(transactionId, out var tcs))
+ {
+ var responsePdu = new ReadOnlySpan(buffer, 6, dataLength).ToArray();
+ tcs.SetResult(responsePdu); // 如需 byte[] 则 ToArray
+ }
+ else
+ {
+ Console.WriteLine($"未匹配到 TransactionId={transactionId} 的请求");
+ }
+
+ }
+ }
+
+ ///
+ /// 构造 Modbus Tcp 报文
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private byte[] BuildPacket(ushort transactionId, byte unitId, byte functionCode, byte[] pduData)
+ {
+ int pduLength = 1 + pduData.Length; // PDU 长度 = 功能码1字节 + 数据长度
+ int totalLength = 7 + pduLength; // MBAP头长度 = 7字节 + PDU长度
+
+ Span packet = totalLength <= 256 ? stackalloc byte[totalLength] : new byte[totalLength];
+
+ // 写入事务ID(大端序)
+ packet[0] = (byte)(transactionId >> 8);
+ packet[1] = (byte)(transactionId);
+
+ // 协议ID(固定0x0000)
+ packet[2] = 0;
+ packet[3] = 0;
+
+ // 长度(PDU长度 + 1字节UnitID)
+ ushort length = (ushort)(pduLength + 1);
+ packet[4] = (byte)(length >> 8);
+ packet[5] = (byte)(length);
+
+ // UnitID & 功能码
+ packet[6] = unitId;
+ packet[7] = functionCode;
+
+ // 复制PDU数据
+ pduData.AsSpan().CopyTo(packet.Slice(8));
+
+ return packet.ToArray();
+ }
+
+
+
+ public void Dispose()
+ {
+ var tcs = _pendingRequests.Values.ToArray();
+ foreach (var pending in tcs)
+ {
+ pending.TrySetCanceled();
+ }
+ _stream?.Close();
+ _tcpClient?.Close();
+ }
+ }
+}
diff --git a/Library/Network/Modbus/ModbusTcpRequest.cs b/Library/Network/Modbus/ModbusTcpRequest.cs
new file mode 100644
index 0000000..969e0e9
--- /dev/null
+++ b/Library/Network/Modbus/ModbusTcpRequest.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Serein.Library.Network.Modbus
+{
+ ///
+ /// Modbus TCP 请求实体
+ ///
+ public class ModbusTcpRequest
+ {
+ ///
+ /// 事务ID
+ ///
+ public ushort TransactionId { get; set; }
+ ///
+ /// 功能码
+ ///
+ public ModbusFunctionCode FunctionCode { get; set; }
+ ///
+ /// PDU 数据
+ ///
+ public byte[] PDU { get; set; }
+ ///
+ /// 请求的完成源,用于异步等待响应
+ ///
+ public TaskCompletionSource Completion { get; set; }
+ }
+
+}
diff --git a/Library/ScriptBaseFunc.cs b/Library/ScriptBaseFunc.cs
new file mode 100644
index 0000000..120e855
--- /dev/null
+++ b/Library/ScriptBaseFunc.cs
@@ -0,0 +1,110 @@
+using Serein.Library;
+using Serein.Library.Utils;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Serein.Library
+{
+
+
+ public static class ScriptBaseFunc
+ {
+ public static DateTime now() => DateTime.Now;
+
+ #region 常用的类型转换
+ public static bool @bool(object value)
+ {
+ return ConvertHelper.ValueParse(value);
+ }
+ public static byte @byte(object value)
+ {
+ return ConvertHelper.ValueParse(value);
+ }
+ public static decimal @decimal(object value)
+ {
+ return ConvertHelper.ValueParse(value);
+ }
+ public static float @float(object value)
+ {
+ return ConvertHelper.ValueParse(value);
+ }
+ public static double @double(object value)
+ {
+ return ConvertHelper.ValueParse(value);
+ }
+ public static int @int(object value)
+ {
+ return ConvertHelper.ValueParse(value);
+ }
+ public static int @long(object value)
+ {
+ return ConvertHelper.ValueParse(value);
+ }
+
+ #endregion
+
+ public static int len(object target)
+ {
+ // 获取数组或集合对象
+ // 访问数组或集合中的指定索引
+ if (target is Array array)
+ {
+ return array.Length;
+ }
+ else if (target is string chars)
+ {
+ return chars.Length;
+ }
+ else if (target is IDictionary