C#搞Modbus通信,这事没你想的那么玄乎
2026/8/26 17:20:01 网站建设 项目流程

用C#写Modbus通信,算下来五六年了。从刚开始对着NModbus源码发懵,到现在自己撸一套轻量级协议栈也就半天功夫。网上教程一搜一大把,但大多照着官方Demo抄,真正现场跑起来该崩还是崩。今天不整花架子,纯实战经验,把C#里那点事儿捋清楚。

先定个调:C#做Modbus通信,轮子不少,但真正好使的还得自己动手撸。第三方库省事是省事,但出问题你连调试的底气都没有。

一、库的选择:用现成的还是自己写?

市面上C#的Modbus库,NModbus是祖师爷,开源、稳定、功能全。但有个硬伤——它是基于.NET Framework写的,.NET Core/.NET 5+环境跑起来各种依赖报错。后来有个NModbus4的fork,支持.NET Standard,能用,但异步支持很弱,高并发场景卡得一塌糊涂。

另外有个EasyModbus,商业授权,功能花哨,但代码封装太严,出问题你连日志都打不出来。

我现在的策略是:串口RTU用NModbus4改一版,TCP自己写。TCP那点东西真没必要引个第三方库,一个Socket加一个解析类,两百行代码完事。

二、串口通信:SerialPort是基本功

.NET自带的SerialPort类,不好用,但够用。最烦人的是DataReceived事件,在子线程触发,你想在回调里直接更新UI?等着崩吧。必须Invoke切回主线程。

关键参数设置:

SerialPort sp = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One); sp.ReadTimeout = 200; sp.WriteTimeout = 200;

超时时间必须设,默认InfiniteTimeout会把你程序挂死。ReadBufferSize和WriteBufferSize别乱改,默认值够了。

有个隐藏坑:DataReceived事件触发时,不一定收全了一帧。你收到几个字节就开始解析,那必然乱套。必须自己实现帧缓存,等够了再处理。RTU要等3.5字符静默,C#里没法精确到那么细,通用做法是用超时判断:收到第一个字节后启动计时器,连续若干毫秒没有新数据就视为帧结束。

三、CRC计算:C#里就几行的事

RTU的CRC-16-Modbus,C#实现起来极其简单:

csharp

public static byte[] CalculateCRC(byte[] data) { ushort crc = 0xFFFF; for (int i = 0; i < data.Length; i++) { crc ^= data[i]; for (int j = 0; j < 8; j++) { if ((crc & 0x0001) != 0) crc = (ushort)((crc >> 1) ^ 0xA001); else crc >>= 1; } } return new byte[] { (byte)(crc & 0xFF), (byte)(crc >> 8) }; }

注意返回顺序:低字节在前,高字节在后。很多人算出来是对的,但发的时候顺序搞反,从站不认。

查表法快,但占内存。工业现场就几百个寄存器轮询,按位算那点CPU消耗根本不叫事,别过度优化。

四、TCP通信:Socket用同步还是异步?

TCP这块分歧最大。有人说用异步BeginReceive/EndReceive,有人说用同步Receive加超时。

我的经验分场景:

场景一:单连接轮询,周期固定。同步Receive足够。把ReceiveTimeout设好,一个循环里发请求、等回复、解析、Sleep,逻辑清晰,代码好维护。发多个请求时,事务ID自己递增,同步模式下不存在并发问题。

场景二:多连接并发,或者需要同时处理多个从站。这时候必须上异步或者多线程。我的做法是每个连接开一个独立线程,内部用同步Receive阻塞,互不干扰。线程数控制在10个以内,别开太多,TCP连接数和线程上下文切换都是开销。

异步模式的坑在于状态机管理复杂。BeginReceive回调里处理粘包,一堆临时缓存和状态变量,代码很快变得不可维护。除非你有经验,否则别轻易碰。

五、粘包处理:这地方写不好就全盘崩

TCP流式传输,粘包必然存在。C#里解析标准流程:

csharp

private byte[] _buffer = new byte[4096]; private int _offset = 0; public void OnDataReceived(byte[] data) { Array.Copy(data, 0, _buffer, _offset, data.Length); _offset += data.Length; while (_offset >= 7) // 至少够MBAP头 { // 解析长度域,第5-6字节 int len = (_buffer[4] << 8) | _buffer[5]; int totalLen = 7 + len; if (_offset >= totalLen) { // 截取一完整帧 byte[] frame = new byte[totalLen]; Array.Copy(_buffer, 0, frame, 0, totalLen); ProcessFrame(frame); // 移除已处理数据 int remain = _offset - totalLen; if (remain > 0) Array.Copy(_buffer, totalLen, _buffer, 0, remain); _offset = remain; } else break; } }

这段逻辑是所有TCP Modbus解析的核心,写对了后面就顺了。注意while循环,缓冲区可能一次收到多帧,要全部拆出来。

六、异常处理:设备不按套路出牌

Modbus异常响应,功能码最高位置1,后面跟异常码。C#里判断:

csharp

if ((response[1] & 0x80) != 0) // 功能码最高位为1 { byte exceptionCode = response[2]; switch(exceptionCode) { case 0x01: throw new Exception("非法功能"); case 0x02: throw new Exception("非法地址"); case 0x03: throw new Exception("非法数据"); case 0x04: throw new Exception("从站设备故障"); default: throw new Exception($"未知异常码:0x{exceptionCode:X2}"); } }

但现实比这复杂。有些设备不按标准来,超时不回、回了长度不对、CRC校验错、功能码跟请求对不上。每一种都得有对应的容错,不然一个异常把整个轮询线程搞死了。

我的经验:任何一次通信失败,记录日志然后继续下一轮,别重试太多次,别阻塞

七、线程安全:写寄存器时注意

多线程环境下,如果一个线程在轮询读数据,另一个线程突然写寄存器,可能引发问题。

写操作是原子性的——发写请求、等回复,这个过程中轮询线程不要发其他请求。最简单的方法:读写共用一把锁,任何操作前Lock住,发完解锁。

有更高性能的做法是读写分离,读操作走一个连接,写操作走另一个独立连接。但前提是从站支持多连接,很多设备不支持。

八、日志与调试:这比代码本身重要

做工业通信,没有日志等于裸奔。每个请求和响应的完整报文必须以十六进制打印出来,带时间戳和方向(发/收)。

csharp

Log($"发送: {BitConverter.ToString(sendBuf)}"); Log($"接收: {BitConverter.ToString(recvBuf)}");

现场出了故障,用户把日志发过来,一看报文就知道是CRC错还是地址错还是设备没回。省下来的时间比你写代码的时间都多。

Wireshark抓TCP包配合自己打的日志,双保险。串口的话用虚拟串口工具或者硬件监听器,把数据流截下来对比。

九、实战框架结构:推荐这么组织代码

我自己用的结构,三层:

底层(通信层):SerialPort或Socket封装,只负责收发原始字节,带超时和重试机制。

中间层(协议层):负责拼装请求帧、解析响应帧、CRC校验、粘包处理。这一层跟通信介质无关,RTU和TCP可以共用大部分代码。

上层(应用层):提供业务接口,比如ReadHoldingRegister(byte slaveId, ushort startAddr, ushort count)。上层不用管报文长什么样,只管传参数拿结果。

三层分离,换通信介质只改底层,换协议只改中间层,应用层纹丝不动。

十、说个实战案例

去年做一套电池化成设备的数据采集系统,128台设备,每台要读30个寄存器。一开始用第三方库,单线程轮询,一圈下来要40多秒,上位机嫌慢。

后来自己改实现,开4个TCP连接,每个连接分担32台设备,线程池调度,事务ID逐请求递增。优化后一圈不到8秒,用户满意了。

但新问题来了:偶尔有设备掉线,程序重连时某些线程卡住,连带其他设备也读不到数据。最后加了健康检查——每个线程维护一个连接状态,读超时就标记异常,单独启动重连,不影响其他线程的正常轮询。

这行的经验就是一个个现场坑踩出来的,代码里每行异常处理背后都是加班到半夜的血泪史。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询