01 · 速查手册(附录)
本篇是「工业设备数据采集」知识体系第 07 篇(附录)的第 1 篇。定位:一张纸贴在工位上就能用的纯速查表,不解释原理。
所有数字均已在 01~05 篇逐条核对过;凡两库实现不一致、或需要现场确认的地方,一律标注 (需实测确认)。
本篇目录
| 章节 | 内容 |
|---|---|
| 1 | 常用端口速查 |
| 2 | Modbus 功能码速查 |
| 3 | Modbus 异常码表 |
| 4 | Modbus 单次读写上限 |
| 5 | S7 区域码表 |
| 6 | S7 TSAP / rack-slot 对照表 |
| 7 | S7 错误码表 |
| 8 | S7 PDU 与单次读取上限 |
| 9 | MC 软元件编码表 |
| 10 | MC 子命令表 |
| 11 | MC 结束代码(错误码)表 |
| 12 | 三协议单次读取上限横向对比 |
| 13 | 字节序四顺序速查 |
| 14 | Java Socket 选项速查 |
| 15 | Wireshark 过滤表达式速查 |
| 16 | TCP 状态排查命令速查 |
| 17 | 两库 Maven 坐标与快速上手 |
第 1 章 常用端口速查
| 端口 | 协议 / 服务 | 说明 |
|---|---|---|
| 502 | Modbus TCP | Modbus 官方端口;网关/仪表常用 |
| 102 | S7comm(ISO-on-TCP / RFC1006) | 西门子 S7-200/300/400/1200/1500 |
| 6000 | MC / SLMP(TCP) | iot-communication 的 MELSEC_PORT 常量,源码确认 |
| 5000 / 5001 | MC(部分 Q 系列 E71 / FX5U 配置) | 需实测确认 |
| 1025~1028 | MC / SLMP(部分配置示例) | 需实测确认 |
| 9600 | S7-200 PPI / 三菱串行 MC | 串行通信波特率默认值 |
| 4840 | OPC UA | OPC Foundation 注册端口 |
| 1883 | MQTT(明文) | |
| 8883 | MQTT over TLS | 生产环境应使用 |
| 8083 | MQTT over WebSocket | |
| 8084 | MQTT over WSS | |
| 80 / 443 | HTTP / HTTPS | 平台 API |
| 3306 | MySQL | |
| 6030 / 6041 | TDengine(TCP / REST) | 需实测确认 |
| 554 | RTSP | 视频流 |
| 4843 | OPC UA over HTTPS | (需实测确认) |
防火墙提醒:出问题时 90% 是端口没开,不是协议错。先
telnet ip port或Test-NetConnection验通。
第 2 章 Modbus 功能码速查
2.1 主表
| 功能码 | 名称 | 对象 | 请求 PDU 数据 | 响应 PDU 数据 |
|---|---|---|---|---|
| 0x01 | Read Coils 读线圈 | 位 | 起始地址(2) + 数量(2) | 字节数(1) + 位状态(N) |
| 0x02 | Read Discrete Inputs 读离散输入 | 位,只读 | 起始地址(2) + 数量(2) | 字节数(1) + 位状态(N) |
| 0x03 | Read Holding Registers 读保持寄存器 | 字 | 起始地址(2) + 数量(2) | 字节数(1) + 寄存器值(2N) |
| 0x04 | Read Input Registers 读输入寄存器 | 字,只读 | 起始地址(2) + 数量(2) | 字节数(1) + 寄存器值(2N) |
| 0x05 | Write Single Coil 写单个线圈 | 位 | 地址(2) + 值(2) | 回显地址(2) + 值(2) |
| 0x06 | Write Single Register 写单个寄存器 | 字 | 地址(2) + 值(2) | 回显地址(2) + 值(2) |
| 0x0F | Write Multiple Coils 写多个线圈 | 位 | 地址(2) + 数量(2) + 字节数(1) + 值(N) | 地址(2) + 数量(2) |
| 0x10 | Write Multiple Registers 写多个寄存器 | 字 | 地址(2) + 数量(2) + 字节数(1) + 值(2N) | 地址(2) + 数量(2) |
| 0x14 | Read File Record 读文件记录 | 记录 | 字节数(1) + 子请求组(每组 7 字节) | 变长 |
| 0x15 | Write File Record 写文件记录 | 记录 | 字节数(1) + 子请求组 | 回显 |
| 0x16 | Mask Write Register 屏蔽写寄存器 | 字 | 地址(2) + And(2) + Or(2) | 回显同样 6 字节 |
| 0x17 | Read/Write Multiple Registers 读写多个 | 字 | 读起始+读数量+写起始+写数量+写字节数+写值 | 字节数(1) + 读到的数据。极少数设备支持 |
| 0x2B | Read Device Identification 读设备识别码 | — | MEI 等 | 厂商/型号字符串 |
源码定位:
HSL:ModBus/ModbusInfo.java:22(01) :27(02) :33(03) :38(04) :44(05) :49(06)
:55(0F) :60(10) :65(14) :70(15) :75(16) :80(17)
iot:enums/EMbFunctionCode.java,额外定义了 0x2B(HSL 未定义)
2.2 请求/响应字节模板
【0x03 读保持寄存器】
请求:| 从站(1) | 03 | 起始地址(2) | 数量(2) |
响应:| 从站(1) | 03 | 字节数(1) | 数据(2N) |
【0x01 读线圈】
请求:| 从站(1) | 01 | 起始地址(2) | 数量(2) |
响应:| 从站(1) | 01 | 字节数(1) | 位压缩数据(⌈N/8⌉) |
【0x06 写单寄存器】
请求:| 从站(1) | 06 | 地址(2) | 值(2) |
响应:| 从站(1) | 06 | 地址(2) | 值(2) | ← 完全回显
【0x10 写多寄存器】
请求:| 从站(1) | 10 | 地址(2) | 数量(2) | 字节数(1) | 值(2N) |
响应:| 从站(1) | 10 | 地址(2) | 数量(2) |
第 3 章 Modbus 异常码表
判别规则:响应功能码 = 请求功能码 | 0x80(最高位置 1)。异常码在响应 PDU 的第 2 个字节。
| 码 | 名称 | 中文解释 | 常见触发 / 处置 |
|---|---|---|---|
| 01 | Illegal Function | 非法功能码 | 设备只支持 03/06,你发了 04 → 换功能码 |
| 02 | Illegal Data Address | 非法数据地址 | 地址越界,或地址+长度组合越界;手册说 100 个寄存器,你读 40100 起 10 个 → 末尾越界 |
| 03 | Illegal Data Value | 非法数据值 | 给 0x05 发了 00 01 而非 FF 00;写入值超量程 |
| 04 | Slave Device Failure | 从站设备故障 | 读设备状态寄存器诊断 |
| 05 | Acknowledge | 已接收、正在处理 | 主站应继续轮询"程序完成"报文 |
| 06 | Slave Device Busy | 从站正忙 | 重试 + 退避,不要立即重发 |
| 08 | Memory Parity Error | 内存奇偶校验错 | 重试;持续则硬件故障 |
| 0A | Gateway Path Unavailable | 网关无法分配内部路径 | 网关配置错误或过载 |
| 0B | Gateway Target Failed to Respond | 网关存在但其后从站未响应 | 查网关后从站是否掉线 / 波特率是否对 |
HSL 只实现了 01/02/03/04 四个中文描述(
Language/DefaultLanguage.java:172-175),其余落到UnknownError。 iot 的中文注释极其详尽,见enums/EMbExceptionCode.java:47-118。
第 4 章 Modbus 单次读写上限
| 方向 | 功能码 | 上限 | 推导 |
|---|---|---|---|
| 读字 | 0x03 / 0x04 | 125 个字 | 1+1+2N ≤ 253 → N ≤ 125.5 |
| 读位 | 0x01 / 0x02 | 2000 个位 | 1+1+⌈N/8⌉ ≤ 253 → ⌈N/8⌉ ≤ 251 → N ≤ 2008,规范取整 2000 |
| 写字 | 0x10 | 123 个字 | 1+1+2+2+1+2N+2 ≤ 256 → 2N ≤ 247 |
| 写位 | 0x0F | 1968 个位 | 1+1+2+2+1+⌈N/8⌉+2 ≤ 256 → ⌈N/8⌉ ≤ 249 |
| 屏蔽写 | 0x16 | 1 个字 | — |
注意读写不对称:读 125 / 写 123。写 len=124 的组包在严格设备上会失败。
库的做法:HSL 在 ModbusInfo.java:155-172 自动切分为字 120 / 位 2000,比理论上限留 5 个寄存器余量。
第 5 章 S7 区域码表
以 iot-communication/.../s7/enums/EArea.java:42-117 为准。
| 值 | 缩写 | 含义 | HSL 地址前缀(S7AddressData.java) |
|---|---|---|---|
0x03 |
— | 200 系列系统信息 | 未使用 |
0x05 |
SM | 200 系列系统标志 | SM |
0x06 |
AI | 200 系列模拟量输入 | AI |
0x07 |
AQ | 200 系列模拟量输出 | AQ |
0x80 |
PI/PQ | 直接访问外设 | P |
0x81 |
I | 输入映像区 | I |
0x82 |
Q | 输出映像区 | Q |
0x83 |
M | 内部标志位 | M |
0x84 |
DB | 数据块 | D / DB |
0x85 |
DI | 背景数据块 | 源码未实现(需实测确认) |
0x86 |
L | 局部变量 | 源码未实现(需实测确认) |
0x87 |
V | 全局变量(V 区) | 见下方说明 |
0x1C |
C | S7 计数器 | — |
0x1D |
T | S7 定时器 | — |
0x1E |
— | IEC 计数器(200 系列) | C |
0x1F |
— | IEC 定时器(200 系列) | T |
两个必须说清的差异:
| 项 | HSL | iot |
|---|---|---|
| V 区 | V100 ≡ DB1.DBB100(area=0x84,DB=1) |
用 0x87(标注 UNKNOWN_YET → "V") |
| T/C | 0x1F(T) / 0x1E(C) |
0x1D(T) / 0x1C(C) |
两者真机行为需实测确认,尤其是 1200/1500 上读写 T/C。
第 6 章 S7 TSAP / rack-slot 对照表
远端 TSAP = ConnectionType << 8 | (rack << 5 | slot)
| 机型 | rack/slot | HSL 远端 (0xC2) | HSL 本地 (0xC1) | iot remote | iot local |
|---|---|---|---|---|---|
| S7-1200 | 0 / 0 | 0x0100 |
0x0102 |
0x0300 + rack*0x20 + slot |
0x0100 |
| S7-1500 | 0 / 0 | 0x0100 |
0x0102 |
0x0300 + slot |
0x0100 |
| S7-300 | 0 / 2 | 0x0102 |
0x0102 |
0x0302 |
0x0100 |
| S7-400 | 0 / 3 | 0x0103 |
0x0100 |
0x0303 |
0x0100 |
| S7-200 | — | 0x4D57("MW") |
0x4D57 |
0x4D57 |
0x4D57 |
| S7-200Smart | — | 0x0300 |
0x1000 |
0x0300 |
0x1000 |
| SINUMERIK 828D | — | — | — | 0x0D04 |
0x0400 |
源码依据:
HSL SiemensS7Net.java:65-92 按机型改 plcHead1[21]
SiemensS7Net.java:848-867 200Smart / S7-200 整条数组替换
(S7-200 的 TPDU-Size 是 0x09 = 512,注意别照抄 1200 的 0x0A)
iot service/PLCNetwork.java:158-191 remote = 0x0300 + 0x20*rack + slot
enums/EPlcType.java:34-40 S300(0,2) S400(0,3) S1200(0,1) S1500(0,1)
为什么两库默认值不同:
HSL 用 ConnectionType = 0x01(PG)
iot 用 ConnectionType = 0x03(S7 单边)
经测试(iot 源码 PLCNetwork.java:161 注释):
S1200 支持 0x01 00 / 0x02 00 / 0x03 00
S200Smart 支持 0x02 00 / 0x03 00
→ 1200/1500 通常忽略 slot 位,只要 byte0 ∈ {01,02,03}
→ 300/400 严格校验 rack/slot
排查动作:TIA Portal 确认 rack/slot → 换 ConnectionType 试 0x0100 → 0x0200 → 0x0300 → 抓包看 CC(回 D0 通过,回 50 或无响应必是 TSAP)。
第 7 章 S7 错误码表
7.1 传输层 / 握手层
| 现象 | 原因 | 动作 |
|---|---|---|
| TCP 连不上 | IP 错、防火墙、PLC 未上电、连接耗尽 | ping + telnet ip 102;减少并发连接 |
| CR 无响应 | TPKT 长度写错、TSAP 非法 | 抓包核对长度 = 22;换 TSAP |
收到 COTP 0x50 |
远端 TSAP 被拒 | 换 ConnectionType(01/02/03);检查连接机制 |
| Setup 响应 PDU 异常小 | 固件限制、连接类型不匹配 | 用响应最后 2 字节的实际值 |
7.2 Header Error Class(第 1 字节)
| 值 | 含义 | 值 | 含义 |
|---|---|---|---|
0x00 |
无错误(此时看 Data 区 Return Code) | 0x84 |
服务处理中错误 |
0x81 |
应用关系错误 | 0x85 |
请求错误 |
0x82 |
对象定义错误 | 0x87 |
访问错误 |
0x83 |
资源不足 | 0xD2 |
下载错误 |
7.3 S7 错误码(2 字节 = ErrorClass + Code)
完整表见 iot-communication/.../s7/constant/ErrorCode.java:60-265,高频摘录:
| 错误码 | 含义 | 错误码 | 含义 |
|---|---|---|---|
0x0000 |
没有错误 | 0x8301 |
内存/工作内存不足或介质不可访问 |
0x0110 |
块号无效 | 0x8302 |
可用资源太少或处理器不可用 |
0x0111 |
请求长度无效 | 0x8304 |
无法进一步并行上传(资源瓶颈) |
0x0112 |
参数无效 | 0x8401 |
无效的服务序列 |
0x0114 |
找不到块 | 0x8402 |
对象状态导致服务无法执行 |
0x0119 |
密码不正确 | 0x8500 |
S7 协议错误:帧错误 |
0x011A / 0x011B |
PG / PLC 资源错误 | 0x8701 |
寻址对象出错(区域长度错误) |
0x011C |
协议错误 | 0x8702 |
模块不支持所请求的服务 |
0x0140 |
可用内存不足 | 0x8703 |
拒绝访问对象 |
0x8100 |
远程模块未知的服务 | 0x8704 |
对象已损坏 |
0x8204 |
对象类型规范不一致 | 0xD001 / 0xD015 / 0xD026 |
非法作业号 / 标签地址非法 / 非法访问类型 |
7.4 高频排查清单(按出现频率)
1. 能连不能读(1200/1500)→ 99% 是没勾 "PUT/GET 通信访问"
2. 读 DB 返回 0x0A → DB 号错 / DB 未下载 / DB 仍是"优化的块访问"
3. 值荒谬(超大数或 0) → 字节序错(应大端);或地址忘了 ×8
4. 长数据前面对、后面错 → 分包地址推进漏了 ×8
5. 握手失败 → TSAP(rack/slot / ConnectionType)
6. 返回 0x05 → 越界:起始地址 + 长度超出区域范围
7. 返回 0x06 → 类型不支持:transport size 与区域不匹配
8. 偶发掉线 → PLC 连接资源打满,或未串行化导致错帧
9. 写多 item 后数据错位 → 奇数长度项未补 1 字节对齐
10. 写位把相邻位清掉 → 用了整字节写;位数组必须 Read-Modify-Write
第 7 章(续)S7 PDU 与单次读取上限
S7 机型默认 PDU(iot 的 EPlcType)
| 机型 | rack | slot | 源码默认 PDU |
|---|---|---|---|
| S200 | 0 | 1 | 240 |
| S200_SMART | 0 | 1 | 240 |
| S300 | 0 | 2 | 240 |
| S400 | 0 | 3 | 480 |
| S1200 | 0 | 1 | 240 |
| S1500 | 0 | 1 | 960 |
| SINUMERIK_828D | 0 | 1 | 240 |
实际值以 Setup Communication 的响应为准,上表只是构造请求时的初值。
单次读取上限
一帧读请求 = Header(12) + Parameter(2 + 12n) + Data(Σ(4 + len_i))
单个 item 最大读取字节数:
maxLen = PDU − 12(Header) − 12(参数区 item) − 4(数据项头) = PDU − 28
PDU = 240 → 212 字节
PDU = 480 → 452 字节
PDU = 960 → 932 字节
item 数量限制:
| 项 | 值 | 说明 |
|---|---|---|
| HSL 硬编码上限 | 19 个 item | SiemensS7Net.java:912(>19 抛异常)、SiemensS7Helper.java:62 |
| 协议字段上限 | 255 | item count 是 1 字节 |
| 真实约束 | PDU | 16n + Σlen ≤ 226(PDU=240) |
⚠️ n=19 时 16×19 = 304 已超 226(Σlen 只能为 0),所以 19 只在 PDU 较大(400 的 480、1500 的 960)时才有意义;1200 上实际可用个数远小于 19(需实测确认)。
保守做法:按 n ≤ (PDU − 28 − Σlen) / 16 动态计算。
iot 的做法更精细:S7SequentialGroupAlg.readRecombination(src, targetSize, extraSize, threshold) 把"每个 item 额外占用 extraSize 字节"显式建模,比 HSL 的两级切分更值得借鉴。
字节序
S7 是大端。HSL 用 ReverseBytesTransform(SiemensS7Net.java:63)。 按 Java 默认小端解析会得到荒谬值。多帧结果按 item 顺序直接拼接。
第 8 章 MC 软元件编码表
8.1 标准软元件(1 字节设备码)
逐条核对自 Profinet/Melsec/MelsecMcDataType.java。FromBase 列是进制陷阱的依据。
| 软元件 | 设备码 | 类型 | FromBase | 源码行 |
|---|---|---|---|---|
| X(输入继电器) | 0x9C |
位 | 16 | 70 / 191 |
| Y(输出继电器) | 0x9D |
位 | 16 | 74 / 196 |
| M(中间继电器) | 0x90 |
位 | 10 | 78 / 201 |
| SM(特殊继电器) | 0x91 |
位 | 10 | 82 / 206 |
| S(步进继电器) | 0x98 |
位 | 10 | 86 / 226 |
| L(锁存继电器) | 0x92 |
位 | 10 | 90 / 211 |
| F(报警器) | 0x93 |
位 | 10 | 94 / 216 |
| V(边沿继电器) | 0x94 |
位 | 10 | 98 / 221 |
| B(链接继电器) | 0xA0 |
位 | 16 | 102 / 231 |
| SB(特殊链接继电器) | 0xA1 |
位 | 16 | 106 / 236 |
| DX(直接访问输入) | 0xA2 |
位 | 16 | 110 / 241 |
| DY(直接访问输出) | 0xA3 |
位 | 16 | 114 / 246 |
| D(数据寄存器) | 0xA8 |
字 | 10 | 120 / 251 |
| SD(特殊寄存器) | 0xA9 |
字 | 10 | 124 / 256 |
| W(链接寄存器) | 0xB4 |
字 | 16 | 128 / 261 |
| SW(特殊链接寄存器) | 0xB5 |
字 | 16 | 132 / 266 |
| R(文件寄存器) | 0xAF |
字 | 10 | 136 / 271 |
| Z(变址寄存器) | 0xCC |
字 | 10 | 140 / 276 |
| ZR(文件寄存器 ZR 区) | 0xB0 |
字 | 10(Hsl)/16(iot) | 144 / EMcDeviceCode.java:262 |
| TN(定时器当前值) | 0xC2 |
字 | 10 | 149 |
| TS(定时器触点) | 0xC1 |
位 | 10 | 153 |
| TC(定时器线圈) | 0xC0 |
位 | 10 | 157 |
| SS / STS(累计定时器触点) | 0xC7 |
位 | 10 | 161 / 297 |
| SC / STC(累计定时器线圈) | 0xC6 |
位 | 10 | 165 / 301 |
| SN / STN(累计定时器当前值) | 0xC8 |
字 | 10 | 169 / 305 |
| CN(计数器当前值) | 0xC5 |
字 | 10 | 173 |
| CS(计数器触点) | 0xC4 |
位 | 10 | 177 |
| CC(计数器线圈) | 0xC3 |
位 | 10 | 181 |
速记串:
D=A8 M=90 X=9C Y=9D L=92 F=93 V=94 B=A0
W=B4 R=AF ZR=B0 SD=A9 SM=91 S=98
TS=C1 TC=C0 TN=C2 CS=C4 CC=C3 CN=C5
SB=A1 SW=B5 DX=A2 DY=A3
8.2 iQ-R 专有扩展(2 字节设备码)
同文件 185-357 行,R_ 前缀:
| 软元件 | 设备码 |
|---|---|
R_LTS |
0x0051 |
R_LTC |
0x0050 |
R_LTN |
0x0052 |
R_LSTS |
0x0059 |
R_LSTC |
0x0058 |
R_LSTN |
0x005A |
R_LCS |
0x0055 |
R_LCC |
0x0054 |
R_LCN |
0x0056 |
8.3 两处源码冲突(需实测确认)
① ZR 进制:
Hsl = 10 进制 (MelsecMcDataType.java:144)
iot = 16 进制 (EMcDeviceCode.java:262)
Q/L 通常十进制,iQ-R 十六进制 → 现场以 ZR 报错为准逐台试
② ASCII 软元件码第二字符:
两库用 "D*"
三菱手册常写 "D "(空格 0x20)
多数 PLC 都接受
第 9 章 MC 命令与子命令表
9.1 命令(Command,2 字节,小端)
⚠️ 字节序陷阱:源码写成 {0x01, 0x04}(线上字节序),但逻辑值是 0x0401。命令是小端:先发低字节。
| 线上字节 | 逻辑值 | 名称 | 源码 |
|---|---|---|---|
01 04 |
0x0401 |
批量读(Device Read Batch) | McBinaryHelper.java:67-68 |
01 14 |
0x1401 |
批量写(Device Write Batch) | McBinaryHelper.java:91-92 |
03 04 |
0x0403 |
随机读(字单位,每地址 1 字) | McBinaryHelper.java:169-170 |
02 14 |
0x1402 |
随机写(字单位) | McBinaryHelper.java:217-218 |
06 04 |
0x0406 |
多块批量读(每块可指定长度) | McBinaryHelper.java:192-193 |
06 14 |
0x1406 |
多块批量写 | EMcCommand.java:113 |
01 08 / 02 08 |
0x0801 / 0x0802 |
监视数据登录 / 监视读取 | EMcCommand.java:119/125 |
01 10 / 02 10 / 06 10 |
0x1001 / 0x1002 / 0x1006 |
远程 RUN / STOP / RESET | McHelper.java:455/468/481 |
01 01 / 17 16 |
0x0101 / 0x1617 |
读 CPU 型号 / 出错信息清除 | McHelper.java:495/512 |
9.2 子命令(Sub Command,2 字节,小端)
位/字访问的分水岭,也是 Q/L 与 iQ-R 的主要差异。
| 线上字节 | 逻辑值 | 适用机型 | 含义 | 源码 |
|---|---|---|---|---|
00 00 |
0x0000 |
Q / L / QnA | 字单位批量读写 | McBinaryHelper.java:69、McReadDeviceBatchInWordReqData.java:54 |
01 00 |
0x0001 |
Q / L / QnA | 位单位批量读写 | McBinaryHelper.java:69/118、McReadDeviceBatchInBitReqData.java:54 |
02 00 |
0x0002 |
iQ-R / iQ-F | 字单位(替代 0x0000) | McReadDeviceBatchInWordReqData.java:54、MelsecMcRNet.java:286 |
03 00 |
0x0003 |
iQ-R / iQ-F | 位单位(替代 0x0001) | McReadDeviceBatchInBitReqData.java:54、MelsecMcRNet.java:286 |
80 00 / 81 00 |
0x0080 / 0x0081 |
Q/L 扩展指定 | 字/位 + 扩展文件寄存器 | McBinaryHelper.java:142 |
代码对照:
// iot:三元表达式最清楚(McReadDeviceBatchInWordReqData.java:53-54)
this.subcommand = series != EMcSeries.IQ_R ? 0x0000 : 0x0002; // 字单位
// 位版本(McReadDeviceBatchInBitReqData.java:53-54)
this.subcommand = series != EMcSeries.IQ_R ? 0x0001 : 0x0003;
// Hsl 的 iQ-R 实现(MelsecMcRNet.java:286)
command[2] = isBit ? (byte) 0x03 : (byte) 0x02;
⚠️ 坑:用 Q/L 的子命令
0x0000访问 iQ-R,PLC 回0xC059(命令/子命令不正确)。
9.3 地址与点数编码
起始地址:3 字节小端(iQ-R 为 4 字节)
点 数:2 字节小端
// McBinaryHelper.java:71-76
command[4] = Utilities.getBytes(addressData.getAddressStart())[0]; // 地址低字节
command[5] = Utilities.getBytes(addressData.getAddressStart())[1];
command[6] = Utilities.getAddressBytes(addressData.getAddressStart())[2];
command[7] = (byte) addressData.getMcDataType().getDataCode(); // 软元件码
command[8] = (byte) (addressData.getLength() % 256); // 点数低字节
command[9] = (byte) (addressData.getLength() / 256); // 点数高字节
两条硬规则:
| 规则 | 说明 |
|---|---|
| 八进制陷阱 | X/Y/B/W/SB/SW/DX/DY/ZR(iot) 用十六进制解析地址;其余十进制 |
| 位软元件按字单位访问 | 起始地址必须是 16 的倍数,否则 PLC 报错 0x0058 |
第 10 章 MC 结束代码(错误码)表
位置:3E/4E 二进制 → 偏移 9,2 字节 UInt16 小端;3E/4E ASCII → 偏移 18,4 个 ASCII 十六进制字符;1E → 偏移 1,1 字节(值为 0x5B 时真实错误码在偏移 2)。
来源:MelsecHelper.GetErrorDescription(MelsecHelper.java:305-370)+ Language/DefaultLanguage.java:202-229。
| 结束代码 | 中文含义(源码原文) | 现场速判 |
|---|---|---|
0x0000 |
正常结束 | 成功 |
0x0002 |
"读/写"(入/出)软元件的指定范围不正确 | 地址或长度越界 |
0x0051 |
随机访问缓冲存储器通讯时,起始地址超出 0-6143 范围 | 缓冲存储器地址越界 |
0x0052 |
起始地址+数据字数超出 0-6143;或数据不能一帧发完 | 帧太长,要切片 |
0x0054 |
选择了 ASCII 通讯,但收到无法转二进制的 ASCII 码 | 编码方式不匹配 |
0x0055 |
PLC 处于 RUN 且未允许"运行中写入",外部请求写数据 | RUN 中禁止写入 |
0x0056 |
从外部进行的软元件指定不正确 | 软元件码不对 |
0x0058 |
起始地址超限;扩展块号不存在;不能指定 R;对位软元件用了字命令;位起始号不是 16 的倍数 | 多因,常见后两条 |
0x0059 |
不能指定扩展文件的寄存器 | — |
0x430D |
请求数据异常,CPU 启用了用户认证,请禁用 | CPU 认证未关 |
0xC04D |
以太网模块 UDP/固定缓冲通讯中,应用领域指定的数据长度不正确 | UDP 帧长错 |
0xC050 |
设置为 ASCII 通讯时收到无法转二进制的 ASCII 数据 | 编码方式不匹配 |
0xC051~0xC054 |
读/写点的数目在允许范围之外 | 点数超限 |
0xC055 |
文件数据读/写点的数目在允许范围之外 | — |
0xC056 |
读/写请求超过了最大地址 | 地址越界 |
0xC057 |
请求数据的长度与字符区域(部分文本)的数据计数不匹配 | 长度字段算错 |
0xC058 |
ASCII-二进制转换后,请求数据长度与字符区域数据计数不符 | ASCII 长度算错 |
0xC059 |
命令和子命令的指定不正确 | 命令/子命令错(含 iQ-R 用错 0x0000) |
0xC05A / 0xC05B |
以太网模块不能对指定软元件进行读出和写入 | 该软元件不可访问 |
0xC05C |
请求内容不正确(以位为单位请求读/写至字软元件) | 位/字混用 |
0xC05D |
不执行监视注册 | 未先 0x0801 登录 |
0xC05E |
以太网模块和 PLC CPU 之间的通讯时间超过了 CPU 监视定时器的时间 | 定时器太短 |
0xC05F |
目标 PLC 上不能执行请求 | — |
0xC060 |
请求内容不正确(对位软元件等指定了不正确的数据) | 位值不是 0x00/0x01/0x10/0x11 |
0xC061 |
请求数据的长度与字符区域中的数据数目不相符 | 长度算错 |
0xC062 |
禁止在线更正时,通过 MC 协议远程 I/O 站执行 3E/4E 帧写入 | 远程站写保护 |
0xC070 |
不能为目标站指定软元件存储器的范围 | — |
0xC072 |
请求内容不正确(以位为单位请求读写至字软元件) | — |
0xC074 |
目标 PLC 不执行请求。需要纠正网络号和 PC 号 | 网络号/PLC 号错 |
| 其它 | 请查看三菱的通讯手册来查看报警的具体信息 | — |
排错优先级(现场速查):
1. 一上电就 0xC059 → 命令/子命令错 → 检查 iQ-R 是否用了 0x0002/0x0003
2. 0xC051~0xC054 → 点数超限 → 加自动切片(上限见第 12 章)
3. 0xC05C / 0xC060 / 0x0058 → 位/字单位与软元件类型不匹配 → 检查子命令
4. 0xC056 / 0x0002 → 地址越界 → 检查 X/Y 的十六进制进制
5. 0x0055 → PLC 在 RUN 且不允许运行中写入 → GX Works 勾上
6. 0xC05E → 定时器太短 → 把 0A 00 调大(如 28 00 = 10s)
7. 0x0054 / 0xC050 → 二进制/ASCII 编码不匹配 → 改 PLC 侧通信数据代码设置
8. 0x430D → CPU 用户认证 → 关掉
iot 额外定义一条(
McNetwork.java:277-279):0xC0B5= 指定了 CPU 模块中无法处理的数据。
第 11 章 三协议单次读取上限横向对比
| 维度 | Modbus TCP | S7comm | MC(3E 二进制) |
|---|---|---|---|
| 上限来源 | PDU ≤ 253 字节(应用层) | 协商出的 PDU 长度 | 系列常量 + 帧长度字段 |
| 单次读字上限 | 125 个字(库用 120) | PDU − 28(1200 上 212 字节) | 960 字(库用 950) |
| 单次读位上限 | 2000 位(规范取整) | PDU − 28 字节 | 7168 位 |
| 单次写上限 | 123 字 / 1968 位 | 固定 200 字节(HSL 保守) | 960 字 / 7168 位 |
| 多地址合并 | ❌ 不支持(不同存储区不能混) | ✅ 支持多 item(源码硬限 19) | ✅ 支持随机读 0x0403 / 多块 0x0406 |
| 随机读上限 | — | 受 PDU 约束 | 随机读 192 字;多块 120 块 / 960 点 |
| ASCII 模式 | 帧长翻倍,上限 100 字左右 | — | 字 460 / 位 3584(约减半) |
| 库保守值 | HSL 120 字 / 2000 位 | HSL 用 PDU−28 | HSL 950 字 / 7168 位 |
11.1 MC 各系列完整上限(EMcSeries)
| 系列 | 帧类型 | 设备码字节 | 地址字节 | 批量读字 | 批量读位 | 随机读字 | 随机写字 | 随机写位 | 多块数 |
|---|---|---|---|---|---|---|---|---|---|
| A | 1E | 2 | 4 | 10 | 160 | 0 | 10 | 80 | 0 |
| QnA | 3E | 1 | 3 | 480 | 3584 | 96 | 960 | 94 | 120 |
| Q_L | 3E | 1 | 3 | 960 | 7168 | 192 | 1920 | 188 | 120 |
| iQ-R | 3E | 2 | 4 | 960 | 7168 | 96 | 960 | 94 | 60 |
11.2 关键结论
✓ PDU ≤ 253 是应用层约束,与传输方式无关
→ Modbus TCP 的 Length 字段虽是 16 位(理论 65535),
但从站内部缓冲区通常仍按 253 设计
→ 强行发超长请求会得到异常码 03/02,或被静默丢弃
✓ 读 125 / 写 123 不对称(固定开销不同)
✓ MC 的 Q_L 系列能力最强(960 字 / 7168 位)
✓ 分包不是性能优化,是正确性要求
→ 超限 PLC 直接报错,一条数据都拿不到
第 12 章 字节序四顺序速查
约定:原始报文顺序为 A B C D,其中 A 是最高字节(MSB),D 是最低字节(LSB)
名称 线上字节顺序 含义
────────────────────────────────────────────────
ABCD A B C D 大端(正常顺序)
BADC B A D C 大端 + 字内字节交换
CDAB C D A B 小端 + 字序交换("字序反了")
DCBA D C B A 小端(完全反转)
| 顺序 | 典型出处 |
|---|---|
| ABCD | 西门子 S7(大端) |
| DCBA | 多数 32 位浮点在 Modbus 中的排列 |
| CDAB | Modbus 常见"高低字反了"的现场现象 |
| BADC | 大端但字内字节交换(较少见) |
12.1 32 位浮点示例
| 顺序 | 内存/报文字节 | 解析结果 |
|---|---|---|
| ABCD | 41 48 00 00 |
12.5 ✅ |
| DCBA | 00 00 48 41 |
12.5 ✅ |
| CDAB | 00 00 41 48 |
❌ 荒谬值 |
| BADC | 48 41 00 00 |
❌ 荒谬值 |
现场口诀:浮点读出乱码 → 先换字序(ABCD↔CDAB 或 DCBA↔BADC),再换字节序。
12.2 代码实现
public enum ByteOrder {
ABCD, BADC, CDAB, DCBA;
public byte[] rearrange(byte[] b) { // 入参 b = 按报文顺序读到的原始字节
if (b == null || b.length != 4) throw new IllegalArgumentException("need 4 bytes");
byte a = b[0], c = b[1], d = b[2], e = b[3];
switch (this) {
case ABCD: return new byte[]{a, c, d, e};
case BADC: return new byte[]{c, a, e, d};
case CDAB: return new byte[]{d, e, a, c};
case DCBA: return new byte[]{e, d, c, a};
default: throw new IllegalStateException();
}
}
public boolean swapBytesInWord() { return this == BADC || this == DCBA; }
public boolean swapWords() { return this == CDAB || this == DCBA; }
}
两库对应:
| 库 | 机制 |
|---|---|
| HSL | Core/Transfer:RegularByteTransform / ReverseBytesTransform / ReverseWordTransform |
| iot | common/buff/EByteBuffFormat + ByteReadBuff / ByteWriteBuff |
第 13 章 Java Socket 选项速查
| 选项 | API | 作用 | 推荐值 | 坑 |
|---|---|---|---|---|
| TCP_NODELAY | setTcpNoDelay(true) |
关闭 Nagle 算法 | true |
❗ 工业小包必开。不开会有 40ms 延迟 |
| SO_TIMEOUT | setSoTimeout(ms) |
read() 阻塞上限 |
1000~3000 ms | ❗ 必须设,否则断线时线程永久挂起 |
| SO_KEEPALIVE | setKeepAlive(true) |
TCP 保活探测 | true |
⚠️ 默认 2 小时才发,靠不住,要配应用层心跳 |
| SO_LINGER | setSoLinger(true, sec) |
close() 时是否等待未发数据 |
不设(默认) | ❗ 设 0 会发 RST,可能丢数据 |
| SO_RCVBUF | setReceiveBufferSize(n) |
接收缓冲区 | 默认即可 | 太大浪费内存 |
| SO_SNDBUF | setSendBufferSize(n) |
发送缓冲区 | 默认即可 | 同上 |
| SO_REUSEADDR | setReuseAddress(true) |
地址复用 | 服务端 true |
客户端无意义 |
| IP_TOS | setTrafficClass(n) |
QoS 标记 | 一般不管 | 需网络设备配合 |
13.1 建连标准写法
Socket socket = new Socket();
socket.setTcpNoDelay(true); // 关 Nagle
socket.setSoTimeout(readTimeoutMs); // 读超时:必须有
socket.setKeepAlive(true); // 保活:不够,要应用层心跳
socket.connect(new InetSocketAddress(host, port), connectTimeoutMs);
13.2 关闭标准写法
// 顺序很重要:先关输出流触发对端 FIN,再关 socket
try { if (out != null) out.close(); } catch (IOException ignored) { }
try { if (in != null) in.close(); } catch (IOException ignored) { }
try { if (socket != null) socket.close(); } catch (IOException ignored) { }
❗ 只关 socket 不关流是 CLOSE_WAIT 堆积的头号原因。
第 14 章 Wireshark 过滤表达式速查
14.1 按协议
| 目的 | 表达式 |
|---|---|
| Modbus TCP | modbus 或 mbtcp |
| Modbus RTU over TCP | modbus (需手动解码端口) |
| 西门子 S7comm | s7comm |
| 西门子 S7comm-Plus | s7comm_plus |
| 三菱 MC / SLMP | slmp(需实测确认;部分版本用 melsec 或需手动指定端口解码) |
| OPC UA | opcua |
| MQTT | mqtt |
14.2 按 TCP 标志
| 目的 | 表达式 |
|---|---|
| RST 包 | tcp.flags.reset == 1 |
| SYN 包 | tcp.flags.syn == 1 |
| 重传 | tcp.analysis.retransmission |
| 乱序 | tcp.analysis.out_of_order |
| 零窗口 | tcp.window_size == 0 |
| 连接建立失败(只有 SYN 没 SYN-ACK) | tcp.flags.syn == 1 && tcp.flags.ack == 0 |
14.3 按地址/端口
| 目的 | 表达式 |
|---|---|
| 指定主机 | ip.addr == 192.168.1.110 |
| 指定端口 | tcp.port == 502 |
| 组合 | ip.addr == 192.168.1.110 && tcp.port == 102 |
| 排除某主机 | !(ip.addr == 192.168.1.1) |
14.4 工业现场常用组合
# 抓取某台 S7-1200 的全部交互
ip.addr == 192.168.1.110 && tcp.port == 102
# 找出所有异常响应(Modbus 功能码最高位置 1)
modbus && modbus.func_code >= 128
# 找出有问题的连接(重传/乱序/RST)
tcp.analysis.flags && !(tcp.analysis.window_update)
# 抓取 Modbus 且排除心跳
modbus && !(modbus.func_code == 3 && modbus.word_cnt == 1)
第 15 章 TCP 状态排查命令速查
15.1 常用命令
| 目的 | Linux | Windows (PowerShell) |
|---|---|---|
| 查看所有连接 | ss -antp |
netstat -ano |
| 查看某端口 | ss -antp | grep 502 |
netstat -ano | findstr :502 |
| 统计各状态数量 | ss -ant | awk '{print $1}' | sort | uniq -c |
netstat -ano | findstr ESTABLISHED /c |
| 查看监听端口 | ss -lntp |
netstat -ano -p tcp | findstr LISTENING |
| 查看进程占用 | lsof -i:502 |
netstat -ano | findstr :502 → tasklist /fi "PID eq X" |
| 内核统计 | netstat -s |
netstat -s |
| 抓包 | tcpdump -i eth0 host 192.168.1.110 -w s7.pcap |
Wireshark / netsh trace |
15.2 TCP 状态含义
| 状态 | 含义 | 异常信号 |
|---|---|---|
LISTEN |
服务端监听中 | — |
SYN_SENT |
已发 SYN,等 SYN-ACK | 大量 = 对端不通或防火墙丢包 |
SYN_RECV |
收到 SYN,等 ACK | 大量 = SYN Flood |
ESTABLISHED |
连接正常 | — |
FIN_WAIT_1 |
主动关闭,等 ACK | 短暂正常 |
FIN_WAIT_2 |
收到 ACK,等 FIN | 短暂正常 |
TIME_WAIT |
主动关闭方最终态(2MSL) | 大量 = 短连接高频 |
CLOSE_WAIT |
被动收到 FIN,本端未关 | ❗ 大量 = 代码没关流/socket |
LAST_ACK |
等最后的 ACK | 短暂正常 |
CLOSING |
双方同时关闭 | 罕见 |
15.3 关键诊断
❗ CLOSE_WAIT 堆积
含义:对端已发 FIN(对端主动关闭),本端没调 close()
定位:ss -antp | grep CLOSE_WAIT → 看 PID → 查代码漏关流
根因:只关 socket 不关流 / 异常路径没走 finally
❗ TIME_WAIT 堆积
含义:本端主动关闭,进入 2MSL 等待(通常 60s)
根因:短连接高频采集(HSL 默认短连接!)
处置:改长连接(HSL 用 SetPersistentConnection())
❗ RST 突然出现
可能:SO_LINGER=0 / 对端进程崩溃 / 读写已关闭的 socket
定位:抓包看 RST 发生时机
15.4 一键诊断脚本
#!/bin/bash
# tcp-status.sh <port>
PORT=${1:-502}
echo "=== 端口 $PORT 连接状态统计 ==="
ss -ant | awk -v p=":$PORT" '$4 ~ p {print $1}' | sort | uniq -c | sort -rn
echo ""
echo "=== 各状态总数 ==="
ss -ant | awk 'NR>1{print $1}' | sort | uniq -c | sort -rn
echo ""
echo "=== 占用 $PORT 的进程 ==="
lsof -i:$PORT 2>/dev/null || ss -antp | grep ":$PORT"
echo ""
echo "=== TCP 重传统计 ==="
netstat -s | grep -i -E "retransmit|reset|time.?wait"
第 16 章 两库 Maven 坐标与快速上手
16.1 Maven 坐标
<!-- iot-communication v1.5.6(MIT) -->
<dependency>
<groupId>com.github.xingshuangs</groupId>
<artifactId>iot-communication</artifactId>
<version>1.5.6</version>
</dependency>
<!-- HslCommunication Java 版(⚠️ License 情况复杂,见 06 篇 01 章 §2) -->
<dependency>
<groupId>com.github.dathlin</groupId>
<artifactId>HslCommunication</artifactId>
<version><!-- 以 Maven Central 为准 --></version>
</dependency>
⚠️ HSL 授权提示:随源码分发的
LICENSE文件是 LGPL-3.0(版权年份 2017-2019),但作者自 2019 年 8 月起停止开源、转向订阅制;现行代码内置Authorization校验模块,未激活时限制使用 8 小时。官方授权页面另行售卖商用授权。闭源商业产品引入前必须书面确认。✅ iot-communication 是 MIT,商用无付费义务,仅需保留版权声明。
16.2 iot-communication 三协议快速上手
// ===== Modbus TCP:读保持寄存器 0 开始的 4 个字 =====
ModbusTcp plc = new ModbusTcp(1, "192.168.1.130", 502);
plc.setComCallback((tag, bytes) ->
System.out.printf("%s[%d] %s%n", tag, bytes.length, HexUtil.toHexString(bytes)));
byte[] data = plc.readHoldRegister(0, 4);
plc.close();
// ===== 西门子 S7:读 DB1 偏移 0 的 8 字节 =====
S7PLC s7PLC = new S7PLC(EPlcType.S1200, "192.168.1.110");
byte[] dbData = s7PLC.readByte("DB1.0", 8);
boolean bit = s7PLC.readBoolean("DB1.6.0");
s7PLC.close();
// ===== 三菱 MC:读 D100 开始的 10 个字 =====
McPLC mcPLC = new McPLC(EMcSeries.Q_L, "192.168.1.120", 6000);
byte[] bytes = mcPLC.readBytes("D100", 10);
mcPLC.close();
16.3 HslCommunication Java 版三协议快速上手
// ===== Modbus TCP =====
ModbusTcpNet modbus = new ModbusTcpNet("192.168.1.130", 502, (byte) 1);
modbus.setAddressStartWithZero(true);
OperateResultExOne<byte[]> r1 = modbus.Read("40001", (short) 4);
if (r1.IsSuccess) { byte[] v = r1.Content; } else { log.warn(r1.Message); }
modbus.ConnectClose();
// ===== 西门子 S7 =====
SiemensS7Net siemens = new SiemensS7Net(SiemensPLCS.S1200, "192.168.1.110");
siemens.SetPersistentConnection(); // ❗ 默认短连接,务必显式开长连接
OperateResult connect = siemens.ConnectServer();
if (connect.IsSuccess) {
OperateResultExOne<byte[]> r2 = siemens.Read("DB1.0", (short) 8);
}
siemens.ConnectClose();
// ===== 三菱 MC =====
MelsecMcNet melsec = new MelsecMcNet("192.168.1.120", 6000);
OperateResultExOne<byte[]> r3 = melsec.Read("D100", (short) 10);
melsec.ConnectClose();
16.4 关键 API 对照
| 任务 | iot-communication | HslCommunication |
|---|---|---|
| Modbus 读保持寄存器 | readHoldRegister(addr, count) → byte[] |
Read("40001", (short) n) → OperateResultExOne<byte[]> |
| Modbus 写单寄存器 | writeInt16(addr, value) |
Write("40001", (short) v) |
| S7 读 DB | readByte("DB1.0", 8) → byte[] |
Read("DB1.0", (short) 8) → OperateResultExOne<byte[]> |
| S7 读位 | readBoolean("DB1.6.0") → boolean |
ReadBool("DB1.6.0") → OperateResultExOne<Boolean> |
| MC 读字 | readBytes("D100", 10) → byte[] |
Read("D100", (short) 10) → OperateResultExOne<byte[]> |
| 错误反馈 | 抛 S7CommException / ModbusCommException / McCommException |
返回 OperateResult,查 .IsSuccess / .Message |
| 报文调试 | setComCallback(...) ⭐ |
需 LogNet 或抓包 |
第 17 章 一张纸速记卡
┌────────────────────────────────────────────────────────────┐
│ 端口 Modbus 502 · S7 102 · MC 6000 · OPCUA 4840 │
│ │
│ 读上限 Modbus 125字/2000位 · S7 PDU-28 · MC 960字/7168位 │
│ 写上限 Modbus 123字/1968位(注意与读不对称) │
│ │
│ 字节序 S7 大端(ABCD) · Modbus 浮点常 DCBA 或 CDAB │
│ │
│ Socket TCP_NODELAY=true · SO_TIMEOUT 必须设 │
│ SO_LINGER 别设 0(会 RST) │
│ │
│ 握手 S7 两次:COTP CR/CC + Setup(0xF0) │
│ TSAP HSL 默认 0x0100(PG),iot 默认 0x0300(单边) │
│ │
│ 状态 CLOSE_WAIT = 没关流;TIME_WAIT = 短连接高频 │
│ │
│ License iot = MIT ✅ HSL = LGPL文件 + 现行商用授权 ⚠️ │
└────────────────────────────────────────────────────────────┘
小结
本手册覆盖了现场 90% 的查询需求。使用方式:
| 场景 | 查哪章 |
|---|---|
| 连不上 | 第 1 章(端口)+ 第 6 章(TSAP)+ 第 16 章(TCP 状态) |
| 数据不对 | 第 13 章(字节序)+ 第 3 章(异常码)+ 第 11 章(读上限) |
| 要抓包 | 第 15 章(Wireshark) |
| 开始写代码 | 第 17 章(Maven + 快速上手) |
三条最容易忘的:
- Modbus 读 125 / 写 123 不对称,写超 123 在严格设备上会失败。
- S7 地址是位地址,任何地址计算和分包推进都要 ×8。
- HSL 默认短连接,高频采集必须
SetPersistentConnection(),否则 TIME_WAIT 堆积且每次多付两次握手。
需要深入原理时,回到 01~05 篇对应章节;需要排查故障时,看 07 篇 02-常见问题排查FAQ。
本文是《工业通信与数据采集》栏目第 21/22 篇 · 上一篇:自研采集器设计实战 | 栏目总览 | 下一篇:常见问题排查 FAQ(附录)