Jamod(Java Modbus)是Java生态中历史最悠久、最经典的Modbus프로토콜实现库,由Dieter Wimberger개발,广泛적용于산업 자동화、데이터采集、SCADA系统等领域。Jamod支持Modbus TCP、RTU和ASCII三种프로토콜,既可作为클라이언트也可作为服务端。本文详细介绍如何使用Jamod库实现Modbus TCP Server(슬레이브),包括환경설정、데이터存储、请求处理、多클라이언트管理以及完整的예제代码。
一、Jamod库개요
1.1 核心特性
- 多프로토콜支持:Modbus TCP、RTU、ASCII三种传输모드
- 主从兼备:既可作为클라이언트(마스터),也可作为服务端(슬레이브)
- 功能完整:支持01-16所有표준기능 코드
- 事件驱动:支持데이터变化监听、연결状态回调
- 跨平台:Windows、Linux、Mac OS均可运行
- 成熟稳定:经过多年工业现场验证,bug少,稳定性高
- 오픈소스무료:BSD프로토콜,可무료用于商业项目
1.2 Maven依赖설정
<!-- pom.xml -->
<dependency>
<groupId>net.wimpi</groupId>
<artifactId>jamod</artifactId>
<version>1.2</version>
</dependency>
<!-- 시리얼 포트支持(RTU모드需要) -->
<dependency>
<groupId>org.rxtx</groupId>
<artifactId>rxtx</artifactId>
<version>2.1.7</version>
</dependency>
1.3 核心类설명
| 类名 | 功能 |
|---|---|
| ModbusTCPListener | TCP监听器,接受클라이언트연결 |
| ModbusCoupler | Modbus耦合器,管理데이터存储 |
| SimpleProcessImage | 简单进程映像,存储레지스터和코일데이터 |
| DigitalOut | 数字출력(코일) |
| DigitalIn | 数字입력(이산 입력) |
| 회원가입 | 레지스터基类 |
| Simple회원가입 | 简单레지스터实现 |
| Input회원가입 | 입력 레지스터 |
二、基础Modbus TCP Server实现
2.1 最简服务端
import net.wimpi.modbus.Modbus;
import net.wimpi.modbus.net.ModbusTCPListener;
import net.wimpi.modbus.procimg.SimpleProcessImage;
import net.wimpi.modbus.procimg.Simple회원가입;
import net.wimpi.modbus.procimg.SimpleDigitalOut;
import net.wimpi.modbus.procimg.SimpleDigitalIn;
import net.wimpi.modbus.ModbusCoupler;
public class SimpleModbusServer {
public static void main(String[] args) {
try {
// 1. 创建进程映像(데이터存储)
SimpleProcessImage processImage = new SimpleProcessImage();
// 추가数字출력(코일)- 16个
for (int i = 0; i < 16; i++) {
processImage.addDigitalOut(new SimpleDigitalOut(false));
}
// 추가数字입력(이산 입력)- 16个
for (int i = 0; i < 16; i++) {
processImage.addDigitalIn(new SimpleDigitalIn(false));
}
// 추가유지 레지스터 - 32个
for (int i = 0; i < 32; i++) {
processImage.add회원가입(new Simple회원가입(0));
}
// 추가입력 레지스터 - 16个
for (int i = 0; i < 16; i++) {
processImage.addInput회원가입(new Simple회원가입(0));
}
// 2. 设置初始值
processImage.get회원가입(0).setValue(100); // 온도
processImage.get회원가입(1).setValue(50); // 湿度
processImage.getDigitalOut(0).set(true); // 릴레이1
// 3. 创建Modbus耦合器并设置进程映像
ModbusCoupler.getReference().setProcessImage(processImage);
ModbusCoupler.getReference().setMaster(false); // 슬레이브모드
ModbusCoupler.getReference().setUnitID(1); // 슬레이브 주소
// 4. 创建TCP监听器
ModbusTCPListener listener = new ModbusTCPListener(3); // 3个线程处理연결
listener.setPort(502);
listener.시작();
System.out.println("Modbus TCP Server已启动,监听포트:502");
System.out.println("슬레이브 주소:1");
System.out.println("코일:16个,이산 입력:16个");
System.out.println("유지 레지스터:32个,입력 레지스터:16个");
// 保持主线程运行
Thread.currentThread().join();
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.2 代码설명
- SimpleProcessImage:进程映像,存储所有Modbus데이터(코일、입력、레지스터)
- SimpleDigitalOut:数字출력,对应Modbus코일(기능 코드01/05/15)
- SimpleDigitalIn:数字입력,对应이산 입력(기능 코드02)
- Simple회원가입:유지 레지스터(기능 코드03/06/16)和입력 레지스터(기능 코드04)
- ModbusCoupler:单例모드,全局管理进程映像和슬레이브설정
- ModbusTCPListener:TCP监听器,매개변수为处理연결的线程数
三、自定义进程映像
在实际项目中,通常需要自定义进程映像,将Modbus데이터与实际硬件IO关联起来。以下예제展示如何创建自定义进程映像,实现데이터与硬件的联动。
import net.wimpi.modbus.procimg.*;
public class CustomProcessImage extends SimpleProcessImage {
// 硬件인터페이스引用(예제)
private HardwareIO hardware;
public CustomProcessImage(HardwareIO hw) {
super();
this.hardware = hw;
// 初始化数字출력
for (int i = 0; i < 8; i++) {
addDigitalOut(new HardwareDigitalOut(i, hw));
}
// 初始化数字입력
for (int i = 0; i < 8; i++) {
addDigitalIn(new HardwareDigitalIn(i, hw));
}
// 初始化유지 레지스터
for (int i = 0; i < 32; i++) {
add회원가입(new Simple회원가입(0));
}
// 初始化입력 레지스터(ADC)
for (int i = 0; i < 8; i++) {
addInput회원가입(new HardwareInput회원가입(i, hw));
}
}
/**
* 自定义数字출력:쓰기时同步控制硬件릴레이
*/
class HardwareDigitalOut implements DigitalOut {
private int channel;
private HardwareIO hw;
private boolean state = false;
public HardwareDigitalOut(int channel, HardwareIO hw) {
this.channel = channel;
this.hw = hw;
}
@Override
public boolean isSet() {
return state;
}
@Override
public void set(boolean b) {
state = b;
hw.setRelay(channel, b); // 控制硬件릴레이
}
}
/**
* 自定义数字입력:읽기时从硬件获取状态
*/
class HardwareDigitalIn implements DigitalIn {
private int channel;
private HardwareIO hw;
public HardwareDigitalIn(int channel, HardwareIO hw) {
this.channel = channel;
this.hw = hw;
}
@Override
public boolean isSet() {
return hw.getInput(channel); // 읽기硬件입력 상태
}
}
/**
* 自定义입력 레지스터:읽기时从ADC获取值
*/
class HardwareInput회원가입 implements Input회원가입 {
private int channel;
private HardwareIO hw;
public HardwareInput회원가입(int channel, HardwareIO hw) {
this.channel = channel;
this.hw = hw;
}
@Override
public int getValue() {
return hw.readADC(channel); // 읽기ADC值
}
@Override
public int toUnsignedShort() {
return getValue();
}
@Override
public short toShort() {
return (short) getValue();
}
@Override
public byte[] toBytes() {
int val = getValue();
return new byte[]{(byte)(val >> 8), (byte)(val & 0xFF)};
}
}
}
// 硬件인터페이스(예제)
interface HardwareIO {
void setRelay(int channel, boolean state);
boolean getInput(int channel);
int readADC(int channel);
}
四、带데이터更新的服务端
import net.wimpi.modbus.net.ModbusTCPListener;
import net.wimpi.modbus.procimg.SimpleProcessImage;
import net.wimpi.modbus.procimg.Simple회원가입;
import net.wimpi.modbus.procimg.SimpleDigitalOut;
import net.wimpi.modbus.ModbusCoupler;
import java.util.Random;
public class 데이터UpdateServer {
private static SimpleProcessImage processImage;
private static volatile boolean running = true;
public static void main(String[] args) {
try {
// 创建进程映像
processImage = new SimpleProcessImage();
// 8路릴레이출력
for (int i = 0; i < 8; i++) {
processImage.addDigitalOut(new SimpleDigitalOut(false));
}
// 8路开关量입력
for (int i = 0; i < 8; i++) {
processImage.addDigitalIn(new SimpleDigitalIn(false));
}
// 32个유지 레지스터
for (int i = 0; i < 32; i++) {
processImage.add회원가입(new Simple회원가입(0));
}
// 8路ADC입력 레지스터
for (int i = 0; i < 8; i++) {
processImage.addInput회원가입(new Simple회원가입(0));
}
// 设置初始值
processImage.get회원가입(0).setValue(9600); // 통신 속도매개변수
processImage.get회원가입(1).setValue(1); // 슬레이브 주소
processImage.get회원가입(2).setValue(250); // 온도设定值
// 设置耦合器
ModbusCoupler.getReference().setProcessImage(processImage);
ModbusCoupler.getReference().setMaster(false);
ModbusCoupler.getReference().setUnitID(1);
// 启动TCP监听器
ModbusTCPListener listener = new ModbusTCPListener(5);
listener.setPort(502);
listener.시작();
System.out.println("Modbus TCP Server已启动,포트:502");
// 启动데이터更新线程
Thread updateThread = new Thread(() -> {
Random random = new Random();
while (running) {
try {
// 模拟센서데이터更新
int temp = 200 + random.nextInt(150); // 20.0-35.0℃
int humidity = 400 + random.nextInt(300); // 40.0-70.0%
int pressure = 1000 + random.nextInt(50); // 1000-1050hPa
processImage.getInput회원가입(0).setValue(temp);
processImage.getInput회원가입(1).setValue(humidity);
processImage.getInput회원가입(2).setValue(pressure);
// 更新ADC通道
for (int i = 0; i < 8; i++) {
processImage.getInput회원가입(i).setValue(random.nextInt(4096));
}
// 更新开关量입력(模拟)
for (int i = 0; i < 8; i++) {
processImage.getDigitalIn(i).set(random.nextBoolean());
}
// 检查유지 레지스터中的控制命令
int cmd = processImage.get회원가입(10).getValue();
if (cmd == 1) {
System.out.println("收到启动命令");
processImage.get회원가입(10).setValue(0); // 清除命令
} else if (cmd == 2) {
System.out.println("收到정지命令");
processImage.get회원가입(10).setValue(0);
}
Thread.sleep(1000); // 每秒更新一次
} catch (Exception e) {
e.printStackTrace();
}
}
});
updateThread.시작();
// 회원가입닫기钩子
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
running = false;
listener.stop();
System.out.println("服务器已정지");
}));
Thread.currentThread().join();
} catch (Exception e) {
e.printStackTrace();
}
}
}
五、多슬레이브 주소支持
import net.wimpi.modbus.net.ModbusTCPListener;
import net.wimpi.modbus.procimg.SimpleProcessImage;
import net.wimpi.modbus.procimg.Simple회원가입;
import net.wimpi.modbus.ModbusCoupler;
import java.util.HashMap;
import java.util.Map;
public class MultiUnitServer {
public static void main(String[] args) {
try {
// 创建多个进程映像,对应不同슬레이브 주소
Map<Integer, SimpleProcessImage> images = new HashMap<>();
// 슬레이브1:온도 센서
SimpleProcessImage img1 = new SimpleProcessImage();
img1.addInput회원가입(new Simple회원가입(250)); // 온도
img1.addInput회원가입(new Simple회원가입(600)); // 湿度
images.put(1, img1);
// 슬레이브2:릴레이模块
SimpleProcessImage img2 = new SimpleProcessImage();
for (int i = 0; i < 8; i++) {
img2.addDigitalOut(new SimpleDigitalOut(false));
}
images.put(2, img2);
// 슬레이브3:PLC
SimpleProcessImage img3 = new SimpleProcessImage();
for (int i = 0; i < 16; i++) {
img3.add회원가입(new Simple회원가입(0));
}
images.put(3, img3);
// 注意:Jamod的ModbusCoupler是单例,只支持一个슬레이브 주소
// 如需多슬레이브,需要自定义ModbusTCPListener或使用其他库
// 此处以슬레이브1为例
ModbusCoupler.getReference().setProcessImage(img1);
ModbusCoupler.getReference().setMaster(false);
ModbusCoupler.getReference().setUnitID(1);
ModbusTCPListener listener = new ModbusTCPListener(3);
listener.setPort(502);
listener.시작();
System.out.println("多슬레이브服务器已启动");
System.out.println("슬레이브1:온도 센서");
System.out.println("슬레이브2:릴레이模块");
System.out.println("슬레이브3:PLC");
Thread.currentThread().join();
} catch (Exception e) {
e.printStackTrace();
}
}
}
六、使用Jamod클라이언트测试服务端
import net.wimpi.modbus.Modbus;
import net.wimpi.modbus.io.ModbusTCPTransaction;
import net.wimpi.modbus.msg.*;
import net.wimpi.modbus.net.TCPMasterConnection;
import java.net.Inet주소;
public class ModbusClientTest {
public static void main(String[] args) {
try {
// 연결到服务器
Inet주소 addr = Inet주소.getBy이름("127.0.0.1");
TCPMasterConnection connection = new TCPMasterConnection(addr);
connection.setPort(502);
connection.connect();
System.out.println("已연결到服务器");
// 设置슬레이브 주소
connection.setTimeout(3000);
// 1. 读유지 레지스터(기능 코드03)
System.out.println("\n=== 读유지 레지스터 ===");
ReadMultiple회원가입sRequest readReq =
new ReadMultiple회원가입sRequest(0, 10);
readReq.setUnitID(1);
ModbusTCPTransaction trans = new ModbusTCPTransaction(connection);
trans.setRequest(readReq);
trans.execute();
ReadMultiple회원가입sResponse readResp =
(ReadMultiple회원가입sResponse) trans . get Res ponse () ;
for (int i = 0; i
System . out . print ln (" 레 지 스터 " + i + " :" + read R esp . get Reg ister V alue (i));
}
/ / 2.단일 레 지 스터 쓰 기 (기 능 코드 0 6)
System . out . print ln (" \ n == = 단일 레 지 스터 를 쓰 기 == =");
Write Sing le Reg ister Re quest Write Re q =
새로운 Write Sing le Reg ister Re quest (5, 123 4) ;
write Re q . set Unit ID (1) ;
trans . set Re quest (wr ite Re q)
trans . execu te () ;
System . out . print ln (" 기 입 레 지 스터 5: 12 34 ");
// 3.읽기 코 일 (기 능 코드 0 1)
System . out . print ln ( " \ n == = 읽기 코 일 == =");
Read Co ils Re quest co ils Re q = new Read Co ils Re quest (0, 8);
co ils Re q . set Unit ID (1) ;
trans . set Re quest (co ils Re q);
trans . execu te () ;
Read Co ils Res ponse co ils R esp = (Read Co ils Res ponse) trans . get Res ponse ();
for (int i = 0; i
System . out . print ln (" Co il " + i + " :" + co ils R esp . get Co il (i));
}
/ / 4개별 코 일 쓰 기 (기 능 코드 0 5)
System . out . print ln ( " \ n == = 단일 코 일 작성 == =);
Write Co il Re quest write Co il Re q = new Write Co il Re quest (0, true);
write Co il Re q . set Unit ID (1) ;
trans . set Re quest (wr ite Co il Re q);
trans . execu te () ;
System . out . print ln (" 코 일 0 설정 ");
/ / 5.읽기 입력 레 지 스터 (기 능 코드 0 4)
System . out . print ln ( " \ n == = 입력 레 지 스터 읽기 == =);
Read In put Reg ister s Re quest input Re q =
새 Read In put Reg ister s Re quest (0, 4);
input Re q . set Unit ID (1) ;
trans . set Re quest (in put Re q)
trans . execu te () ;
Read In put Reg ister s Res ponse input Res p =
(Read In put Reg ister s Res ponse) trans . get Res ponse ();
for (int i = 0; i
System . out . print ln (" 입 력 레 지 스터 " + i + " :" + input R esp . get Reg ister V alue (i));
}
connection . close ();
System . out . print ln (" \ n 연결 이 닫 혔 습니다 ");
} catch (Ex ception e) {
e . print Sta ck Tra ce () ;
}
}
}
일반적인 질문 과 모 범 사례
7. 1 자주 묻는 질문
- 포트502被占用
- 클라이언트연결被拒绝
- 데이터不更新
- 多슬레이브不支持
- 并发문제
- RTU모드报错
7. 2 모 범 사례
- 合理设置线程数
- 使用닫기钩子
- 데이터更新独立线程
- 로그记录
- 설정外部化
- 데이터持久化
- 心跳检测
Jam od 와 다른 Java Mod bus 라이 브 러 리 비교
| 库名 | ||||
|---|---|---|---|---|
| Jamod | ||||
| EasyModbus | ||||
| j2mod | ||||
| Modbus4J |
Java Mod bus 에서 개발 된 고 전 적인 라이 브 러 리 인 Jam od 는 업데이트 가 느 리 지만 안정 성과 신뢰 성은 수 년간 업 계 현 장에서 검 증 되었습니다 .이 문서 에서 제공 되는 코 드는프로젝트 개발에 직접 사용되며 실제 환경에 배포하기 전에 로컬 테스트를 통과하는 것이 좋습니다.새로운 프로젝트의 경우 Easy Modbus 와 같은보다 활동적인 라이브러리를 사용하는 것도 고려할 수 있습니다.
답글 작성