4. Modbus TCP详解

Modbus TCP 是 Modbus 协议在以太网上的承载形式。它保留了 Modbus 最核心的“功能码 + 寄存器地址 + 数据”语义,但把底层的串行链路换成了 TCP/IP。理解 Modbus TCP,关键不是记住某个功能码,而是看清一条请求从客户端到服务器之间,究竟经过了哪些封装、由哪些字段负责寻址与匹配。对于网关工程而言,这直接决定了你能否正确配置端口、处理多主站并发、定位超时与错配问题。

一、以太网基础:Modbus TCP 跑在什么之上

Modbus TCP 通常使用标准以太网和 TCP/IP 协议栈。物理层可以是 100BASE-TX、1000BASE-T,链路层是以太网帧,网络层是 IP,传输层是 TCP。Modbus 自身不定义 IP 地址分配、路由和交换机行为,它只定义“应用层报文格式”和“默认端口”。因此,一个 Modbus TCP 设备能否被访问,首先取决于 IP 可达性,其次才取决于 Modbus 报文是否正确。

与 Modbus RTU 相比,Modbus TCP 不再使用从站地址来区分设备,而是用 IP 地址 + 端口号定位服务器。RTU 中的 CRC 校验也被 TCP 的校验和与链路层校验替代,因此 MBAP 报文中不再出现 CRC 字段。这是初学者最容易混淆的地方:RTU 帧尾的 CRC 在 TCP 中不存在,取而代之的是 MBAP 头中的长度字段和事务标识。

记忆要点:Modbus TCP = MBAP 头 + PDU。PDU 与 Modbus RTU 的 PDU 完全一致,区别只在“外面套了什么”。

二、MBAP 报文头:7 个字节决定一切

MBAP(Modbus Application Protocol)头固定为 7 个字节,位于每个 Modbus TCP 报文的最前面。它依次包含:事务标识符(2 字节)、协议标识符(2 字节)、长度(2 字节)、单元标识符(1 字节)。其后紧跟 PDU,即功能码和数据。

字段长度典型值作用
事务标识符2 字节0x0001、0x0002…匹配请求与响应
协议标识符2 字节0x00000 表示 Modbus
长度2 字节后续字节数单元标识符 + PDU 的长度
单元标识符1 字节0x01、0xFF网关中用于指向串行从站
PDU可变功能码 + 数据真正的 Modbus 操作

其中“长度”字段容易算错。它统计的是从单元标识符开始到 PDU 结束的字节数,不包括事务标识符和协议标识符本身。例如读保持寄存器请求,PDU 为 6 字节,加上单元标识符 1 字节,长度字段应为 7。若长度字段错误,服务器可能直接丢弃报文或等待更多数据,表现为“连接正常但无响应”。

Modbus TCP 报文结构:MBAP 头 + PDU MBAP 报文头(7 字节) 事务标识符 2B 协议标识符 2B 长度 2B 单元标识符 1B PDU 可变 PDU = 功能码 + 数据 与 Modbus RTU 的 PDU 完全一致,不含 CRC 典型读保持寄存器请求(十六进制) 00 01 | 00 00 | 00 06 | 01 | 03 | 00 00 | 00 0A 事务ID | 协议ID | 长度 | 单元ID | 功能码 | 起始地址 | 寄存器数量

三、端口号与连接管理

Modbus TCP 的默认服务器端口是 502。客户端通常使用临时端口发起连接,服务器在 502 端口监听。工程中常见做法是:网关作为 Modbus TCP 服务器,PLC、SCADA 或上位机作为客户端主动连接;也可以让网关作为客户端,主动连接远端 Modbus TCP 设备。方向不同,连接管理策略完全不同。

Modbus TCP 支持长连接和短连接。长连接适合周期性轮询,减少三次握手开销;短连接适合低频访问或需要快速释放资源的场景。需要注意:Modbus TCP 本身没有规定“一个连接只能有一个未完成事务”,但很多设备实现为“单连接串行处理”。如果客户端在同一连接上连续发送多个请求而不等待响应,部分设备会乱序或丢弃。稳妥做法是:同一连接内保持请求-响应串行,或使用多个连接并行。

# 使用命令行测试 Modbus TCP 502 端口连通性
telnet 192.168.1.10 502

# 使用 Python pymodbus 读取保持寄存器
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('192.168.1.10', port=502)
client.connect()
rr = client.read_holding_registers(address=0, count=10, slave=1)
print(rr.registers)
client.close()

四、事务标识与单元标识:两个“ID”不要混

事务标识符由客户端生成,服务器在响应中原样返回。它的作用是让客户端把响应匹配到对应的请求。在并发或多连接场景中,事务标识符是防止“张冠李戴”的关键。若客户端不检查事务标识符,就可能把 A 请求的响应当成 B 请求的结果,导致数据错位。

单元标识符则用于网关场景:当 Modbus TCP 网关背后挂接多个 Modbus RTU 从站时,单元标识符就是 RTU 从站地址。服务器收到 TCP 请求后,根据单元标识符把 PDU 转发到对应串行从站。若单元标识符为 0 或 255,部分网关会将其视为“直接访问网关本身”或广播,具体行为取决于设备实现。

常见误区:把事务标识符当成从站地址,或把单元标识符当成 TCP 连接编号。事务标识符只用于请求-响应匹配;单元标识符才用于网关后端寻址。

五、案例与排障复盘

某项目中,SCADA 通过 Modbus TCP 读取网关下挂的 3 台 RTU 仪表。现象是:第一台正常,第二、三台偶尔返回第一台的数据。排查发现,SCADA 在同一 TCP 连接上并发发送了 3 个请求,事务标识符分别设为 1、2、3,但网关固件对同一连接只按顺序处理,且响应中的事务标识符被错误地固定为 1。客户端未校验事务标识符,于是把后续响应都当成了第一台的。解决方法是升级网关固件,并在客户端启用事务标识符校验。

另一个高频问题是端口与防火墙。Modbus TCP 默认 502,但部分现场改为 1502 或 8502。若客户端仍按 502 连接,会表现为连接超时而非协议错误。排障顺序建议为:先 ping 通 IP,再 telnet 测端口,再用抓包工具确认 MBAP 头字段,最后检查单元标识符与后端从站地址是否一致。

工程检查清单:IP 可达 → 502 端口开放 → MBAP 长度字段正确 → 协议标识符为 0 → 事务标识符匹配 → 单元标识符对应后端从站 → 功能码与寄存器地址合法。

掌握 Modbus TCP 的关键,是把“以太网连接”和“Modbus 事务”分开看:TCP 负责把字节可靠送到,MBAP 负责标识这次事务和寻址后端,PDU 负责具体读写。三者各司其职,排障时才能逐层定位,而不是一遇到无响应就盲目改功能码或地址。