本文重点整理 Modbus TCP/IP、Modbus UDP/IP、Modbus RTU Over TCP/IP 与 Modbus RTU Over UDP/IP 的区别、帧结构、C# 常用操作和设备联调排查方法。内容基于当前 Modebus 文件夹中的学习材料、NModbus4 示例代码和设备联调记录整理,并补充实际项目中常见的判断方法。
一、核心概念
| Modbus | 工业现场常用的主从式通信协议,常用于 PLC、传感器、仪表、网关与上位机之间的数据读写。 |
| 主站 / 客户端 | 主动发起请求的一方,例如上位机软件、采集程序、SCADA 系统。 |
| 从站 / 服务端 | 被动响应请求的一方,例如温湿度传感器、PLC、串口服务器、Modbus 模拟器。 |
| 从站地址 / Unit ID | 用于区分设备或网关后的串口从站。Modbus RTU 中常见范围是 1-247。 |
| 功能码 | 表示本次操作类型,例如读保持寄存器 0x03、读输入寄存器 0x04、写单寄存器 0x06、写多个寄存器 0x10。 |
| 寄存器地址 | 设备内部数据地址。程序中常使用从 0 开始的协议地址,说明书中可能写成 40001、30001 等逻辑地址。 |
| Modbus TCP/IP | 原生以太网 Modbus 协议,使用 TCP 传输,通常使用 502 端口,报文中包含 MBAP 头,不使用 RTU CRC。 |
| Modbus UDP/IP | 部分设备或软件支持的 Modbus over UDP 实现,是否可用要以设备说明书或网关配置为准。 |
| Modbus RTU | 串口场景下常见的 Modbus 格式,常基于 RS-485,帧尾包含 CRC16 校验。 |
| Modbus RTU Over TCP/IP | 将完整 RTU 帧通过 TCP 传输,常见于串口服务器或透明传输网关。 |
| Modbus RTU Over UDP/IP | 将完整 RTU 帧通过 UDP 数据报传输,常见于部分串口服务器的 UDP 透明传输模式。 |
| MBAP Header | Modbus TCP/IP 报文头,包含事务标识、协议标识、长度、单元标识等字段。 |
| CRC16 | Modbus RTU 使用的循环冗余校验,低字节在前,高字节在后。 |
1. Modbus TCP/IP
Modbus TCP/IP 是把 Modbus 应用层数据封装到 TCP 中的通信方式。它是原生以太网 Modbus 协议,不是简单地把 RTU 串口帧放进 TCP。
| 传输层 | TCP,面向连接,可靠传输。 |
| 常用端口 | 通常是 502,但串口服务器、网关或模拟器可能配置为其他端口。 |
| 报文结构 | MBAP Header + PDU。 |
| 是否包含 CRC | 不包含 RTU CRC,TCP/IP 链路本身已有校验机制。 |
| 常见场景 | 上位机读取 PLC、网关、传感器、模拟器等网络设备。 |
| 角色关系 | 通常设备或模拟器作为服务端监听端口,上位机程序作为客户端主动连接。 |
通信过程可以理解为:
客户端软件 -> TCP 连接 -> 设备端口 -> 读取或写入寄存器 -> 返回响应
2. Modbus UDP/IP
Modbus UDP/IP 与 Modbus TCP/IP 类似,都是以太网场景下的 Modbus 通信方式,但底层使用 UDP。它不像 Modbus TCP/IP 那样常见,实际项目中必须先确认设备是否支持。
| 传输层 | UDP,无连接。 |
| 常用端口 | 可能使用 502,也可能由设备或网关自定义。 |
| 报文结构 | 通常按设备要求封装 Modbus IP 报文。 |
| 可靠性 | UDP 不保证送达、不保证顺序、不自动重传。 |
| 常见场景 | 设备明确支持 UDP、局域网快速采集、厂家自定义广播或发现机制。 |
UDP 的优势是开销较小、延迟通常较低;缺点是可靠性需要应用层自行处理,例如超时、重试、丢包判断等。如果设备同时支持 TCP 和 UDP,生产环境通常优先选择 TCP。
3. Modbus RTU Over TCP/IP
Modbus RTU Over TCP/IP 不是 Modbus TCP/IP。它只是把完整的 RTU 帧通过 TCP 传输,帧中仍然包含从站地址和 CRC16。
| 传输层 | TCP。 |
| 报文结构 | RTU 地址 + 功能码 + 数据 + CRC16。 |
| 是否包含 CRC | 包含 RTU CRC16。 |
| 典型设备 | RS-485 传感器 + 串口服务器。 |
| 常见场景 | 原有设备只支持 Modbus RTU,但现场希望通过网线或网络访问。 |
常见链路如下:
上位机程序 -> TCP -> 串口服务器 -> RS-485 -> Modbus RTU 设备
这种方式适合兼容已有串口 RTU 设备。在原生以太网设备之间通信时,一般优先选择 Modbus TCP/IP。
4. Modbus RTU Over UDP/IP
Modbus RTU Over UDP/IP 与 RTU Over TCP 类似,只是底层改为 UDP。它仍然保留 RTU 帧格式和 CRC16,可靠性取决于网络质量以及程序中的超时重试机制。
| 传输层 | UDP。 |
| 报文结构 | RTU 地址 + 功能码 + 数据 + CRC16。 |
| 是否包含 CRC | 包含 RTU CRC16。 |
| 可靠性 | UDP 不保证送达,通常需要应用层重试。 |
| 常见场景 | 部分串口服务器的 UDP 透明传输模式。 |
5. 四种网络方式对比
| Modbus TCP/IP | TCP | MBAP + PDU | 需要 | 否 | 高 | 原生网口 PLC、网关、模拟器通信。 |
| Modbus UDP/IP | UDP | 取决于设备实现 | 不需要 | 通常否 | 中低 | 明确支持 UDP 的设备或厂家扩展场景。 |
| RTU Over TCP/IP | TCP | RTU 完整帧 | 需要 | 是 | 较高 | 串口服务器连接 RS-485 设备。 |
| RTU Over UDP/IP | UDP | RTU 完整帧 | 不需要 | 是 | 中低 | UDP 透明传输串口服务器。 |
6. Modbus TCP 与 RTU Over TCP 的关键区别
| 本质 | 原生以太网 Modbus 协议。 | 串口 RTU 帧的网络透明传输。 |
| 报文头 | 有 MBAP Header。 | 无 MBAP Header。 |
| CRC | 不带 RTU CRC。 | 带 CRC16。 |
| Unit ID | 在 MBAP Header 中。 | 在 RTU 帧第 1 个字节。 |
| 设备类型 | 网络型 Modbus 设备。 | 串口设备加串口服务器或网关。 |
| 选型依据 | 设备说明书写 Modbus TCP。 | 设备说明书写 Modbus RTU,网关配置为 TCP/UDP 透明传输。 |
二、常用操作
1. 常用功能码
| 0x01 | Read Coils | ReadCoils | 读取线圈,返回布尔值。 |
| 0x02 | Read Discrete Inputs | ReadInputs | 读取离散输入,返回布尔值。 |
| 0x03 | Read Holding Registers | ReadHoldingRegisters | 读取保持寄存器,常用于采集设备数据。 |
| 0x04 | Read Input Registers | ReadInputRegisters | 读取输入寄存器,常用于只读测量值。 |
| 0x05 | Write Single Coil | WriteSingleCoil | 写单个线圈。 |
| 0x06 | Write Single Register | WriteSingleRegister | 写单个保持寄存器。 |
| 0x0F | Write Multiple Coils | WriteMultipleCoils | 写多个线圈。 |
| 0x10 | Write Multiple Registers | WriteMultipleRegisters | 写多个保持寄存器。 |
2. C# 使用 NModbus4 创建不同通信模式
| Modbus TCP/IP | TcpClient | ModbusIpMaster.CreateIp(tcpClient) | 原生 Modbus TCP。 |
| Modbus UDP/IP | UdpClient | ModbusIpMaster.CreateIp(udpClient) | UDP 方式的 Modbus IP,需设备支持。 |
| RTU Over TCP/IP | TcpClient | ModbusSerialMaster.CreateRtu(tcpClient) | TCP 中传输 RTU 帧。 |
| RTU Over UDP/IP | UdpClient | ModbusSerialMaster.CreateRtu(udpClient) | UDP 中传输 RTU 帧。 |
using Modbus.Device;
using System;
using System.Net;
using System.Net.Sockets;
public enum ModbusNetworkMode
{
TcpIp,
UdpIp,
RtuOverTcp,
RtuOverUdp
}
public static class ModbusMasterFactory
{
public static ModbusMaster Create(string ip, int port, ModbusNetworkMode mode)
{
IPAddress address = IPAddress.Parse(ip);
switch (mode)
{
case ModbusNetworkMode.TcpIp:
TcpClient tcpClient = new TcpClient();
tcpClient.Connect(address, port);
return ModbusIpMaster.CreateIp(tcpClient);
case ModbusNetworkMode.UdpIp:
UdpClient udpClient = new UdpClient();
udpClient.Connect(address, port);
return ModbusIpMaster.CreateIp(udpClient);
case ModbusNetworkMode.RtuOverTcp:
TcpClient rtuTcpClient = new TcpClient();
rtuTcpClient.Connect(address, port);
return ModbusSerialMaster.CreateRtu(rtuTcpClient);
case ModbusNetworkMode.RtuOverUdp:
UdpClient rtuUdpClient = new UdpClient();
rtuUdpClient.Connect(address, port);
return ModbusSerialMaster.CreateRtu(rtuUdpClient);
default:
throw new NotSupportedException("不支持的 Modbus 通信模式");
}
}
}
具体是否能正常通信,取决于设备或网关支持的协议模式,不能只看代码创建方式。
3. 读取保持寄存器
| slaveAddress | 1 | 从站地址或 Unit ID。 |
| startAddress | 0 | 起始寄存器地址,通常从 0 开始。 |
| numberOfPoints | 4 | 读取寄存器数量。 |
| 返回值 | ushort[] | 每个寄存器是 16 位无符号整数。 |
using Modbus.Device;
public static ushort[] ReadFourHoldingRegisters(ModbusMaster master)
{
byte slaveAddress = 1;
ushort startAddress = 0;
ushort registerCount = 4;
return master.ReadHoldingRegisters(slaveAddress, startAddress, registerCount);
}
4. 定时读取设备数据
| 建立连接 | 根据设备说明选择 TCP、UDP、RTU Over TCP 或 RTU Over UDP。 |
| 周期读取 | 使用定时器或后台任务定时读取寄存器。 |
| 数据校验 | 判断返回数量是否符合预期。 |
| UI 更新 | WinForms 中需要通过 Invoke 回到 UI 线程。 |
| 停止读取 | 使用 CancellationTokenSource 取消后台任务。 |
using Modbus.Device;
using System;
using System.Threading;
using System.Threading.Tasks;
public class ModbusPollingService
{
private readonly ModbusMaster _master;
private CancellationTokenSource _cts;
public ModbusPollingService(ModbusMaster master)
{
_master = master;
}
public void Start(Action<ushort[]> onDataReceived)
{
_cts = new CancellationTokenSource();
Task.Run(async () =>
{
while (!_cts.IsCancellationRequested)
{
ushort[] data = _master.ReadHoldingRegisters(1, 0, 4);
if (data.Length == 4)
{
onDataReceived(data);
}
await Task.Delay(1000, _cts.Token);
}
}, _cts.Token);
}
public void Stop()
{
_cts?.Cancel();
}
}
5. 写单个保持寄存器
| 从站地址 | 1 | 目标设备地址。 |
| 寄存器地址 | 0 | 要写入的寄存器地址。 |
| 写入值 | 12 | 写入的 16 位数值。 |
using Modbus.Device;
public static void WriteSingleRegister(ModbusMaster master)
{
byte slaveAddress = 1;
ushort registerAddress = 0;
ushort value = 12;
master.WriteSingleRegister(slaveAddress, registerAddress, value);
}
6. RTU 帧结构与 CRC16
| 从站地址 | 1 字节 | 设备地址,常见范围 1-247。 |
| 功能码 | 1 字节 | 表示读写类型。 |
| 数据区 | N 字节 | 起始地址、数量、写入值等。 |
| CRC16 | 2 字节 | 低字节在前,高字节在后。 |
读取保持寄存器 01 03 00 00 00 0A 的 CRC 结果常见为 CD C5,完整 RTU 请求帧为:
01 03 00 00 00 0A CD C5
public static byte[] CalculateModbusCrc16(byte[] data)
{
ushort crc = 0xFFFF;
foreach (byte item in data)
{
crc ^= item;
for (int i = 0; i < 8; i++)
{
bool lsb = (crc & 0x0001) != 0;
crc >>= 1;
if (lsb)
{
crc ^= 0xA001;
}
}
}
return new[] { (byte)(crc & 0xFF), (byte)(crc >> 8) };
}
7. 设备联调流程
| 确认设备型号 | 根据设备名称和型号找到官网资料。 |
| 下载说明书 | 重点查看通信协议、寄存器表、接线方式、默认地址、波特率、端口。 |
| 判断协议类型 | 区分 Modbus TCP、Modbus RTU、RTU Over TCP、RTU Over UDP。 |
| 准备工具 | 使用 Modbus Poll、Modbus Slave、网络调试助手、串口调试助手或厂家工具。 |
| 先工具验证 | 先用工具读通设备,再写代码。 |
| 编写程序 | 按说明书读取指定寄存器,并处理比例系数、单位和数据类型。 |
| 记录结果 | 记录 IP、端口、从站地址、寄存器地址、数据含义和异常现象。 |
三、问题排查
错误1:SocketException 或连接失败
-
现象:TCP 连接失败,程序无法连接设备 IP 和端口。
-
原因:IP 不通、端口错误、防火墙拦截、设备未开启 Modbus TCP 服务、串口服务器模式配置错误。
-
解决:
-
检查网络:确认电脑和设备在同一网段,先 ping 设备 IP。
-
检查端口:Modbus TCP 通常使用 502,串口服务器可能使用自定义端口。
-
检查模式:设备如果是 RTU 设备接串口服务器,应确认网关是 TCP Server、TCP Client 还是 UDP 模式。
-
检查占用:同一设备可能只允许一个客户端连接,关闭其他调试工具后重试。
-
错误2:读取寄存器返回超时
-
现象:连接成功,但 ReadHoldingRegisters 长时间无响应或抛出超时异常。
-
原因:从站地址错误、寄存器地址错误、功能码不支持、设备没有接好、串口参数不一致。
-
解决:
-
确认从站地址:RTU 设备尤其要确认地址,常见默认值为 1。
-
确认寄存器地址:说明书写 40001 时,程序里常用地址 0。
-
确认功能码:保持寄存器用 0x03,输入寄存器用 0x04。
-
确认串口参数:RTU 设备需要一致的波特率、数据位、校验位、停止位。
-
错误3:CRC 校验失败
-
现象:RTU 或 RTU Over TCP/UDP 返回数据后 CRC 校验不通过。
-
原因:通信噪声、帧截断、协议模式选错、把 Modbus TCP 当成 RTU Over TCP 使用。
-
解决:
-
区分协议:Modbus TCP 不带 RTU CRC;RTU Over TCP/UDP 必须带 CRC。
-
检查网关模式:串口服务器需要配置为透明传输或对应的 Modbus 网关模式。
-
检查接线:RS-485 的 A/B 线接反、屏蔽接地不良都可能导致数据异常。
-
降低频率:轮询太快可能导致设备响应不完整,适当增加读取间隔。
-
错误4:数据能读到但数值不正确
-
现象:寄存器有返回值,但温度、湿度、压力等数据明显不合理。
-
原因:寄存器地址偏移、字节序错误、数据类型解析错误、未处理比例系数。
-
解决:
-
查看说明书:确认数据类型是 ushort、short、int32、float 还是 BCD。
-
处理比例系数:很多传感器需要除以 10、100 或 1000。
-
处理有符号数:温度等可能使用 short 表示负数。
-
检查高低字顺序:32 位数据可能存在 AB CD、CD AB、BA DC、DC BA 等字节序差异。
-
错误5:UDP 模式偶发丢包
-
现象:UDP 通信有时读取成功,有时无响应。
-
原因:UDP 本身不保证可靠传输,网络拥塞或设备处理能力不足都会导致丢包。
-
解决:
-
增加重试:请求失败后重发 1 到 3 次。
-
设置超时:避免程序一直等待。
-
降低频率:读取周期不要过短。
-
优先 TCP:如果设备支持 TCP,生产环境优先使用 Modbus TCP 或 RTU Over TCP。
-
错误6:WinForms 实时读取界面卡顿或跨线程异常
-
现象:实时读取时界面卡死,或提示不能从非 UI 线程访问控件。
-
原因:在 UI 线程执行耗时通信,或后台线程直接修改控件。
-
解决:
-
后台读取:把 Modbus 读写放到 Task.Run 或定时任务中。
-
UI 线程更新:WinForms 使用 Invoke 或 BeginInvoke 更新控件。
-
支持取消:使用 CancellationTokenSource 停止实时读取。
-
四、相关资源
-
官方文档:Modbus Organization
-
协议规范:Modbus Application Protocol Specification
-
C# 库:NModbus4 NuGet
-
常用工具:Modbus Poll
-
常用工具:Modbus Slave
五、学习总结
-
协议选择:设备说明书写 Modbus TCP 时,用原生 Modbus TCP/IP;设备是 RS-485 RTU 再通过串口服务器联网时,通常考虑 RTU Over TCP/IP 或 RTU Over UDP/IP。
-
帧格式判断:带 MBAP、不带 CRC 的是 Modbus TCP/IP;带从站地址和 CRC 的是 Modbus RTU 或 RTU Over 网络。
-
UDP 使用原则:UDP 不是首选通用方案,只有设备明确支持或业务确实需要时才使用,并且要做好超时和重试。
-
联调顺序:先读说明书,再用工具验证,最后写代码。不要一开始就直接写程序猜地址和功能码。
-
常见坑点:端口不对、从站地址不对、寄存器偏移、功能码选错、比例系数没处理、TCP 与 RTU Over TCP 混淆。



