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 + 快速上手)

三条最容易忘的:

  1. Modbus 读 125 / 写 123 不对称,写超 123 在严格设备上会失败。
  2. S7 地址是位地址,任何地址计算和分包推进都要 ×8。
  3. HSL 默认短连接,高频采集必须 SetPersistentConnection(),否则 TIME_WAIT 堆积且每次多付两次握手。

需要深入原理时,回到 01~05 篇对应章节;需要排查故障时,看 07 篇 02-常见问题排查FAQ。


本文是《工业通信与数据采集》栏目第 21/22 篇 · 上一篇:自研采集器设计实战 | 栏目总览 | 下一篇:常见问题排查 FAQ(附录)