前言 --- 一个"老设备"的诉说
在工控现场,总有一些老旧设备,它们不支持以太网、也没有 Wi-Fi,但是依然忠实地工作着。它们只能通过串口 (Serial) 与外界通信。你可能已经忘了它们,但当它们出现了问题的时候,也许就只有串口能拯救你。这篇文章,就带你了解 TouchSocket 如何用
SerialPortClient模块,把这些"老伙伴"连接起来,让它们在现代 .NET 应用里复活。
一、SerialPortClient 是什么?
在 TouchSocket 生态里,SerialPortClient 是负责串口 (Serial) 连接的客户端组件。它封装了串口连接、发送、接收和处理逻辑。通过它,你可以很方便地用 .NET(C#)代码与串口设备通讯。 ([TouchSocket][1])
它所在的命名空间是 TouchSocket.SerialPorts,程序集是 TouchSocket.SerialPorts.dll。 ([TouchSocket][1])
二、SerialPortClient 的特点
为什么选它?以下是它的几个核心优势:
- 简单易用:对串口设备的连接、断开、数据收发都非常直观。 ([TouchSocket][2])
- 内存池支持:减少内存分配开销,提高性能。 ([TouchSocket][1])
- 高性能:通过底层优化,让串口通信更可靠。 ([TouchSocket][2])
- 适配器机制:内置"适配器 (Adapter)",可以处理分包/粘包问题,并且可以用来做协议解析(比如 Modbus)。 ([TouchSocket][1])
- 灵活的发送方式:支持同步、异步发送,以及延迟发送 (DelaySender) 来优化小数据包的发送效率。 ([TouchSocket][1])
- 插件驱动 (Plugin):你可以在连接、接收、发送等各个阶段插入自己的逻辑,实现 AOP (面向切面编程) 风格处理。 ([TouchSocket][2])
三、应用场景 --- 老设备 + 自定义协议
SerialPortClient 非常适合以下几类场景:
-
传统串口设备通信
PLC、传感器、仪器、工业控制老设备等,通过 RS-232、RS-485 等串口标准通信。
-
自定义协议解析
你的设备数据格式并不标准(不是 Modbus,也不是某个现成协议),你可以自己实现适配器 (Adapter),让
SerialPortClient帮你处理粘包 / 拆包 / 解析。 -
跨平台使用
由于 TouchSocket 支持 .NET 的多个平台 (.NET Core、.NET 5/6/7 等),串口客户端可以在不同系统上运行。
四、如何配置 SerialPortClient
下面是如何创建和配置一个 SerialPortClient 的基本步骤。这里用一个故事化场景:
小王 是工控工程师,他在工厂里有一台老设备,只能通过串口跟电脑通信。他决定用 TouchSocket 在他的 .NET 项目里写一个客户端去跟设备对话。
4.1 第一步 --- 添加依赖
他首先在项目里通过 NuGet 引入串口模块:
bash
dotnet add package TouchSocket.SerialPorts
(因为 SerialPortClient 在这个包里) ([TouchSocket][1])
4.2 第二步 --- 设置串口参数
他知道设备串口参数是:波特率 9600,8 数据位,无校验,1 停止位,端口是 COM1。他这样写:
csharp
var config = new TouchSocket.Core.TouchSocketConfig()
.SetSerialPortOption(new SerialPortOption
{
PortName = "COM1",
BaudRate = 9600,
DataBits = 8,
Parity = System.IO.Ports.Parity.None,
StopBits = System.IO.Ports.StopBits.One
});
参照官方配置方式。 ([TouchSocket][2])
4.3 第三步 --- 创建客户端并连接
他这样创建客户端,并设置事件:
csharp
var client = new SerialPortClient();
client.Connecting = (c, e) => { Console.WriteLine("Connecting..."); return EasyTask.CompletedTask; };
client.Connected = (c, e) => { Console.WriteLine("Connected!"); return EasyTask.CompletedTask; };
client.Received = (c, e) =>
{
var msg = Encoding.UTF8.GetString(e.ByteBlock.Buffer, 0, e.ByteBlock.Len);
Console.WriteLine($"Received: {msg}");
return EasyTask.CompletedTask;
};
client.Setup(config);
client.Connect(); // 或者 ConnectAsync(取决版本)
Console.WriteLine("串口连接已发起");
根据不同版本 (2.1、3.x 等),Connect / ConnectAsync 用法可能略有不同。 ([TouchSocket][2])
五、接收数据 --- 不同方式
小王还不会完全确定设备每次发什么,他想灵活地处理接收。
5.1 委托 (Delegate) 方式
最简单:通过 Received 委托,每次有数据进来就触发:
csharp
client.Received = (c, e) =>
{
var bytes = e.ByteBlock.Buffer;
int len = e.ByteBlock.Len;
string text = Encoding.UTF8.GetString(bytes, 0, len);
Console.WriteLine($"串口收到: {text}");
return EasyTask.CompletedTask;
};
这样可以快速打印出设备发来的内容。 ([TouchSocket][1])
5.2 异步读取 (ReadAsync)
如果想在业务逻辑中同步等数据 (比如"发一个命令然后等回复"),可以用 CreateReceiver():
csharp
await client.ConnectAsync();
using (var receiver = client.CreateReceiver())
{
while (true)
{
var result = await receiver.ReadAsync(CancellationToken.None);
if (result.IsClosed)
{
break;
}
string msg = result.ByteBlock.Span.ToString(Encoding.UTF8);
Console.WriteLine($"ReadAsync 收到: {msg}");
}
}
这样你的业务逻辑就能够控制:什么时候等待,什么时候处理。
六、发送数据 --- 方式与技巧
小王想发命令给老设备,他可以这样做:
6.1 同步发送
csharp
byte[] data = Encoding.UTF8.GetBytes("HELLO\r\n");
client.Send(data);
也支持发送 ByteBlock、或指定偏移和长度。 ([TouchSocket][1])
6.2 异步发送
如果他在异步方法里,也可以:
csharp
await client.SendAsync(Encoding.UTF8.GetBytes("COMMAND1"));
6.3 延迟发送 (DelaySender)
TouchSocket 提供一个 "延迟发送"机制 (DelaySender),可以把多个小数据包合并后再发送,提高效率。默认情况下,小包可能会合并。 ([TouchSocket][1])
这个机制对频繁发送少量数据 (比如命令 / echo) 的场景非常有用。
七、插件机制 --- 给串口通信加"中间件"
在工业应用中,仅仅发数据、收数据,通常还不够。你可能还要加权限校验、日志、数据监控等。TouchSocket 的 SerialPortClient 支持插件 (Plugin),可以在连接、断开、接收、发送等各个阶段插入逻辑。
例如:
ISerialConnectingPlugin:在连接之前触发ISerialConnectedPlugin:连接成功后触发ISerialReceivingPlugin:收到原始数据 (ByteBlock) 时触发ISerialReceivedPlugin:收到经过适配器处理后的数据 (可能是对象、请求) 时触发ISerialSendingPlugin:发送前,可在适配器之后处理数据 ([TouchSocket][2])
示例插件 (接收日志):
csharp
public class LogReceivePlugin : PluginBase, ISerialReceivedPlugin<ISerialPortClient>
{
public async Task OnSerialReceived(ISerialPortClient client, ReceivedDataEventArgs e)
{
var bytes = e.ByteBlock.Buffer;
string hex = BitConverter.ToString(bytes, 0, e.ByteBlock.Len);
Console.WriteLine($"[Plugin] 接收到 (hex): {hex}");
await e.InvokeNext(); // 调用下一个插件 / 默认处理
}
}
然后,在客户端设置时注册插件:
csharp
var client = new SerialPortClient();
client.Setup(new TouchSocket.Core.TouchSocketConfig()
.SetSerialPortOption(new SerialPortOption { /* ... */ })
.ConfigurePlugins(p => p.Add<LogReceivePlugin>())
);
await client.ConnectAsync();
八、小王的场景实战 --- 一个命令 & 回应流程
让我们回到小王。他想做这样的流程:
- 打开串口
- 给设备发一个 "STATUS?" 命令
- 等待设备回 "OK:<状态>"
- 打印状态并关闭串口
示例代码:
csharp
var client = new SerialPortClient();
client.Setup(new TouchSocket.Core.TouchSocketConfig()
.SetSerialPortOption(new SerialPortOption
{
PortName = "COM1",
BaudRate = 9600,
DataBits = 8,
Parity = Parity.None,
StopBits = StopBits.One
}));
await client.ConnectAsync();
var receiver = client.CreateReceiver();
byte[] cmd = Encoding.ASCII.GetBytes("STATUS?\r\n");
var waitClient = client.CreateWaitingClient(new WaitingOptions
{
FilterFunc = response => true, // 简单示例:接受第一个回应
Timeout = 2000
};
byte[] reply = waitClient.SendThenReturn(cmd);
string replyStr = Encoding.ASCII.GetString(reply);
Console.WriteLine($"收到回应: {replyStr}");
await receiver.DisposeAsync();
client.Close();
这段代码里用了 CreateWaitingClient,可以同步发送然后等待回应 (或超时),非常适合命令/响应型协议。
九、小结 --- 为什么推荐 SerialPortClient
- 它是 TouchSocket 提供的、对串口 (.NET) 非常友好的客户端库。
- 支持高性能、内存池、适配器机制,可以处理复杂协议。
- 插件机制强大:你可以根据业务插入自己的逻辑 (AOP)。
- 发送 / 接收灵活:同步 / 异步 / 延迟发送 / 异步接收 / 等待回应。
- 可用于现代 .NET 应用,同时兼容旧串口设备。
十、进一步思考与扩展
- 如果你不仅做串口,而且还在做 Modbus,你可以把
SerialPortClient和ModbusRtuMaster结合起来,这样既能处理 Modbus,又能灵活扩展。 - 考虑线程安全:如果你的应用是高并发、多线程 (多个串口任务),要注意
SerialPortClient的生命周期和资源管理。 - 日志与监控:通过插件记录每次收发的数据、连接状态等,对于运维非常有帮助。
- 错误处理:串口可能断开 (被物理拔线)、读写失败等,要在实际项目里写好重连策略和异常处理。