1. 工控场景下的串口通信痛点解析
在工业控制领域工作了十几年,我见过太多同行在C#串口通信上栽跟头。记得2017年参与某汽车生产线改造时,现场工程师花了三天三夜调试一个简单的PLC通信程序——不是功能有多复杂,而是SerialPort类的各种坑让他疲于奔命。这正是促使我整理这套模板的契机。
工控场景的特殊性决定了串口通信必须满足三个核心要求:
- 高可靠性:生产线不能因为通信错误停机,一次误码可能导致百万损失
- 实时响应:传感器数据需要在50ms内完成采集-传输-响应闭环
- 异常健壮:要能处理插拔抖动、电压不稳、电磁干扰等恶劣环境
传统做法直接使用SerialPort类会遇到几个典型问题:
- 数据接收使用DataReceived事件,但事件触发存在线程安全问题
- 未处理串口热插拔导致的资源释放异常
- 缺少通信超时重试机制,设备无响应时会永久阻塞
- 多线程环境下容易引发资源竞争死锁
2. 通用模板的架构设计
这套模板的核心思想是状态机+双缓冲队列,经过六年迭代已形成稳定架构。最新版本(V3.2)的模块组成如下:
csharp复制public class SerialPortHelper : IDisposable
{
// 硬件层
private SerialPort _serialPort;
private readonly object _syncLock = new();
// 协议层
private readonly Queue<byte[]> _sendQueue = new(); // 发送缓冲
private readonly List<byte> _receiveBuffer = new(); // 接收缓冲
private Timer _watchdogTimer; // 看门狗计时器
// 业务层
public event Action<byte[]> OnDataReceived;
public event Action<string> OnStatusChanged;
}
2.1 线程安全的三层缓冲设计
发送缓冲队列采用生产者-消费者模式:
csharp复制public void Send(byte[] data)
{
lock (_syncLock)
{
_sendQueue.Enqueue(data);
if (_sendQueue.Count == 1) // 首次添加时触发发送
{
ThreadPool.QueueUserWorkItem(_ => SendNextPacket());
}
}
}
private void SendNextPacket()
{
byte[] packet;
lock (_syncLock)
{
if (_sendQueue.Count == 0) return;
packet = _sendQueue.Peek();
}
try
{
_serialPort.Write(packet, 0, packet.Length);
// 发送成功后出队
lock (_syncLock) _sendQueue.Dequeue();
}
catch { /* 异常处理 */ }
}
接收缓冲采用动态扩容设计:
- 初始分配1KB缓冲区
- 遇到数据溢出时自动扩容(最大限制16MB)
- 通过MemoryStream优化大块数据处理性能
2.2 看门狗机制的实现
工控现场最怕通信死锁,模板内置三重保护:
- 发送超时:单次发送超过500ms未完成则终止
- 响应超时:发送后3秒内未收到回复触发重试
- 心跳检测:空闲状态下每分钟发送0x55心跳包
csharp复制private void StartWatchdog()
{
_watchdogTimer = new Timer(state =>
{
if (_lastActiveTime.AddSeconds(30) < DateTime.Now)
{
