Modbus TCP 是工业自动化领域广泛使用的一种通信协议,常用于 PLC、传感器、变频器、智能仪表、工业网关以及 MES、SCADA 等系统之间的数据交换。
它将 Modbus 应用层协议运行在 TCP/IP 网络之上,使工业设备能够通过以太网进行通信,是工业物联网和边缘计算系统中常见的设备接入方式之一。
一、什么是 Modbus TCP?

Modbus 最初由 Modicon(现施耐德电气旗下相关业务)推出,是一种开放、简单的工业通信协议。
Modbus TCP 则是 Modbus 在 TCP/IP 网络上的实现,通常使用 TCP 502 端口。
举个例子:工厂里有一台 PLC,负责控制生产设备。MES 希望获取设备的运行状态、当前温度、累计产量等信息。
通过 Modbus TCP,MES 或边缘网关可以向 PLC 发送读取请求,PLC 返回对应寄存器中的数据。
典型通信链路:
MES / SCADA / 边缘网关
发起读取或写入请求
TCP/IP 以太网
TCP 连接 · 通常使用端口 502
PLC / 仪表 / 设备控制器
解析请求,读取或更新相应寄存器

这里需要注意:Modbus TCP 只定义通信与数据访问方式,并不自动规定"寄存器 100 就代表设备温度"。具体地址、数据类型和工程含义,需要参考设备厂商提供的寄存器映射表。
二、Modbus TCP 的核心架构
Modbus TCP 报文通常由两部分组成:
-
MBAP Header:Modbus TCP 应用协议头。
-
PDU(Protocol Data Unit):功能码与数据。
标准 Modbus TCP 报文一般没有串行 Modbus RTU 中的 CRC 校验字段,因为 TCP/IP 已经提供传输层校验机制。
2.1 MBAP Header
MBAP Header 通常为 7 字节,结构如下。
| 字段 | 长度 | 作用 |
|---|---|---|
| Transaction Identifier | 2 字节 | 关联请求和响应 |
| Protocol Identifier | 2 字节 | Modbus 协议通常为 0 |
| Length | 2 字节 | 后续 Unit Identifier 与 PDU 的字节数 |
| Unit Identifier | 1 字节 | 标识目标单元,常用于网关后的设备寻址 |
其后是 PDU,包含功能码以及寄存器地址、数量或数据等内容。
2.2 常见功能码
| 功能码 | 名称 | 用途 |
|---|---|---|
01 |
Read Coils | 读取线圈状态 |
02 |
Read Discrete Inputs | 读取离散输入 |
03 |
Read Holding Registers | 读取保持寄存器 |
04 |
Read Input Registers | 读取输入寄存器 |
05 |
Write Single Coil | 写入单个线圈 |
06 |
Write Single Register | 写入单个保持寄存器 |
0F |
Write Multiple Coils | 写入多个线圈 |
10 |
Write Multiple Registers | 写入多个保持寄存器 |
最常见的设备监控场景是使用功能码 03 或 04 读取数据。
三、Modbus 的四类数据区
Modbus 常见的数据模型包括四类对象。
| 数据区 | 数据类型 | 常见访问方式 | 典型用途 |
|---|---|---|---|
| Coils | 单个位,可读写 | 01、05、0F |
开关状态、控制位 |
| Discrete Inputs | 单个位,只读 | 02 |
限位开关、输入状态 |
| Holding Registers | 16 位寄存器,可读写 | 03、06、10 |
设定值、计数值、参数 |
| Input Registers | 16 位寄存器,只读 | 04 |
温度、电压、测量值 |
需要注意,虽然一个寄存器通常为 16 位,但厂商可以将两个寄存器组合表示 32 位整数或浮点数,也可以使用多个寄存器表示更复杂的数据。
另外,文档中出现的 40001 等传统参考地址,与报文里实际使用的零基地址可能存在偏移差异。开发时必须确认设备文档的地址约定,不能简单地将参考地址原样写入报文。
四、Modbus TCP 如何读取数据?
假设某台设备的寄存器映射表规定:
| 寄存器地址 | 数据含义 | 数据类型 |
|---|---|---|
| 100 | 当前温度 | 16 位有符号整数,单位 0.1℃ |
| 101 | 当前压力 | 16 位无符号整数,单位 0.01 MPa |
| 102 | 设备状态 | 16 位整数 |
| 103 | 累计产量 | 16 位无符号整数 |
以上地址和数据类型仅为示例。
如果要读取地址 100 开始的 4 个保持寄存器,可以使用功能码 03,请求从零基地址 100 开始读取 4 个寄存器。
典型请求的数据结构如下:
| 字段 | 示例值 |
|---|---|
| Transaction ID | 00 01 |
| Protocol ID | 00 00 |
| Length | 00 06 |
| Unit ID | 01 |
| Function Code | 03 |
| Starting Address | 00 64 |
| Quantity | 00 04 |
其中 00 64 是十六进制的 100。
响应中会返回 4 个寄存器的数值。客户端再根据设备寄存器映射表,将原始整数转换为温度、压力、状态和产量等业务数据。
例如,温度寄存器返回十进制数值 256,如果厂商规定比例系数为 0.1℃,那么实际温度为:
这说明,协议负责传输原始数据,而业务系统负责按照设备定义进行解码和转换。
五、Java 如何实现 Modbus TCP 通信?
在 Java 项目中,可以使用成熟的 Modbus 库,避免自行处理 TCP 报文的所有细节。
例如,可以评估 DigitalPetri Modbus 等 Java 实现。具体 API 应以所选版本的文档为准。
对于实际工程,通信客户端通常需要实现:
-
建立和关闭 TCP 连接。
-
读取保持寄存器和输入寄存器。
-
处理超时和通信异常。
-
按寄存器映射表解码数据。
-
记录设备通信状态。
-
在合适的策略下重连和重试。
下面展示一个不依赖特定第三方库的 Java Socket 示例,用于理解 Modbus TCP 请求与响应的底层结构。该示例读取保持寄存器,并检查响应报文,不包含完整的生产级连接池和重连机制。
java
import java.io.*;
import java.net.Socket;
import java.util.Arrays;
public class ModbusTcpDemo {
public static void main(String[] args) throws IOException {
String host = "192.168.1.100";
int port = 502;
int transactionId = 1;
int unitId = 1;
int startAddress = 100;
int quantity = 4;
// MBAP Header + Read Holding Registers 请求
byte[] request = {
(byte) (transactionId >> 8),
(byte) transactionId,
0, 0, // Protocol ID
0, 6, // Length
(byte) unitId,
0x03, // Function Code
(byte) (startAddress >> 8),
(byte) startAddress,
(byte) (quantity >> 8),
(byte) quantity
};
try (Socket socket = new Socket(host, port)) {
socket.setSoTimeout(3000);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
out.write(request);
out.flush();
// 先读取 7 字节 MBAP Header
byte[] header = readFully(in, 7);
int responseTransactionId =
((header[0] & 0xFF) << 8) | (header[1] & 0xFF);
int protocolId =
((header[2] & 0xFF) << 8) | (header[3] & 0xFF);
int length =
((header[4] & 0xFF) << 8) | (header[5] & 0xFF);
if (responseTransactionId != transactionId
|| protocolId != 0
|| length < 2 || length > 254) {
throw new IOException("Invalid Modbus TCP header");
}
// Length 包含 Unit ID 和 PDU,因此减去已读的 Unit ID
byte[] pdu = readFully(in, length - 1);
int functionCode = pdu[0] & 0xFF;
if (functionCode == 0x83) {
throw new IOException(
"Modbus exception code: " + (pdu[1] & 0xFF)
);
}
if (functionCode != 0x03 || pdu.length < 2) {
throw new IOException("Unexpected Modbus response");
}
int byteCount = pdu[1] & 0xFF;
if (byteCount != quantity * 2
|| pdu.length != 2 + byteCount) {
throw new IOException("Invalid register data length");
}
byte[] data = Arrays.copyOfRange(pdu, 2, pdu.length);
for (int i = 0; i < quantity; i++) {
int value =
((data[i * 2] & 0xFF) << 8)
| (data[i * 2 + 1] & 0xFF);
System.out.println(
"Register " + (startAddress + i) + ": " + value
);
}
}
}
private static byte[] readFully(InputStream in, int length)
throws IOException {
byte[] data = new byte[length];
int offset = 0;
while (offset < length) {
int n = in.read(data, offset, length - offset);
if (n < 0) {
throw new EOFException("Connection closed unexpectedly");
}
offset += n;
}
return data;
}
}
生产环境注意事项: 示例用于教学,不应直接作为完整的工业通信驱动投入生产。正式实现还应验证响应中的 Unit ID、功能码、异常报文长度和寄存器数量,并根据设备特点处理连接生命周期、并发请求、重连、日志及数据质量。
此外,未经授权不要向生产设备写入寄存器。错误的写入可能改变设定值,甚至影响设备运行。
六、Modbus TCP 在边缘计算中的作用
在工业边缘计算架构中,Modbus TCP 常作为设备数据接入协议。
现场设备
PLC · 仪表 · 变频器 · 传感器网关
Modbus TCP 驱动
寄存器读取 · 数据解码 · 通信异常处理
边缘数据处理
数据清洗 · 规则判断 · 本地缓存 · 异常检测
MQTT / HTTP / 企业 API
统一数据模型 · 事件上报 · 云边协同
MES / SCADA / 数据平台
设备状态 · 生产统计 · 质量追踪 · 告警分析

例如,边缘网关每秒读取设备状态和生产计数器,根据业务规则计算设备运行状态,再将状态变化事件发送给 MES,而不是把所有原始数据都直接写入 MES 数据库。
在设计时,应根据设备响应能力合理设置轮询频率,避免过多并发请求影响控制器负载。
七、Modbus TCP 的安全注意事项
Modbus TCP 的传统实现通常不提供内置的身份认证和传输加密。如果将其直接暴露在不可信网络中,会带来数据读取和未授权写入等风险。
建议:
-
将工业设备网络与办公网络、互联网进行分区隔离。
-
使用工业防火墙和访问控制规则,限制来源 IP、目的设备和端口。
-
默认只开放必要的读取功能,写入操作需要额外授权和审计。
-
远程访问通过受控 VPN、工业 DMZ 或安全网关进行。
-
监控异常连接、频繁请求、非法功能码和异常写入行为。
-
对安全敏感的设备控制,不应仅依赖普通 Modbus TCP 通信。
部分扩展方案,例如 Modbus Security,会通过 TLS 和相关认证机制增强通信安全,但设备与客户端必须同时支持对应机制。
八、总结
Modbus TCP 是工业设备接入中值得掌握的基础协议。对于 MES 开发工程师和工业边缘计算工程师,建议重点理解以下内容:
-
TCP 连接和端口 502。
-
MBAP Header 与 PDU 报文结构。
-
功能码
03、04、06、10。 -
线圈、离散输入、保持寄存器和输入寄存器。
-
寄存器映射、数据类型、比例系数和字节顺序。
-
超时、断线重连、数据校验和安全隔离。
掌握这些内容后,就可以进一步开发 PLC 数据采集驱动、工业边缘网关,以及连接现场设备与 MES 的统一数据接入服务。