Modbus 全栈技术解析:TCP、RTU、ASCII、RTU over TCP

摘要 :本文以官方规范为基线,系统分析 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。

不过,厂商手册可能采用以下任意一种写法:

  1. 参考号:40001
  2. 零基偏移:0
  3. 一基序号:1
  4. 带区号的扩展写法:4x:0001

因此对接前必须确认三个问题:地址是否包含 4xxxx 前缀?起点是 0 还是 1?文档显示的是十进制还是十六进制? 不要只凭软件界面中的"PLC Address/Base 0/Base 1"猜测。

3.3 一个可发布、可维护的数据字典至少要有

  • 逻辑区、协议零基地址、占用寄存器数;
  • 数据类型:uint16int16uint32float32、位域、字符串等;
  • 字节序与跨寄存器字序;
  • 缩放系数、偏移、单位、有效范围、精度;
  • 读写权限、写入前置状态、写入是否持久化;
  • 无效值、缺测值、报警位和枚举定义;
  • 固件版本兼容范围。

四、请求/响应与异常模型

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 0A01 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 mst1.5 ≈ 1.719 mst3.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 在线路上传为字符 5A

典型结构:

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 是四个不同层次。正确接收逻辑应当:

  1. 将收到的字节追加到连接私有缓冲区;
  2. 缓冲区不足 7 字节时继续等待;
  3. 读取并校验 MBAP,尤其是 Protocol ID 与 Length;
  4. 等待缓存达到 6 + Length 字节;
  5. 取出一帧解析,剩余字节继续循环。

禁止用"本次 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 设备没有下游串行总线时,有的忽略该字段,有的要求固定为 0xFF0x01 或其他值。因此:

  • 网关场景:按映射表填写目标 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 推荐分层架构

建议拆成四层:

  1. 传输适配层:TCP 连接与缓存,或 UART/DMA、RS-485 收发方向与时序;
  2. ADU 层:MBAP、站号、CRC/LRC、长度和帧边界;
  3. PDU 层:功能码分派、请求长度、地址范围、数量和异常响应;
  4. 数据模型/业务层:寄存器快照、硬件 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 接收状态机

典型流程:

  1. t3.5 静默后等待首字符;
  2. 收到首字符后启动/刷新 t1.5 与帧结束定时;
  3. 发现帧内间隔违规、长度超限或 UART 错误时丢弃当前帧;
  4. 判定帧结束后检查最小长度、站号和 CRC;
  5. 非本站单播地址直接忽略;
  6. 处理 PDU,生成响应;
  7. 广播地址 0 仅处理允许的写操作,不发送响应;
  8. 等总线允许后切换发送方向,发完并确认移位寄存器空,再释放驱动器。

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 位数据的打包顺序

01020F,响应或请求的数据字节中,最低有效位对应起始地址。若读取 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;
  • 设备可能用 0xFFFF0x8000 或特定最大值表示无效/缺测,不能直接当真实测量值;
  • 单位可能随配置变化,例如 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 最有效的优化方法

  1. 把连续地址合并成块读,减少事务数;
  2. 数据字典按采样频率分区,高频量与低频配置分开;
  3. 不跨越设备不支持的空洞地址,必要时拆块;
  4. 根据实测分布设置超时,不用拍脑袋的极短值;
  5. 对多个原生 TCP 设备适度并发,对同一 RTU 总线保持串行化;
  6. 对网关设置每条下游总线独立队列与公平调度;
  7. 缓存静态信息,例如设备标识与版本,不必每周期读取。

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

两个寄存器依次为 0x40E20x0000。按 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/安全网关、审计和最小权限访问。


参考规范与延伸阅读

  1. Modbus Organization, MODBUS Application Protocol Specification V1.1b3
  2. Modbus Organization, MODBUS over Serial Line Specification and Implementation Guide V1.02
  3. Modbus Organization, MODBUS Messaging on TCP/IP Implementation Guide V1.0b
  4. Modbus Organization, MODBUS/TCP Security Protocol Specification V2.1
相关推荐
北域码匠1 天前
冒泡排序太慢?鸡尾酒排序双向优化,原生 C# 零第三方库完整代码
数据结构·排序算法·泛型·c# 算法·鸡尾酒排序·原生 c# 开发·冒泡排序优化·嵌入式算法
王二端茶倒水1 天前
一套可落地的无线运营方案,不能只管 AP,还要管用户、计费和运维
网络协议
162723816081 天前
EtherCAT 分布式时钟(DC)原理与配置实战:把多轴真正"对齐到同一时刻"
网络协议
王二端茶倒水2 天前
宽带无线项目,怎么从一次性交付变成长期运营收入?
网络协议
用户2530171996273 天前
第6篇:从技术到产品 — Ghost Proxifier 的设计哲学
网络协议
用户2530171996273 天前
第3篇:注入的艺术 — Ghost Proxifier 核心架构拆解
网络协议
王二端茶倒水4 天前
商业 WiFi 不是免费上网,而是门店数字化的入口
网络协议
Darling噜啦啦8 天前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试