2026-06-24 f4bd1f3c89d906131495a0aca5aaf82966378510
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
package cn.iocoder.yudao.module.iot.gateway.protocol.modbus;
 
import com.ghgande.j2mod.modbus.io.ModbusRTUTCPTransport;
import com.ghgande.j2mod.modbus.msg.*;
import com.ghgande.j2mod.modbus.procimg.*;
import com.ghgande.j2mod.modbus.slave.ModbusSlave;
import com.ghgande.j2mod.modbus.slave.ModbusSlaveFactory;
 
import java.net.ServerSocket;
import java.net.Socket;
 
/**
 * Modbus RTU over TCP 完整 Demo
 *
 * 架构:Master(主站)启动 TCP Server 监听 → Slave(从站)主动 TCP 连接上来
 * 通信协议:RTU 帧格式(带 CRC)通过 TCP 传输,而非标准 MBAP 头
 *
 * 流程:
 * 1. Master 启动 TCP ServerSocket 监听端口
 * 2. Slave(从站模拟器)作为 TCP Client 连接到 Master
 * 3. Master 通过 accept 得到的 Socket,使用 {@link ModbusRTUTCPTransport} 发送读写请求
 *
 * 实现说明:
 * 因为 j2mod 的 ModbusSlave 只能以 TCP Server 模式运行(监听端口等待 Master 连接),
 * 不支持"Slave 作为 TCP Client 主动连接 Master"的模式。
 * 所以这里用一个 TCP 桥接(bridge)来模拟:
 * - Slave 在本地内部端口启动(RTU over TCP 模式)
 * - 一个桥接线程同时连接 Master Server 和 Slave 内部端口,做双向数据转发
 * - Master 视角:看到的是 Slave 主动连上来
 *
 * 依赖:j2mod 3.2.1(pom.xml 中已声明)
 *
 * @author 芋道源码
 */
@Deprecated // 仅技术演示,非是必须的
public class ModbusRtuOverTcpDemo {
 
    /**
     * Master(主站)TCP Server 监听端口
     */
    private static final int PORT = 5021;
    /**
     * Slave 内部端口(仅本地中转用,不对外暴露)
     */
    private static final int SLAVE_INTERNAL_PORT = PORT + 100;
    /**
     * Modbus 从站地址
     */
    private static final int SLAVE_ID = 1;
 
    public static void main(String[] args) throws Exception {
        // ===================== 第一步:Master 启动 TCP Server 监听 =====================
        ServerSocket serverSocket = new ServerSocket(PORT);
        System.out.println("===================================================");
        System.out.println("[Master] TCP Server 已启动,监听端口: " + PORT);
        System.out.println("[Master] 等待 Slave 连接...");
        System.out.println("===================================================");
 
        // ===================== 第二步:后台启动 Slave,它会主动连接 Master =====================
        ModbusSlave slave = startSlaveInBackground();
 
        // Master accept Slave 的连接
        Socket slaveSocket = serverSocket.accept();
        System.out.println("[Master] Slave 已连接: " + slaveSocket.getRemoteSocketAddress());
 
        // ===================== 第三步:Master 通过 RTU over TCP 发送读写请求 =====================
        // 使用 ModbusRTUTCPTransport 包装 Socket(RTU 帧 = SlaveID + 功能码 + 数据 + CRC,无 MBAP 头)
        ModbusRTUTCPTransport transport = new ModbusRTUTCPTransport(slaveSocket);
 
        try {
            System.out.println("[Master] RTU over TCP 通道已建立\n");
 
            // 1. 读操作演示:4 种功能码
            demoReadCoils(transport);           // 功能码 01:读线圈
            demoReadDiscreteInputs(transport);  // 功能码 02:读离散输入
            demoReadHoldingRegisters(transport); // 功能码 03:读保持寄存器
            demoReadInputRegisters(transport);   // 功能码 04:读输入寄存器
 
            // 2. 写操作演示 + 读回验证
            demoWriteCoil(transport);            // 功能码 05:写单个线圈
            demoWriteRegister(transport);        // 功能码 06:写单个保持寄存器
 
            System.out.println("\n===================================================");
            System.out.println("所有 RTU over TCP 读写操作执行成功!");
            System.out.println("===================================================");
        } finally {
            // 清理资源
            transport.close();
            slaveSocket.close();
            serverSocket.close();
            slave.close();
            System.out.println("[Master] 资源已关闭");
        }
    }
 
    // ===================== Slave 设备模拟(作为 TCP Client 连接 Master) =====================
 
    /**
     * 在后台启动从站模拟器,并通过 TCP 桥接连到 Master Server
     *
     * @return ModbusSlave 实例(用于最后关闭资源)
     */
    private static ModbusSlave startSlaveInBackground() throws Exception {
        // 1. 创建进程映像,初始化寄存器数据
        SimpleProcessImage spi = new SimpleProcessImage(SLAVE_ID);
        // 1.1 线圈(Coil,功能码 01/05)- 可读写,地址 0~9
        for (int i = 0; i < 10; i++) {
            spi.addDigitalOut(new SimpleDigitalOut(i % 2 == 0));
        }
        // 1.2 离散输入(Discrete Input,功能码 02)- 只读,地址 0~9
        for (int i = 0; i < 10; i++) {
            spi.addDigitalIn(new SimpleDigitalIn(i % 3 == 0));
        }
        // 1.3 保持寄存器(Holding Register,功能码 03/06/16)- 可读写,地址 0~19
        for (int i = 0; i < 20; i++) {
            spi.addRegister(new SimpleRegister(i * 100));
        }
        // 1.4 输入寄存器(Input Register,功能码 04)- 只读,地址 0~19
        for (int i = 0; i < 20; i++) {
            spi.addInputRegister(new SimpleInputRegister(i * 10 + 1));
        }
 
        // 2. 启动 Slave(RTU over TCP 模式,在本地内部端口监听)
        ModbusSlave slave = ModbusSlaveFactory.createTCPSlave(SLAVE_INTERNAL_PORT, 5, true);
        slave.addProcessImage(SLAVE_ID, spi);
        slave.open();
        System.out.println("[Slave] 从站模拟器已启动(内部端口: " + SLAVE_INTERNAL_PORT + ")");
 
        // 3. 启动桥接线程:TCP Client 连接 Master Server,同时连接 Slave 内部端口,双向转发
        Thread bridgeThread = new Thread(() -> {
            try {
                Socket toMaster = new Socket("127.0.0.1", PORT);
                Socket toSlave = new Socket("127.0.0.1", SLAVE_INTERNAL_PORT);
                System.out.println("[Bridge] 已建立桥接: Master(" + PORT + ") <-> Slave(" + SLAVE_INTERNAL_PORT + ")");
 
                // 双向桥接:Master ↔ Bridge ↔ Slave
                Thread forward = new Thread(() -> bridge(toMaster, toSlave), "bridge-master→slave");
                Thread backward = new Thread(() -> bridge(toSlave, toMaster), "bridge-slave→master");
                forward.setDaemon(true);
                backward.setDaemon(true);
                forward.start();
                backward.start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "bridge-setup");
        bridgeThread.setDaemon(true);
        bridgeThread.start();
 
        return slave;
    }
 
    /**
     * TCP 双向桥接:从 src 读取数据,写入 dst
     */
    private static void bridge(Socket src, Socket dst) {
        try {
            byte[] buf = new byte[1024];
            var in = src.getInputStream();
            var out = dst.getOutputStream();
            int len;
            while ((len = in.read(buf)) != -1) {
                out.write(buf, 0, len);
                out.flush();
            }
        } catch (Exception ignored) {
            // 连接关闭时正常退出
        }
    }
 
    // ===================== Master 读写操作 =====================
 
    /**
     * 发送请求并接收响应(通用方法)
     */
    private static ModbusResponse sendRequest(ModbusRTUTCPTransport transport, ModbusRequest request) throws Exception {
        request.setUnitID(SLAVE_ID);
        transport.writeRequest(request);
        return transport.readResponse();
    }
 
    /**
     * 功能码 01:读线圈(Read Coils)
     */
    private static void demoReadCoils(ModbusRTUTCPTransport transport) throws Exception {
        ReadCoilsRequest request = new ReadCoilsRequest(0, 5);
        ReadCoilsResponse response = (ReadCoilsResponse) sendRequest(transport, request);
 
        StringBuilder sb = new StringBuilder("[功能码 01] 读线圈(0~4): ");
        for (int i = 0; i < 5; i++) {
            sb.append(response.getCoilStatus(i) ? "ON" : "OFF");
            if (i < 4) {
                sb.append(", ");
            }
        }
        System.out.println(sb);
    }
 
    /**
     * 功能码 02:读离散输入(Read Discrete Inputs)
     */
    private static void demoReadDiscreteInputs(ModbusRTUTCPTransport transport) throws Exception {
        ReadInputDiscretesRequest request = new ReadInputDiscretesRequest(0, 5);
        ReadInputDiscretesResponse response = (ReadInputDiscretesResponse) sendRequest(transport, request);
 
        StringBuilder sb = new StringBuilder("[功能码 02] 读离散输入(0~4): ");
        for (int i = 0; i < 5; i++) {
            sb.append(response.getDiscreteStatus(i) ? "ON" : "OFF");
            if (i < 4) {
                sb.append(", ");
            }
        }
        System.out.println(sb);
    }
 
    /**
     * 功能码 03:读保持寄存器(Read Holding Registers)
     */
    private static void demoReadHoldingRegisters(ModbusRTUTCPTransport transport) throws Exception {
        ReadMultipleRegistersRequest request = new ReadMultipleRegistersRequest(0, 5);
        ReadMultipleRegistersResponse response = (ReadMultipleRegistersResponse) sendRequest(transport, request);
 
        StringBuilder sb = new StringBuilder("[功能码 03] 读保持寄存器(0~4): ");
        for (int i = 0; i < response.getWordCount(); i++) {
            sb.append(response.getRegisterValue(i));
            if (i < response.getWordCount() - 1) {
                sb.append(", ");
            }
        }
        System.out.println(sb);
    }
 
    /**
     * 功能码 04:读输入寄存器(Read Input Registers)
     */
    private static void demoReadInputRegisters(ModbusRTUTCPTransport transport) throws Exception {
        ReadInputRegistersRequest request = new ReadInputRegistersRequest(0, 5);
        ReadInputRegistersResponse response = (ReadInputRegistersResponse) sendRequest(transport, request);
 
        StringBuilder sb = new StringBuilder("[功能码 04] 读输入寄存器(0~4): ");
        for (int i = 0; i < response.getWordCount(); i++) {
            sb.append(response.getRegisterValue(i));
            if (i < response.getWordCount() - 1) {
                sb.append(", ");
            }
        }
        System.out.println(sb);
    }
 
    /**
     * 功能码 05:写单个线圈(Write Single Coil)+ 读回验证
     */
    private static void demoWriteCoil(ModbusRTUTCPTransport transport) throws Exception {
        int address = 0;
 
        // 1. 先读取当前值
        ReadCoilsRequest readReq = new ReadCoilsRequest(address, 1);
        ReadCoilsResponse readResp = (ReadCoilsResponse) sendRequest(transport, readReq);
        boolean beforeValue = readResp.getCoilStatus(0);
 
        // 2. 写入相反的值
        boolean writeValue = !beforeValue;
        WriteCoilRequest writeReq = new WriteCoilRequest(address, writeValue);
        sendRequest(transport, writeReq);
 
        // 3. 读回验证
        ReadCoilsResponse verifyResp = (ReadCoilsResponse) sendRequest(transport, readReq);
        boolean afterValue = verifyResp.getCoilStatus(0);
 
        System.out.println("[功能码 05] 写线圈: 地址=" + address
                + ", 写入前=" + (beforeValue ? "ON" : "OFF")
                + ", 写入值=" + (writeValue ? "ON" : "OFF")
                + ", 读回值=" + (afterValue ? "ON" : "OFF")
                + (afterValue == writeValue ? " ✓ 验证通过" : " ✗ 验证失败"));
    }
 
    /**
     * 功能码 06:写单个保持寄存器(Write Single Register)+ 读回验证
     */
    private static void demoWriteRegister(ModbusRTUTCPTransport transport) throws Exception {
        int address = 0;
        int writeValue = 12345;
 
        // 1. 先读取当前值
        ReadMultipleRegistersRequest readReq = new ReadMultipleRegistersRequest(address, 1);
        ReadMultipleRegistersResponse readResp = (ReadMultipleRegistersResponse) sendRequest(transport, readReq);
        int beforeValue = readResp.getRegisterValue(0);
 
        // 2. 写入新值
        WriteSingleRegisterRequest writeReq = new WriteSingleRegisterRequest(address, new SimpleRegister(writeValue));
        sendRequest(transport, writeReq);
 
        // 3. 读回验证
        ReadMultipleRegistersResponse verifyResp = (ReadMultipleRegistersResponse) sendRequest(transport, readReq);
        int afterValue = verifyResp.getRegisterValue(0);
 
        System.out.println("[功能码 06] 写保持寄存器: 地址=" + address
                + ", 写入前=" + beforeValue
                + ", 写入值=" + writeValue
                + ", 读回值=" + afterValue
                + (afterValue == writeValue ? " ✓ 验证通过" : " ✗ 验证失败"));
    }
 
}