在工业自动化领域,Modbus协议是最常用的通信协议之一。作为一名长期从事工业控制系统开发的工程师,我经常需要测试和验证Modbus主站设备的通信功能。为此,我开发了一个基于Java和Spring Boot的Modbus TCP从站模拟器,它可以在同一个端口上模拟多个不同从站ID的设备。
这个模拟器的主要特点包括:
项目采用以下核心技术栈:
在pom.xml中需要添加以下关键依赖:
xml复制<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.68.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
通过Spring Boot的@ConfigurationProperties实现灵活的配置管理:
java复制@ConfigurationProperties(prefix = "modbus.slave")
public class ModbusSlaveProperties {
private int port = 502;
private boolean enabled = true;
private Map<Integer, Integer> slaves = new HashMap<>();
// getters and setters
}
对应的application.yml配置示例:
yaml复制modbus:
slave:
enabled: true
port: 502
slaves:
1: 100
2: 50
3: 200
服务类使用Spring的生命周期注解管理Netty服务器的启动和停止:
java复制@PostConstruct
public void init() {
if (!properties.isEnabled()) return;
// 初始化从站设备
initSlaveDevices();
// 启动Netty服务器
startModbusServer();
}
@PreDestroy
public void stop() {
// 优雅关闭Netty资源
if (serverChannel != null) serverChannel.close();
if (bossGroup != null) bossGroup.shutdownGracefully();
if (workerGroup != null) workerGroup.shutdownGracefully();
}
模拟器使用ConcurrentHashMap管理多个从站设备,确保线程安全:
java复制private final Map<Integer, SlaveDevice> slaveDevices = new ConcurrentHashMap<>();
public void addSlaveDevice(int slaveId, SlaveDevice device) {
if (slaveId < 1 || slaveId > 247) {
throw new IllegalArgumentException("从站ID必须在1-247之间");
}
slaveDevices.put(slaveId, device);
}
核心的协议处理逻辑在ModbusServerHandler中实现:
java复制@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
try {
// 解析Modbus TCP报文头
int transactionId = in.readUnsignedShort();
int protocolId = in.readUnsignedShort();
int length = in.readUnsignedShort();
int unitId = in.readUnsignedByte();
// 处理Modbus请求
byte[] response = processModbusRequest(unitId, functionCode, requestData);
// 发送响应
if (response != null) {
sendResponse(ctx, transactionId, unitId, functionCode, response);
}
} finally {
ReferenceCountUtil.release(msg);
}
}
SimpleSlaveDevice实现了基本的保持寄存器读写功能:
java复制public static class SimpleSlaveDevice implements SlaveDevice {
private final short[] holdingRegisters;
public SimpleSlaveDevice(int slaveId, int registerCount) {
this.holdingRegisters = new short[registerCount];
// 初始化示例数据
for (int i = 0; i < registerCount; i++) {
holdingRegisters[i] = (short) (i * 10);
}
}
@Override
public byte[] processRequest(int slaveId, int functionCode, byte[] data) {
switch (functionCode) {
case 0x03: return readHoldingRegisters(data);
case 0x06: return writeSingleRegister(data);
case 0x10: return writeMultipleRegisters(data);
default: return createErrorResponse(functionCode, 0x01);
}
}
}
保持寄存器的读写操作实现细节:
java复制private byte[] readHoldingRegisters(byte[] data) {
int startAddress = ((data[0] & 0xFF) << 8) | (data[1] & 0xFF);
int quantity = ((data[2] & 0xFF) << 8) | (data[3] & 0xFF);
ByteBuffer buffer = ByteBuffer.allocate(1 + quantity * 2);
buffer.put((byte) (quantity * 2));
for (int i = 0; i < quantity; i++) {
buffer.putShort(holdingRegisters[startAddress + i]);
}
return buffer.array();
}
提供REST API用于动态管理从站设备:
java复制@RestController
@RequestMapping("/api/modbus")
public class ModbusController {
@Autowired
private ModusTcpSlaveService modbusService;
@GetMapping("/slaves")
public ResponseEntity<Map<String, Object>> getSlaves() {
// 返回所有从站信息
}
@PostMapping("/slave")
public ResponseEntity<Map<String, Object>> addSlave(
@RequestParam int slaveId,
@RequestParam int registerCount) {
// 添加新从站
}
@DeleteMapping("/slave/{slaveId}")
public ResponseEntity<Map<String, Object>> removeSlave(
@PathVariable int slaveId) {
// 移除从站
}
}
提供Modbus服务的启停管理:
java复制@PostMapping("/restart")
public ResponseEntity<Map<String, Object>> restart() {
modbusService.restart();
return ResponseEntity.ok(Collections.singletonMap("message", "Modbus服务重启成功"));
}
推荐使用以下工具进行测试:
典型测试场景包括:
无法建立连接:
连接频繁断开:
响应不符合预期:
性能瓶颈:
在实际项目中使用这个模拟器时,我总结了以下几点经验:
寄存器初始化:建议在从站设备初始化时填充有意义的测试数据,便于调试。
日志记录:详细的日志对于排查问题非常重要,但要注意不要记录敏感数据。
资源清理:确保在服务停止时正确释放所有网络资源,避免端口占用问题。
动态配置:通过REST API动态管理从站设备可以大大提高测试效率。
性能测试:在实际使用前应进行压力测试,确保模拟器能够处理预期的负载。
这个模拟器已经在多个工业自动化项目中得到应用,有效提高了开发测试效率。通过不断优化和完善,它已经成为一个稳定可靠的Modbus测试工具。