在工业物联网和智能制造系统中,Java적용程序经常需要与PLC、센서、릴레이模块等Modbus设备进行通讯。本文详细介绍如何使用EasyModbus库实现Java Modbus TCP클라이언트,包括환경搭建、연결管理、코일读写、레지스터读写、浮点数处理、异常处理以及完整的项目예제。
一、EasyModbus库介绍
EasyModbus是一个강력한 성능的Java Modbus通讯库,支持Modbus TCP、Modbus RTU和Modbus UDP三种프로토콜。它具有以下特点:
- 프로토콜支持全面:支持Modbus TCP、RTU、UDP,可作为클라이언트和服务端
- API简洁易用:方法命名直观,几行代码即可完成读写操作
- 데이터유형丰富:支持16位整数、32位浮点数、64位长整数、문자串等
- 自动重连:内置연결状态检测和自动重连机制
- 线程安全:支持多线程환경下的并发访问
- 오픈소스무료:基于MIT프로토콜,可무료用于商业项目
二、환경搭建
2.1 Maven依赖
在pom.xml中추가EasyModbus依赖:
<dependency>
<groupId>com.github.s7connector</groupId>
<artifactId>easymodbus</artifactId>
<version>1.0.0</version>
</dependency>
<!-- 或者使用JAR包방식 -->
<!-- 下载주소:https://github.com/rossmann-engineering/EasyModbusTCP -->
2.2 手动导入JAR包
- 从GitHub下载EasyModbusTCP.jar
- 在Eclipse中:右键项目 → Build Path → Add External Archives
- 在IntelliJ IDEA中:File → Project Structure → Libraries → + → Java
- 导入包:import de.re.easymodbus.modbusclient.ModbusClient;
三、基础연결与读写操作
3.1 建立연결
import de.re.easymodbus.modbusclient.ModbusClient;
public class ModbusTcpExample {
public static void main(String[] args) {
// 创建Modbus클라이언트实例
ModbusClient modbusClient = new ModbusClient("192.168.1.100", 502);
try {
// 建立연결
modbusClient.Connect();
System.out.println("연결成功!");
// 设置슬레이브 주소(默认为1)
modbusClient.setUnitIdentifier((byte) 1);
// 设置연결超时(毫秒)
modbusClient.setConnectionTimeout(5000);
// 设置读写超时(毫秒)
modbusClient.setReceiveTimeout(3000);
// 业务逻辑...
// 닫기연결
modbusClient.Disconnect();
System.out.println("연결已닫기");
} catch (Exception e) {
System.err.println("연결失败:" + e.getMessage());
e.printStackTrace();
}
}
}
3.2 读코일(기능 코드01)
// 읽기从주소0开始的8个코일 상태
boolean[] coils = modbusClient.ReadCoils(0, 8);
// 출력每个코일的状态
for (int i = 0; i < coils.length; i++) {
System.out.println("코일" + i + "状态:" + (coils[i] ? "ON" : "OFF"));
}
// 方法설명:
// ReadCoils(int 시작ing주소, int 수량)
// 시작ing주소: 시작주소(0-based)
// 수량: 읽을 수량(1-2000)
// 返回: boolean数组,true=코일吸合,false=코일断开
3.3 读이산 입력(기능 코드02)
// 읽기从주소0开始的4个이산 입력
boolean[] inputs = modbusClient.ReadDiscreteInputs(0, 4);
for (int i = 0; i < inputs.length; i++) {
System.out.println("입력" + i + "状态:" + (inputs[i] ? "高电平" : "低电平"));
}
3.4 读유지 레지스터(기능 코드03)
// 읽기从주소0开始的10个유지 레지스터
int[] registers = modbusClient.ReadHolding회원가입s(0, 10);
for (int i = 0; i < registers.length; i++) {
System.out.println("레지스터" + i + "值:" + registers[i]);
}
// 方法설명:
// ReadHolding회원가입s(int 시작ing주소, int 수량)
// 返回: int数组,每个元素为16位无符号整数(0-65535)
// 如果需要有符号整数,需要自行转换:(short) registers[i]
3.5 读입력 레지스터(기능 코드04)
// 읽기从주소0开始的2个입력 레지스터(通常用于센서데이터)
int[] inputRegs = modbusClient.ReadInput회원가입s(0, 2);
System.out.println("온도:" + inputRegs[0] / 10.0 + "℃");
System.out.println("湿度:" + inputRegs[1] / 10.0 + "%RH");
3.6 写单个코일(기능 코드05)
// 控制주소0的코일吸合
modbusClient.WriteSingleCoil(0, true);
// 控制주소0的코일断开
modbusClient.WriteSingleCoil(0, false);
// 方法설명:
// WriteSingleCoil(int 주소, boolean value)
// value: true=FF00(吸合),false=0000(断开)
3.7 写单个레지스터(기능 코드06)
// 向주소0쓰기数值1234
modbusClient.WriteSingle회원가입(0, 1234);
// 쓰기有符号数(需要转换为无符号)
short signedValue = -100;
modbusClient.WriteSingle회원가입(0, signedValue & 0xFFFF);
3.8 写多个코일(기능 코드15)
// 同时控制前4个코일
boolean[] coilValues = {true, false, true, false};
modbusClient.WriteMultipleCoils(0, coilValues);
// 方法설명:
// WriteMultipleCoils(int 시작ing주소, boolean[] values)
// values数组길이即为쓸 수량
3.9 写多个레지스터(기능 코드16)
// 向주소0开始쓰기3个레지스터
int[] regValues = {100, 200, 300};
modbusClient.WriteMultiple회원가입s(0, regValues);
四、浮点数与32位데이터处理
Modbus프로토콜本身只传输16位레지스터,32位浮点数需要占用两个连续的레지스터。EasyModbus提供了便捷的转换方法。
4.1 읽기32位浮点数
// 읽기주소11开始的2个레지스터,转换为32位浮点数
float value = ModbusClient.Convert회원가입sToFloat(
modbusClient.ReadHolding회원가입s(11, 2)
);
System.out.println("浮点数值:" + value);
// 指定바이트序(默认ABCD,可选BADC、CDAB、DCBA)
float value2 = ModbusClient.Convert회원가입sToFloat(
modbusClient.ReadHolding회원가입s(11, 2),
ModbusClient.회원가입Order.HighByteFirst // 或 LowByteFirst
);
System.out.println("浮点数值(指定바이트序):" + value2);
4.2 쓰기32位浮点数
// 将浮点数123.56转换为2个레지스터并쓰기주소11
float floatValue = 123.56f;
int[] regs = ModbusClient.ConvertFloatToTwo회원가입s(floatValue);
modbusClient.WriteMultiple회원가입s(11, regs);
System.out.println("쓰기的레지스터 값:" + regs[0] + ", " + regs[1]);
4.3 32位整数处理
// 읽기32位整数
int[] regs32 = modbusClient.ReadHolding회원가입s(0, 2);
int int32Value = (regs32[0] << 16) | regs32[1];
System.out.println("32位整数值:" + int32Value);
// 쓰기32位整数
int writeValue = 12345678;
int[] writeRegs = { (writeValue >> 16) & 0xFFFF, writeValue & 0xFFFF };
modbusClient.WriteMultiple회원가입s(0, writeRegs);
4.4 문자串处理
// 읽기문자串(每个레지스터存2个문자)
int[] strRegs = modbusClient.ReadHolding회원가입s(0, 10);
StringBuilder sb = new StringBuilder();
for (int reg : strRegs) {
sb.append((char) ((reg >> 8) & 0xFF));
sb.append((char) (reg & 0xFF));
}
String str = sb.toString().trim();
System.out.println("읽기的문자串:" + str);
五、完整的도구类封装
在实际项目中,建议将Modbus操作封装为도구类,便于复用和管理。
import de.re.easymodbus.modbusclient.ModbusClient;
import java.io.IOException;
public class ModbusTcpUtil {
private ModbusClient client;
private String host;
private int port;
private int unitId;
private boolean connected = false;
public ModbusTcpUtil(String host, int port, int unitId) {
this.host = host;
this.port = port;
this.unitId = unitId;
}
/**
* 建立연결(带自动重连)
*/
public synchronized boolean connect() {
try {
if (client == null) {
client = new ModbusClient(host, port);
client.setUnitIdentifier((byte) unitId);
client.setConnectionTimeout(5000);
client.setReceiveTimeout(3000);
}
if (!connected) {
client.Connect();
connected = true;
System.out.println("Modbus연결成功:" + host + ":" + port);
}
return true;
} catch (Exception e) {
System.err.println("Modbus연결失败:" + e.getMessage());
connected = false;
return false;
}
}
/**
* 断开연결
*/
public synchronized void disconnect() {
try {
if (client != null && connected) {
client.Disconnect();
connected = false;
}
} catch (Exception e) {
System.err.println("断开연결异常:" + e.getMessage());
}
}
/**
* 유지 레지스터 읽기 (FC03)(带重试)
*/
public int[] readHolding회원가입s(int addr, int 수량) {
int retry = 3;
while (retry-- > 0) {
try {
if (!connected) connect();
return client.ReadHolding회원가입s(addr, 수량);
} catch (Exception e) {
System.err.println("읽기失败,剩余重试次数:" + retry);
connected = false;
try { Thread.sleep(500); } catch (InterruptedException ie) {}
}
}
throw new RuntimeException("유지 레지스터 읽기 (FC03)失败,주소:" + addr);
}
/**
* 읽기浮点数
*/
public float readFloat(int addr) {
int[] regs = readHolding회원가입s(addr, 2);
return ModbusClient.Convert회원가입sToFloat(regs);
}
/**
* 쓰기浮点数
*/
public void writeFloat(int addr, float value) {
int[] regs = ModbusClient.ConvertFloatToTwo회원가입s(value);
writeMultiple회원가입s(addr, regs);
}
/**
* 写多个레지스터(带重试)
*/
public void writeMultiple회원가입s(int addr, int[] values) {
int retry = 3;
while (retry-- > 0) {
try {
if (!connected) connect();
client.WriteMultiple회원가입s(addr, values);
return;
} catch (Exception e) {
System.err.println("쓰기失败,剩余重试次数:" + retry);
connected = false;
try { Thread.sleep(500); } catch (InterruptedException ie) {}
}
}
throw new RuntimeException("쓰기레지스터失败,주소:" + addr);
}
/**
* 读코일
*/
public boolean[] readCoils(int addr, int 수량) {
try {
if (!connected) connect();
return client.ReadCoils(addr, 수량);
} catch (Exception e) {
connected = false;
throw new RuntimeException("读코일失败:" + e.getMessage());
}
}
/**
* 写单个코일
*/
public void writeCoil(int addr, boolean value) {
try {
if (!connected) connect();
client.WriteSingleCoil(addr, value);
} catch (Exception e) {
connected = false;
throw new RuntimeException("写코일失败:" + e.getMessage());
}
}
/**
* 检查연결状态
*/
public boolean isConnected() {
return connected;
}
}
六、实际적용예제:릴레이模块控制
public class RelayControlDemo {
public static void main(String[] args) {
// 연결到8路네트워크릴레이模块
ModbusTcpUtil modbus = new ModbusTcpUtil("192.168.1.100", 502, 1);
try {
modbus.connect();
// 1. 읽기所有릴레이状态
System.out.println("=== 当前릴레이状态 ===");
boolean[] states = modbus.readCoils(0, 8);
for (int i = 0; i < states.length; i++) {
System.out.println("릴레이" + (i+1) + ":" + (states[i] ? "吸合" : "断开"));
}
// 2. 控制第1路릴레이吸合
System.out.println("\n=== 控制第1路릴레이吸合 ===");
modbus.writeCoil(0, true);
Thread.sleep(500);
// 3. 읽기数字입력 상태
System.out.println("\n=== 数字입력 상태 ===");
boolean[] inputs = modbus.readCoils(0, 8); // 实际应使用ReadDiscreteInputs
for (int i = 0; i < inputs.length; i++) {
System.out.println("입력" + (i+1) + ":" + (inputs[i] ? "有信号" : "无信号"));
}
// 4. 읽기模块온도(假设在레지스터100)
System.out.println("\n=== 模块온도 ===");
float temp = modbus.readFloat(100);
System.out.println("온도:" + temp + "℃");
// 5. 依次吸合所有릴레이(跑马灯效果)
System.out.println("\n=== 跑马灯测试 ===");
for (int i = 0; i < 8; i++) {
modbus.writeCoil(i, true);
Thread.sleep(200);
modbus.writeCoil(i, false);
}
// 6. 전체断开
boolean[] allOff = {false, false, false, false, false, false, false, false};
// modbus.writeMultipleCoils(0, allOff); // 需要封装此方法
System.out.println("\n测试完成!");
} catch (Exception e) {
e.printStackTrace();
} finally {
modbus.disconnect();
}
}
}
七、异常处理与最佳实践
7.1 常见异常유형
| 异常 | 原因 | 解决方法 |
|---|---|---|
| ConnectException | 연결被拒绝 | 检查IP和포트,确认设备在线 |
| SocketTimeoutException | 연결或读写超时 | 추가超时时间,检查네트워크지연 |
| ModbusException | Modbus프로토콜错误 | 检查슬레이브 주소、기능 코드、레지스터주소 |
| Illegal기능Exception | 非法기능 코드 | 设备不支持该기능 코드 |
| Illegal주소Exception | 非法주소 | 읽기/쓰기주소超出设备范围 |
| IllegalValueException | 非法데이터值 | 쓰기的데이터不符合设备要求 |
7.2 最佳实践
- 使用연결池:频繁创建和销毁연결会影响性能,建议复用연결
- 추가重试机制:네트워크不稳定时,读写失败应自动重试2-3次
- 控制读写频率:避免过快轮询,建议间隔不小于100ms
- 批量读写:尽量一次읽기多个레지스터,减少通讯次数
- 异常恢复:捕获异常后重新建立연결,不要直接退出程序
- 线程安全:多线程访问时使用synchronized或锁机制
- 자료释放:使用完毕后调用Disconnect()释放연결
- 로그记录:记录每次读写的주소、데이터和耗时,便于排查문제
7.3 性能优化建议
// 优化예제:批量읽기 vs 单次읽기
// 低效:循环읽기10个레지스터
for (int i = 0; i < 10; i++) {
int val = modbusClient.ReadHolding회원가입s(i, 1)[0]; // 10次通讯
}
// 高效:一次읽기10个레지스터
int[] vals = modbusClient.ReadHolding회원가입s(0, 10); // 1次通讯
// 优化예제:合理设置超时
modbusClient.setConnectionTimeout(3000); // 연결超时3秒
modbusClient.setReceiveTimeout(2000); // 读写超时2秒
// 超时设置过短会导致频繁失败너무 길 면 프로그램이 막 힐 수 있습니다 .
다른 Java Mod bus 라이 브 러 리 비교
| 库名 | |||
|---|---|---|---|
| EasyModbus | |||
| j2mod | |||
| Modbus4J | |||
| Jamod | |||
| JLibModbus |
이 문서 에서 제공하는 코 드는 프로젝트 에서 직접 복 사 할 수 있습니다 . Mod bus Poll 과 같은 디 버 그 도구를 사용하여 장치 통신 이 정상 인지 확인 한 다음 Java 코드를 실제 프로젝트에 연결 하는 것이 좋습니다 . Mod bus R TU 직 렬 포 트 통신 을 위해 j 2 mod 또는 J Lib Mod bus 라이 브 러 리를 사용하여 R XT X 또는 j S erial C omm 을 통해 직 렬 포 트에 액세스 할 수 있습니다 .
답글 작성