摘要 :本文以官方规范为基线,系统分析 Modbus TCP、Modbus RTU、Modbus ASCII、Modbus RTU over TCP,以及通常被称为 Modbus TCP Slave、Modbus RTU Slave 的设备端实现。文中继续保留行业常用的"Master/Slave"关键词,方便检索和对照旧手册;在概念说明与新系统设计中优先使用官方近年推荐的 Client/Server(客户端/服务器)。

一、先用一张表分清所有"Modbus"
| 名称 | 典型承载 | ADU 结构 | 校验/边界 | 常见角色 | 最关键特征 |
|---|---|---|---|---|---|
| Modbus RTU | RS-485、RS-232 | 地址 + PDU + CRC | CRC-16;以静默时间分帧 | Master / Slave | 二进制、紧凑、工业现场最常见 |
| Modbus ASCII | RS-485、RS-232 | : + ASCII 十六进制内容 + LRC + CRLF |
LRC;显式起止符 | Master / Slave | 可读但报文更长、吞吐更低 |
| Modbus TCP | TCP/IP,以太网 | MBAP + PDU | TCP 可靠传输;MBAP Length 分帧 | Client / Server | 标准 TCP 变体,默认端口 502 |
| Modbus RTU over TCP | TCP/IP | 原始 RTU ADU 放入 TCP 字节流 | 常保留 CRC;分帧多为厂商约定 | Client / Server 或透明隧道端点 | 行业惯用称呼,不等于 Modbus TCP |
| Modbus TCP Server | TCP/IP | 接收 MBAP + PDU | 按事务 ID 回应 | Server(旧称 TCP Slave) | 可面对多个连接,需处理粘包、拆包与并发 |
| Modbus RTU Slave | 串行总线 | 接收地址 + PDU + CRC | 按站号与 t3.5 分帧 | Server/Slave | 半双工总线通常一次只处理一个事务 |
一句话判断:RTU、ASCII、TCP 描述的是"如何承载报文";Client/Server 或 Master/Slave 描述的是"谁发起请求、谁响应"。
其他容易遇到的名称
- Modbus Security:标准化安全扩展,在 Modbus TCP 之上使用 TLS 与证书机制,本文在安全章节说明;
- Modbus Plus:面向存量工业系统的专用网络,不是 RTU/TCP 的简单别名;新项目通常通过专用接口或网关兼容;
- Modbus UDP:部分厂商把 Modbus 语义放在 UDP 上,但基础 Modbus TCP 规范并未把它定义为与 TCP 等价的统一替代,需要逐项确认分帧、重试和重复报文处理;
- "Modbus 转 MQTT/OPC UA":描述的是网关上层数据转换,不是新的 Modbus ADU 格式。
二、共同的协议核心:PDU 与 ADU
2.1 PDU:决定"要做什么"
所有主流 Modbus 变体都共享 Modbus PDU(Protocol Data Unit):
text
PDU = Function Code(1 字节) + Data(0~252 字节)
PDU 最大 253 字节。功能码说明操作类型,例如 0x03 读取保持寄存器、0x06 写单个保持寄存器;Data 则承载起始地址、数量、字节数、值或异常码。
2.2 ADU:决定"如何送达"
ADU(Application Data Unit)是在 PDU 外增加当前链路需要的寻址、分帧和校验信息:
- 串行 RTU ADU:
地址 1 B + PDU ≤253 B + CRC 2 B,最大 256 字节; - Modbus TCP ADU:
MBAP 7 B + PDU ≤253 B,最大 260 字节; - ASCII ADU:把地址、PDU、LRC 的每个二进制字节编码为两个十六进制字符,再加
:与 CRLF。

这带来一个重要工程结论:PDU 处理器可以复用,但串口接收状态机、TCP 缓冲区、地址字段、CRC/LRC 和并发策略不能直接复用。
三、数据模型、地址与数据字典
3.1 四类逻辑数据区
| 数据区 | 传统参考号 | 元素宽度 | 访问属性 | 常用功能码 |
|---|---|---|---|---|
| Coil(线圈) | 0xxxx | 1 bit | 读写 | 01、05、0F |
| Discrete Input(离散输入) | 1xxxx | 1 bit | 只读 | 02 |
| Input Register(输入寄存器) | 3xxxx | 16 bit | 只读 | 04 |
| Holding Register(保持寄存器) | 4xxxx | 16 bit | 读写 | 03、06、10、16、17 |
这些"区"是逻辑模型,不必与 MCU 的真实内存布局一一对应。服务器可以把寄存器映射到 ADC、PLC 变量、数据库字段或计算结果。

3.2 最常见的 40001 偏移陷阱
协议报文中的地址是 16 位零基偏移 ,范围 0x0000~0xFFFF。旧式手册中的 40001 往往表示"第一个保持寄存器",真正放入 0x03 请求的起始地址通常是 0x0000,而不是十进制 40001,也不是 1。
不过,厂商手册可能采用以下任意一种写法:
- 参考号:
40001; - 零基偏移:
0; - 一基序号:
1; - 带区号的扩展写法:
4x:0001。
因此对接前必须确认三个问题:地址是否包含 4xxxx 前缀?起点是 0 还是 1?文档显示的是十进制还是十六进制? 不要只凭软件界面中的"PLC Address/Base 0/Base 1"猜测。
3.3 一个可发布、可维护的数据字典至少要有
- 逻辑区、协议零基地址、占用寄存器数;
- 数据类型:
uint16、int16、uint32、float32、位域、字符串等; - 字节序与跨寄存器字序;
- 缩放系数、偏移、单位、有效范围、精度;
- 读写权限、写入前置状态、写入是否持久化;
- 无效值、缺测值、报警位和枚举定义;
- 固件版本兼容范围。
四、请求/响应与异常模型
Modbus 是请求/响应协议。客户端或主站发起请求,目标服务器或从站处理后返回正常响应或异常响应。标准 Modbus 并不定义设备任意时刻主动向客户端推送数据;所谓"主动上报"通常是厂商私有协议、反向连接或上层消息系统。

正常响应通常回显原功能码;异常响应把原功能码最高位置 1,也就是 Function Code + 0x80,并附加 1 字节异常码。例如,对 0x03 的非法地址请求可能返回:
text
83 02
│ └─ 0x02:非法数据地址
└──── 0x03 + 0x80:异常响应
"收到异常响应"与"超时"意义完全不同:前者表明链路、分帧和基本寻址大概率已经成功;后者还可能发生在物理层、端口、串口参数、站号、CRC、网关路由等任何位置。
五、Modbus RTU:现场总线的主力
5.1 RTU 帧格式
text
┌──────────┬──────────────┬──────────────────┬────────────┐
│ Address │ Function Code│ Data │ CRC-16 │
│ 1 byte │ 1 byte │ 0...252 bytes │ 2 bytes │
└──────────┴──────────────┴──────────────────┴────────────┘
- 地址
1~247:常用单播从站地址; - 地址
0:串行广播地址,从站可执行写请求,但不得返回响应; - 地址
248~255:规范保留; - CRC 覆盖地址、功能码和数据,初值通常为
0xFFFF,多项式的反转表示为0xA001; - CRC 在线路上 低字节先发送,高字节后发送。

示例:请求站号 1 从零基地址 0 开始读取 2 个保持寄存器:
text
01 03 00 00 00 02 C4 0B
字段解释:
| 字节 | 含义 |
|---|---|
01 |
从站地址 1 |
03 |
读保持寄存器 |
00 00 |
起始地址 0 |
00 02 |
数量 2 |
C4 0B |
CRC Low、CRC High |
正常响应可能为:
text
01 03 04 00 0A 01 F4 xx xx
其中 04 是后续数据字节数,00 0A 与 01 F4 是两个寄存器值,末尾为响应 CRC。
5.2 RTU 的帧边界:t1.5 与 t3.5
RTU 没有起止字符,接收器通过静默时间判断边界:
- 一帧开始前,总线应至少静默
t3.5; - 帧内两个字符之间的间隔不应超过
t1.5; - 若帧内间隔超过
t1.5,接收端应把当前帧判为无效; - 帧结束后至少经过
t3.5才能开始下一帧。

字符时间不是简单的 1 / 波特率。以常见 8E1 为例,一个字符包含 1 起始位、8 数据位、1 校验位、1 停止位,共 11 bit:
text
Tchar = 11 / BaudRate
在 9600 bit/s 下,Tchar ≈ 1.146 ms,t1.5 ≈ 1.719 ms,t3.5 ≈ 4.010 ms。串行线规范建议:波特率大于 19200 bit/s 时,分别采用固定的 750 μs 与 1.750 ms,以减轻高频定时负担。
5.3 串口参数必须全部一致
两端要同时匹配波特率、数据位、奇偶校验和停止位。例如"9600 8E1"与"9600 8N1"不是同一配置。许多设备在无校验时使用 2 个停止位以维持 11 bit 字符长度,但不能把它当作所有设备的默认值;最终以设备手册为准。
六、Modbus ASCII:仍然有用的可读串行格式
ASCII 模式以冒号 : 开始,以回车换行 CR LF 结束。地址、功能码、数据和 LRC 的每个二进制字节都编码为两个大写十六进制字符。例如 0x5A 在线路上传为字符 5、A。

典型结构:
text
: 01 03 0000 0002 FA CR LF
真正在线路上连续出现的是 ASCII 字符,不包含空格。LRC 的经典计算方式是:对地址、功能码和数据的二进制字节求和,取其二进制补码;不把冒号、CRLF 和 LRC 自身纳入计算。
ASCII 的优势是边界显式、抓取后可直接阅读,对不稳定或延迟较大的串行链路更宽容;代价是同样一个二进制字节需要两个字符,报文显著变长,吞吐通常低于 RTU。新项目一般优先 RTU,只有在存量设备、人工诊断或特殊链路明确需要时选择 ASCII。
七、Modbus TCP:不是"RTU 去掉 CRC"这么简单
7.1 MBAP 头格式
Modbus TCP ADU 由 7 字节 MBAP Header 与 PDU 组成:
| 字段 | 长度 | 作用 |
|---|---|---|
| Transaction Identifier | 2 B | 客户端生成,服务器在响应中回显,用于事务匹配 |
| Protocol Identifier | 2 B | Modbus 通常为 0x0000 |
| Length | 2 B | 其后字节数,即 Unit Identifier + PDU |
| Unit Identifier | 1 B | 网关后串行单元路由;原生 TCP 设备行为看手册 |
| Function + Data | 1...253 B | Modbus PDU |

容易出错的是 Length:它不包含事务 ID、协议 ID 和 Length 字段自身。整帧应有 6 + Length 字节。若 PDU 为 5 字节,则 Length 为 1 + 5 = 6,完整 TCP ADU 为 12 字节。
7.2 TCP 是字节流,没有"报文包边界"
一次 send() 可能被拆成多次 recv();多次 send() 也可能在一次 recv() 中粘在一起。以太网帧、IP 包、TCP 段和应用层 Modbus ADU 是四个不同层次。正确接收逻辑应当:
- 将收到的字节追加到连接私有缓冲区;
- 缓冲区不足 7 字节时继续等待;
- 读取并校验 MBAP,尤其是 Protocol ID 与 Length;
- 等待缓存达到
6 + Length字节; - 取出一帧解析,剩余字节继续循环。

禁止用"本次 recv 返回了多少字节""一个 TCP 包"或"短暂无数据"作为 Modbus TCP 分帧依据。对于异常 Length,要在分配内存前限制上限,否则不仅是兼容性问题,也会演变为资源耗尽风险。
7.3 事务标识符与并发
Transaction Identifier 由客户端分配,服务器应在响应中原样回显。一个健壮的客户端维护"连接内未完成事务表",用事务 ID 将响应映射回请求,并避免在旧事务完成前复用相同 ID。
理论上客户端可以在同一 TCP 连接中保留多个未完成请求,但实际设备差异很大:有的服务器可并发处理,有的只会顺序处理,有的甚至要求"一问一答后再问"。工程上应先查询设备的并发能力;未知时从每连接 1 个在途请求开始,逐步压测,而不是把协议允许当作设备一定支持。
7.4 连接生命周期
推荐持久连接,避免每次轮询都执行 TCP 三次握手。客户端至少要处理:
- 连接超时、响应超时、对端关闭、RST 和半开连接;
- 指数退避重连,叠加少量随机抖动,避免大量客户端同步重连;
- 每个连接独立的接收缓存和事务表;
- 最大空闲时间、保活策略和设备端连接数上限;
- 重连后清空旧缓存与未完成事务,不能把旧响应匹配到新会话。
TCP 可靠只代表字节按序、无重复地到达连接对端,不代表设备业务执行成功,更不代表写操作能够安全地盲目重试。若连接在写请求发出后断开,客户端可能无法判断服务器究竟"未执行、已执行还是执行中"。关键写操作应结合幂等设计、状态回读或业务序列号解决。
7.5 Unit Identifier 不能一概而论
跨 TCP/RTU 网关时,Unit ID 通常用于选择网关后面的串行站号。原生 Modbus TCP 设备没有下游串行总线时,有的忽略该字段,有的要求固定为 0xFF、0x01 或其他值。因此:
- 网关场景:按映射表填写目标 RTU 站号;
- 原生 TCP 场景:按设备手册;
- 不要把 RTU 的地址 0 广播规则机械套到所有原生 Modbus TCP 服务器;
- 广播能否穿过网关、是否返回响应属于设备策略,必须实测。
八、Modbus RTU over TCP:最容易被误解的变体
"Modbus RTU over TCP"通常指把完整 RTU ADU------包括串行地址和 CRC------原样放入 TCP 字节流。它经常出现在串口服务器、4G DTU、透明传输模块和旧设备联网改造中,但并没有像 Modbus TCP 那样形成统一的 MBAP 封装标准。

8.1 三种常被混叫的模式
| 模式 | TCP 中传什么 | CRC | 端口 | 边界识别 |
|---|---|---|---|---|
| 原生 Modbus TCP | MBAP + PDU | 无 RTU CRC | 通常 502 | MBAP Length |
| 透明 RTU over TCP | 地址 + PDU + CRC | 保留 | 502、2000、4001、厂商自定义均可能 | 厂商约定、帧长推导、超时或组合方法 |
| Modbus TCP ↔ RTU 网关 | TCP 侧 MBAP + PDU;串行侧重建 RTU ADU | TCP 侧无,RTU 侧有 | TCP 侧常为 502 | 两侧分别按各自规范 |
8.2 为什么"都用 TCP"仍然完全不兼容
假设 RTU 请求为:
text
01 03 00 00 00 02 C4 0B
透明 RTU over TCP 可能直接发送这 8 字节。标准 Modbus TCP 请求则可能是:
text
00 01 00 00 00 06 01 03 00 00 00 02
└TID┘ └PID┘ └Len┘ UI └────PDU────┘
两者从首字节开始就采用不同解析方式。把 RTU 帧发到标准 502 服务器,或把 MBAP 帧发给透明隧道端点,TCP 连接可能保持成功,但应用层不会得到有效响应。
8.3 RTU over TCP 的 TCP 分帧难题
RTU 在串行线上依靠 t3.5 判定边界;进入 TCP 后,网络延迟和 TCP 缓冲会破坏这种时间语义。TCP 也不会保留发送调用的边界。因此实现前必须明确:
- 是否固定一条 TCP 连接只承载一个串行总线;
- 是否通过功能码推导期望帧长;
- 是否允许一个 TCP 读取中包含多帧;
- 超时多久才认为一帧结束;
- CRC 是否保留且如何处理错误;
- 是否存在厂商自定义长度头、转义或心跳。
如果双方都支持标准 Modbus TCP,优先选标准协议;如果一端只有 RTU,优先使用明确执行协议转换的网关。透明 RTU over TCP 应被视为一个需要写入接口控制文档的"具体厂商协议配置",而不是模糊的通用选项。
8.4 网关下游或独立 RTU 总线的物理层重点
Modbus RTU 经常运行在 RS-485 上,但 RTU 是应用/链路层报文格式,不等于 RS-485。RS-485 只规定差分电气特性。可靠现场通常需要:
- 采用主干式菊花链,减少星形和过长支线;
- 仅在总线物理两端端接,常见端接值为 120 Ω;
- 使用合适的偏置,避免所有驱动器释放后总线漂浮;
- 核对厂商 A/B、D+/D− 标法,因为命名并非始终一致;
- 处理参考地、屏蔽、隔离、浪涌与接地环路;
- 避开变频器、电机动力线等强干扰源,必要时降低波特率。

九、Modbus TCP Slave 与 Modbus RTU Slave 的设备端实现
9.1 术语与职责
- Modbus TCP Server:监听 TCP 连接、解析 MBAP、处理 PDU、回显事务 ID。旧资料常叫 Modbus TCP Slave;
- Modbus RTU Slave/Server:监听串行总线,仅响应本站地址或支持的广播请求,校验 CRC 并遵守时序;
- Client/Master:决定何时发请求、轮询哪些点、超时多久、如何重试。
Server/Slave 并不是"某一种 Modbus 格式",而是设备端角色。一个网关甚至可以同时扮演 TCP Server 和 RTU Master:上游接受 SCADA 的 TCP 请求,下游代其轮询 RTU 从站。
9.2 推荐分层架构

建议拆成四层:
- 传输适配层:TCP 连接与缓存,或 UART/DMA、RS-485 收发方向与时序;
- ADU 层:MBAP、站号、CRC/LRC、长度和帧边界;
- PDU 层:功能码分派、请求长度、地址范围、数量和异常响应;
- 数据模型/业务层:寄存器快照、硬件 I/O、权限、持久化与审计。
这样做的价值是:同一套 PDU 与数据模型可被 TCP、RTU 甚至单元测试共用;传输层错误不会污染业务逻辑。
9.3 TCP Server 接收主循环
伪代码如下:
text
on_bytes(connection, bytes):
connection.buffer.append(bytes)
while true:
if buffer.size < 7:
return
tid = u16_be(buffer[0:2])
pid = u16_be(buffer[2:4])
length = u16_be(buffer[4:6])
unit_id = buffer[6]
if pid != 0 or length < 2 or length > 254:
close_or_reject(connection)
return
adu_size = 6 + length
if buffer.size < adu_size:
return
adu = buffer.take(adu_size)
pdu = adu[7:]
response_pdu = handle_pdu(unit_id, pdu)
send(tid + pid + response_length + unit_id + response_pdu)
长度校验必须先于内存分配和字段访问。length 至少要容纳 Unit ID 与功能码,因此正常请求通常不小于 2;最大值不应超过 254。
9.4 RTU Slave 接收状态机
典型流程:
- t3.5 静默后等待首字符;
- 收到首字符后启动/刷新 t1.5 与帧结束定时;
- 发现帧内间隔违规、长度超限或 UART 错误时丢弃当前帧;
- 判定帧结束后检查最小长度、站号和 CRC;
- 非本站单播地址直接忽略;
- 处理 PDU,生成响应;
- 广播地址 0 仅处理允许的写操作,不发送响应;
- 等总线允许后切换发送方向,发完并确认移位寄存器空,再释放驱动器。
RS-485 的"发送完成"不能只看软件发送缓冲区为空,否则最后几个位仍在 UART 移位寄存器中时就关闭 DE,会截断 CRC。
9.5 数据一致性与实时性
一次读取多个寄存器时,服务器最好从同一业务快照取值。若温度高字、低字或 32 位计数器在读到一半时更新,客户端会得到撕裂数据。常用方法包括:
- 短临界区复制寄存器快照;
- 双缓冲并原子切换版本;
- 用业务锁保护多寄存器复合值;
- 对慢速硬件采用后台采集,协议线程只读缓存;
- 写操作进入队列,明确"响应时已接受"还是"响应时已实际落地"。
协议线程不应长时间阻塞在 I²C、Flash 擦写、数据库或云端接口上,否则会拖垮所有客户端事务。
9.6 广播、写操作与幂等性
RTU 广播没有响应,主站无法从协议层知道每个从站是否执行成功。只应把广播用于明确允许、风险可控、结果可后续读取验证的写操作。涉及启停、固件更新、地址修改和安全联锁时,应谨慎甚至禁用广播。
写单寄存器并不天然幂等。写入"目标值"通常可重复;写入"执行一次""计数加一""清除并启动"等命令寄存器可能重复触发。断线重试前必须理解业务语义。
十、功能码:能力边界,而不是寄存器说明书

10.1 常用公共功能码
| 十六进制 | 名称 | 典型请求数量上限* | 说明 |
|---|---|---|---|
01 |
读线圈 | 2000 bit | 响应按位打包 |
02 |
读离散输入 | 2000 bit | 只读位 |
03 |
读保持寄存器 | 125 register | 最常用读寄存器功能 |
04 |
读输入寄存器 | 125 register | 只读寄存器 |
05 |
写单线圈 | 1 bit | ON 为 FF00,OFF 为 0000 |
06 |
写单保持寄存器 | 1 register | 正常响应通常回显请求 |
0F |
写多个线圈 | 1968 bit | 请求中带字节数和位数据 |
10 |
写多个保持寄存器 | 123 register | 请求 PDU 容量限制 |
16 |
屏蔽写寄存器 | 1 register | 通过 AND/OR 掩码修改位 |
17 |
读/写多个寄存器 | 读 125、写 121 | 一个事务组合读写 |
2B/0E |
读取设备标识 | 分段 | 获取厂商、产品、版本等 |
* 表中为应用协议规范的常见上限,具体设备可能只支持更小数量或更少功能码。
10.2 位数据的打包顺序
对 01、02、0F,响应或请求的数据字节中,最低有效位对应起始地址。若读取 10 个线圈,需要 2 个数据字节;最后一个字节中未使用的高位填 0。位序错误会表现为"每 8 个点颠倒",这与寄存器字节序不是同一问题。
10.3 功能码白名单
服务器只应启用产品真正需要的功能码。即便功能码受支持,也要再次校验:
- 请求 PDU 长度是否与功能码一致;
- 数量是否为 0、是否超规范或设备上限;
start + quantity是否溢出 16 位地址空间;- 数据字节数是否与数量匹配;
- 地址范围是否跨越未映射区或权限边界;
- 当前设备状态是否允许写入;
- 写值是否符合枚举、范围和联锁条件。
十一、寄存器字节序、字序、缩放与字符串
11.1 16 位寄存器内的字节序
Modbus 报文中的 16 位字段和寄存器值通常按高字节在前传输。例如寄存器值 0x1234 在线路数据区中为 12 34。这与 RTU CRC 的低字节先发送是两个不同规则,不能混为一谈。
11.2 32/64 位类型没有统一跨寄存器字序
Modbus 基础数据模型只定义 16 位寄存器。32 位整数、浮点数、64 位计数器由多个寄存器组合时,寄存器顺序属于厂商数据表示约定。常见 32 位 0x12345678 排列包括:

| 常见代号 | 线路字节 | 说明 |
|---|---|---|
| ABCD | 12 34 56 78 |
寄存器内大端,先高字 |
| CDAB | 56 78 12 34 |
交换两个 16 位字,现场很常见 |
| BADC | 34 12 78 56 |
每个寄存器内交换字节 |
| DCBA | 78 56 34 12 |
整体反序 |
"Big Endian/Little Endian"在许多软件界面中含义不够明确,最好直接用 ABCD 字节序列描述,并用一个已知常量验证。
11.3 有符号数、浮点数与缩放
int16常以二进制补码解释,但这是数据字典约定;- IEEE 754
float32需要 2 个寄存器,先确定字序再解释浮点; - 很多设备不用浮点,而是把温度 25.3 ℃ 存为整数 253,并规定缩放系数 0.1;
- 设备可能用
0xFFFF、0x8000或特定最大值表示无效/缺测,不能直接当真实测量值; - 单位可能随配置变化,例如 Pa/kPa、℃/℉,客户端必须绑定版本和单位元数据。
11.4 字符串
字符串常以两个 ASCII 字符占一个寄存器,但字符先后顺序、终止符、定长填充和编码并未由基础 Modbus 统一规定。中文字符串更不宜凭猜测解析,必须拿到厂商的编码说明。
十二、异常码、超时与错误分层

12.1 常见异常码
| 异常码 | 名称 | 含义与常见原因 |
|---|---|---|
01 |
Illegal Function | 服务器不支持该功能码,或当前状态不允许 |
02 |
Illegal Data Address | 起始地址/范围未映射,或访问越界 |
03 |
Illegal Data Value | 数量、字节数、枚举值或请求结构非法 |
04 |
Server Device Failure | 服务器处理时发生不可恢复错误 |
05 |
Acknowledge | 已接受但需要较长时间完成,常见于编程类操作 |
06 |
Server Device Busy | 服务器忙,客户端稍后重试 |
08 |
Memory Parity Error | 存储一致性/校验错误,较少见 |
0A |
Gateway Path Unavailable | 网关到目标路径不可用 |
0B |
Gateway Target Failed to Respond | 网关未收到下游目标响应 |
服务器应选择最贴近事实的异常码,不要对所有错误都返回 04。客户端则应区分永久错误与暂态错误:01/02/03 通常需要修正请求,不应高速重试;05/06/0B 可在受控退避后重试。
12.2 不会生成 Modbus 异常码的错误
以下问题通常表现为丢帧、断开或客户端超时,而不是异常响应:
- RTU CRC 错、ASCII LRC 错;
- 帧太短、MBAP Length 非法、Protocol ID 错;
- RTU 站号不是本站;
- 串口参数不匹配;
- TCP 端口、路由或防火墙错误;
- 请求发给了错误协议模式;
- 设备断电、总线冲突或网关离线。
12.3 超时与重试策略
推荐分别设置连接超时、单事务响应超时、总操作截止时间,不要只用一个"万能超时"。重试应满足:
- 只对明确可重试的错误重试;
- 使用退避和抖动,避免固定间隔重试风暴;
- 限制次数,并把最后失败原因暴露给上层;
- 写操作重试前确认幂等性;
- RTU 主站重试前留足总线恢复与 t3.5;
- 网关场景把上游超时设得大于下游超时与排队时间之和。
十三、性能、轮询周期与容量规划

13.1 RTU 事务时间粗估
对串口,发送时间可用:
text
发送时间 = 字符数 × 每字符位数 / 波特率
以 9600 bit/s、8E1(11 bit/字符)为例:8 字节请求约 9.17 ms,9 字节响应约 10.31 ms,再加设备处理和必要静默,一次事务很容易超过 25~30 ms。理论每秒事务数不等于可用扫描率,还要考虑:
- 多从站轮询;
- 不同功能码的响应长度;
- 设备处理时间的 P95/P99,而不是平均值;
- 超时与重试;
- 主站调度、串口驱动和 USB 转换器抖动。
13.2 TCP 性能的主要变量
Modbus TCP 没有 RTU 的串行发送时间和 t3.5,但仍受网络往返、服务器处理、连接数、在途事务、网关排队与下游串口限制影响。千兆以太网并不能让后面的 9600 bit/s RTU 总线变快。
13.3 最有效的优化方法
- 把连续地址合并成块读,减少事务数;
- 数据字典按采样频率分区,高频量与低频配置分开;
- 不跨越设备不支持的空洞地址,必要时拆块;
- 根据实测分布设置超时,不用拍脑袋的极短值;
- 对多个原生 TCP 设备适度并发,对同一 RTU 总线保持串行化;
- 对网关设置每条下游总线独立队列与公平调度;
- 缓存静态信息,例如设备标识与版本,不必每周期读取。
13.4 不要为"少发几个字节"牺牲可维护性
读取 10 个连续寄存器通常比发 10 个单寄存器请求高效得多;但把所有数据塞进巨大连续区也会带来版本兼容和更新一致性问题。合理做法是按业务一致性、刷新频率和权限设计若干稳定的数据块。
十四、协议选型:从约束出发

14.1 选 RTU 的场景
- 存量仪表只提供串口;
- 多点、低速、距离较长,现场已有 RS-485 总线;
- 数据量和扫描频率不高;
- 成本、功耗、实现复杂度受限;
- 可以接受单主站顺序轮询。
14.2 选 ASCII 的场景
- 必须兼容既有 ASCII 设备;
- 人工观察线路字符很重要;
- 链路字符间隔较大而吞吐要求低;
- 项目明确接受报文长度和效率代价。
新设计若没有这些约束,通常不把 ASCII 作为第一选择。
14.3 选 Modbus TCP 的场景
- 设备原生支持以太网;
- 需要与 SCADA、边缘计算或 IT 网络集成;
- 多设备、多连接、较高吞吐;
- 希望使用标准抓包工具和成熟网络基础设施;
- 能够进行工业网络分区和安全控制。
14.4 选网关而非透明 RTU over TCP 的场景
当上游系统讲标准 Modbus TCP、下游设备讲 RTU 时,协议网关通常更清晰:它显式完成 MBAP 与 RTU 地址/CRC/时序转换。透明 RTU over TCP 只应在两端软件明确支持同一封装、且必须保留原始 RTU 语义时使用。
14.5 对比矩阵
| 维度 | RTU | ASCII | Modbus TCP | RTU over TCP |
|---|---|---|---|---|
| 标准化程度 | 高 | 高 | 高 | 依厂商,名称不统一 |
| 编码效率 | 高 | 低 | 高 | 高 |
| 网络复用 | 低 | 低 | 高 | 高,但互操作性较弱 |
| 调试可读性 | 中 | 高 | 高(Wireshark) | 中 |
| 多客户端 | 通常不适用 | 通常不适用 | 可支持 | 依实现 |
| 校验 | CRC-16 | LRC | TCP 校验;无 RTU CRC | 常保留 CRC |
| 帧边界 | t3.5 | : 与 CRLF |
MBAP Length | 厂商约定 |
| 最适合 | 现场串行总线 | 存量/特殊链路 | 原生以太网 | 透明串口联网改造 |
十五、安全:经典 Modbus 不能裸奔上网
经典 Modbus TCP 的设计目标是互操作与简单,不提供应用层身份认证、授权、机密性和防重放。任何能连到端口且通过网络策略的人,都可能尝试读写寄存器。因此"端口不是 502"或"攻击者不知道寄存器表"都不是安全控制。

15.1 分层防护基线
- 资产与暴露面:清点设备、固件、端口、角色、下游总线和数据字典;禁止公网直连;
- 网络分区:企业 IT、工业 DMZ、控制区、单元区分层;跨区默认拒绝;
- 访问控制:只允许指定源、目标和端口;远程访问走 VPN/堡垒机并启用 MFA;
- 协议白名单:只放行业务需要的功能码、Unit ID、地址范围和读写方向;
- 最小权限:监控账号只读;配置和控制写入走独立路径;
- 监测审计:记录连接、功能码、地址、写值、异常码和配置变更;检测扫描、暴力轮询、异常写和重连风暴;
- 韧性:配置备份、离线恢复、冗余、失效安全和应急断开方案。
15.2 加密与 Modbus Security
Modbus Security 在经典 Modbus TCP 之上引入 TLS 和基于证书的安全机制,规范分配了 TCP 端口 802。是否采用取决于设备与平台支持度;现实中也常用工业 VPN、TLS 代理或安全网关保护传统设备。无论采用哪种加密,网络分区、最小权限和协议白名单仍然必要,因为加密不能阻止已授权端点发送危险写命令。
15.3 安全不是以可用性为代价的"加一层防火墙"
控制系统还要考虑实时性与故障模式。上线前应测试证书过期、时间不同步、网关重启、网络切换、规则更新与安全设备旁路失败。所有安全设备都应纳入容量和高可用设计。
十六、工程排障:沿协议栈逐层收敛

16.1 第一层:物理与连接
RTU 检查供电、共地/隔离、A/B 线序、端接、偏置、屏蔽、支线、干扰和收发方向;TCP 检查 IP、掩码、网关、ARP、路由、防火墙、端口监听和连接数上限。
不要因为串口指示灯闪烁就认为协议正确,也不要因为 TCP 三次握手成功就认为应用层兼容。
16.2 第二层:链路参数
RTU/ASCII 核对波特率、数据位、奇偶校验、停止位、模式、站号和响应延迟;TCP 核对标准 TCP、RTU over TCP 或厂商私有网关模式,并记录断线与重连时序。
16.3 第三层:ADU 与分帧
- RTU:帧间隔、帧内间隔、CRC、地址和最小长度;
- ASCII:冒号、CRLF、偶数字符、十六进制合法性和 LRC;
- TCP:MBAP Protocol ID、Length、完整 ADU 字节数、粘包拆包;
- RTU over TCP:CRC 是否保留、边界策略和一次读取多帧的处理。
16.4 第四层:协议语义
核对功能码、零基地址、数量、数据字节数、Unit ID、广播行为和设备支持范围。收到异常码后优先解释异常码,而不是继续改串口参数。
16.5 第五层:数据解释
通信完全正常但数值错误时,再检查字序、类型、缩放、符号、单位、位域、无效值和固件版本。将原始寄存器值与工程值同时记录,排障效率会高很多。
16.6 一个高质量最小复现包
提交给设备厂商或软件团队时,至少包含:
text
时间戳(含时区与毫秒)
方向(Client→Server / Server→Client)
完整原始十六进制
串口全部参数,或 TCP 两端 IP/端口
协议模式与 Unit ID/站号
期望结果、实际结果、超时值与重试次数
设备型号、固件、网关配置与接线图
"读不到 40001"不是可复现问题;"2026-06-23 10:00:01.152+08:00,9600 8E1,站号 1,发送 01 03 00 00 00 02 C4 0B,500 ms 内无任何返回,同线另一设备正常"才是。
十七、端到端示例:读取并解释一个 32 位温度值
假设设备数据字典规定:
| 项目 | 定义 |
|---|---|
| 数据区 | Holding Register |
| 手册参考号 | 40101~40102 |
| 协议零基地址 | 100(0x0064) |
| 类型 | int32 |
| 字序 | CDAB(低字寄存器在前) |
| 缩放 | ×0.001 ℃ |
| 访问 | 只读 |
17.1 RTU 请求
站号 1,功能码 03,起始地址 100,数量 2:
text
01 03 00 64 00 02 CRC_L CRC_H
17.2 TCP 请求 PDU
PDU 相同:
text
03 00 64 00 02
外层再添加 MBAP。若事务 ID 为 0x0012、Unit ID 为 1:
text
00 12 00 00 00 06 01 03 00 64 00 02
17.3 解析响应
若数据字节为:
text
04 40 E2 00 00
两个寄存器依次为 0x40E2、0x0000。按 CDAB 交换 16 位字后得到原始 32 位 0x000040E2 = 16610,再乘 0.001,工程值为 16.610 ℃。如果误按 ABCD 直接拼接,得到的数值会完全不同。
这个例子说明:链路、功能码和地址都正确,只能证明"读到了四个字节";数据字典才决定这四个字节是什么意思。
十八、常见问题速答
Q1:Modbus TCP 还需要 CRC 吗?
标准 Modbus TCP 不携带 RTU CRC,使用 MBAP 头与 TCP 的可靠传输机制。若 TCP 负载末尾仍有 RTU CRC,很可能是 RTU over TCP 或厂商私有模式。
Q2:端口 502 就一定是 Modbus TCP 吗?
不一定。502 是标准 Modbus TCP 的默认端口,但设备可改端口,也可能在 502 上配置透明 RTU。必须检查首帧结构与设备配置。
Q3:为什么连接成功但没有响应?
最常见原因是协议模式不匹配、Unit ID/站号错误、MBAP Length 错、地址越界、服务器只允许一个连接、或请求已到达但设备处理超时。
Q4:40001 应发送 40001 还是 0?
多数情况下发送零基地址 0,但厂商软件和手册可能采用不同显示基准。以数据字典与已知点位验证为准。
Q5:RTU 可以有多个主站吗?
经典单总线模型通常只有一个主站。多个未经协调的主站会造成碰撞。若业务需要冗余主站,必须由专门仲裁、切换机制或设备厂商方案保证同一时刻只有一个有效发起者。
Q6:TCP Server 可以同时服务多少客户端?
协议没有替具体产品承诺连接数。上限取决于设备内存、线程/事件模型、业务 I/O 和厂商限制,应通过规格与压力测试确认。
Q7:读到的浮点数错误,是不是字节序问题?
很可能,但也可能是地址偏移、数据类型、缩放或单位错误。先保留四个原始字节,用 ABCD/CDAB/BADC/DCBA 与已知值对照,不要连续切换选项碰运气。
Q8:可以把 Modbus TCP 直接暴露到公网吗?
不应这样做。经典 Modbus TCP 缺少内建认证与加密,应放在分区网络中,通过白名单、防火墙、VPN/安全网关、审计和最小权限访问。
参考规范与延伸阅读
- Modbus Organization, MODBUS Application Protocol Specification V1.1b3
- Modbus Organization, MODBUS over Serial Line Specification and Implementation Guide V1.02
- Modbus Organization, MODBUS Messaging on TCP/IP Implementation Guide V1.0b
- Modbus Organization, MODBUS/TCP Security Protocol Specification V2.1