串口客户端背后的故事:TouchSocket SerialPortClient 探秘

前言 --- 一个"老设备"的诉说

在工控现场,总有一些老旧设备,它们不支持以太网、也没有 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 非常适合以下几类场景:

  1. 传统串口设备通信

    PLC、传感器、仪器、工业控制老设备等,通过 RS-232、RS-485 等串口标准通信。

  2. 自定义协议解析

    你的设备数据格式并不标准(不是 Modbus,也不是某个现成协议),你可以自己实现适配器 (Adapter),让 SerialPortClient 帮你处理粘包 / 拆包 / 解析。

  3. 跨平台使用

    由于 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();

八、小王的场景实战 --- 一个命令 & 回应流程

让我们回到小王。他想做这样的流程:

  1. 打开串口
  2. 给设备发一个 "STATUS?" 命令
  3. 等待设备回 "OK:<状态>"
  4. 打印状态并关闭串口

示例代码:

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,你可以把 SerialPortClientModbusRtuMaster 结合起来,这样既能处理 Modbus,又能灵活扩展。
  • 考虑线程安全:如果你的应用是高并发、多线程 (多个串口任务),要注意 SerialPortClient 的生命周期和资源管理。
  • 日志与监控:通过插件记录每次收发的数据、连接状态等,对于运维非常有帮助。
  • 错误处理:串口可能断开 (被物理拔线)、读写失败等,要在实际项目里写好重连策略和异常处理。

相关推荐
哥哥还在IT中2 小时前
Docker的Cgroup Driver设置为Cgroupfs 和 Systemd 的区别
运维·docker·容器
胜似代码仔2 小时前
metrics-server 部署报错
运维
b***65322 小时前
自己编译RustDesk,并将自建ID服务器和key信息写入客户端
运维·服务器
k***45992 小时前
服务器无故nginx异常关闭之kauditd0 kswapd0挖矿病毒 CPU占用200% 内存耗尽
运维·服务器·nginx
p***62992 小时前
【Sql Server】sql server 2019设置远程访问,外网服务器需要设置好安全组入方向规则
运维·服务器·安全
java_logo2 小时前
LobeHub Docker 容器化部署指南
运维·人工智能·docker·ai·容器·ai编程·ai写作
q***01772 小时前
Linux 下安装 Golang环境
linux·运维·golang
企鹅侠客3 小时前
Linux性能调优使用strace来分析文件系统的性能问题
linux·运维·服务器
qinyia3 小时前
WisdomSSH解决因未使用Docker资源导致的磁盘空间不足问题
运维·服务器·人工智能·后端·docker·ssh·github