内容目录
本文目录
- 1. 一、Jamod库概述
- 2. 二、基础Modbus TCP Server实现
- 3. 三、自定义进程映像
- 4. 四、带数据更新的服务端
- 5. 五、多从站地址支持
- 6. 六、使用Jamod客户端测试服务端
- 7. 七、常见问题与最佳实践
- 8. 八、Jamod与其他Java Modbus库对比
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 | 数字输入(离散输入) |
| Register | 寄存器基类 |
| SimpleRegister | 简单寄存器实现 |
| InputRegister | 输入寄存器 |
二、基础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.SimpleRegister;
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.addRegister(new SimpleRegister(0));
}
// 添加输入寄存器 - 16个
for (int i = 0; i < 16; i++) {
processImage.addInputRegister(new SimpleRegister(0));
}
// 2. 设置初始值
processImage.getRegister(0).setValue(100); // 温度
processImage.getRegister(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.start();
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)
- SimpleRegister:保持寄存器(功能码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++) {
addRegister(new SimpleRegister(0));
}
// 初始化输入寄存器(ADC)
for (int i = 0; i < 8; i++) {
addInputRegister(new HardwareInputRegister(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 HardwareInputRegister implements InputRegister {
private int channel;
private HardwareIO hw;
public HardwareInputRegister(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.SimpleRegister;
import net.wimpi.modbus.procimg.SimpleDigitalOut;
import net.wimpi.modbus.ModbusCoupler;
import java.util.Random;
public class DataUpdateServer {
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.addRegister(new SimpleRegister(0));
}
// 8路ADC输入寄存器
for (int i = 0; i < 8; i++) {
processImage.addInputRegister(new SimpleRegister(0));
}
// 设置初始值
processImage.getRegister(0).setValue(9600); // 波特率参数
processImage.getRegister(1).setValue(1); // 从站地址
processImage.getRegister(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.start();
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.getInputRegister(0).setValue(temp);
processImage.getInputRegister(1).setValue(humidity);
processImage.getInputRegister(2).setValue(pressure);
// 更新ADC通道
for (int i = 0; i < 8; i++) {
processImage.getInputRegister(i).setValue(random.nextInt(4096));
}
// 更新开关量输入(模拟)
for (int i = 0; i < 8; i++) {
processImage.getDigitalIn(i).set(random.nextBoolean());
}
// 检查保持寄存器中的控制命令
int cmd = processImage.getRegister(10).getValue();
if (cmd == 1) {
System.out.println("收到启动命令");
processImage.getRegister(10).setValue(0); // 清除命令
} else if (cmd == 2) {
System.out.println("收到停止命令");
processImage.getRegister(10).setValue(0);
}
Thread.sleep(1000); // 每秒更新一次
} catch (Exception e) {
e.printStackTrace();
}
}
});
updateThread.start();
// 注册关闭钩子
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.SimpleRegister;
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.addInputRegister(new SimpleRegister(250)); // 温度
img1.addInputRegister(new SimpleRegister(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.addRegister(new SimpleRegister(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.start();
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.InetAddress;
public class ModbusClientTest {
public static void main(String[] args) {
try {
// 连接到服务器
InetAddress addr = InetAddress.getByName("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=== 读保持寄存器 ===");
ReadMultipleRegistersRequest readReq =
new ReadMultipleRegistersRequest(0, 10);
readReq.setUnitID(1);
ModbusTCPTransaction trans = new ModbusTCPTransaction(connection);
trans.setRequest(readReq);
trans.execute();
ReadMultipleRegistersResponse readResp =
(ReadMultipleRegistersResponse) trans.getResponse();
for (int i = 0; i < readResp.getWordCount(); i++) {
System.out.println("寄存器" + i + ":" + readResp.getRegisterValue(i));
}
// 2. 写单个寄存器(功能码06)
System.out.println("\n=== 写单个寄存器 ===");
WriteSingleRegisterRequest writeReq =
new WriteSingleRegisterRequest(5, 1234);
writeReq.setUnitID(1);
trans.setRequest(writeReq);
trans.execute();
System.out.println("写入寄存器5:1234");
// 3. 读线圈(功能码01)
System.out.println("\n=== 读线圈 ===");
ReadCoilsRequest coilsReq = new ReadCoilsRequest(0, 8);
coilsReq.setUnitID(1);
trans.setRequest(coilsReq);
trans.execute();
ReadCoilsResponse coilsResp = (ReadCoilsResponse) trans.getResponse();
for (int i = 0; i < 8; i++) {
System.out.println("线圈" + i + ":" + coilsResp.getCoil(i));
}
// 4. 写单个线圈(功能码05)
System.out.println("\n=== 写单个线圈 ===");
WriteCoilRequest writeCoilReq = new WriteCoilRequest(0, true);
writeCoilReq.setUnitID(1);
trans.setRequest(writeCoilReq);
trans.execute();
System.out.println("线圈0置为ON");
// 5. 读输入寄存器(功能码04)
System.out.println("\n=== 读输入寄存器 ===");
ReadInputRegistersRequest inputReq =
new ReadInputRegistersRequest(0, 4);
inputReq.setUnitID(1);
trans.setRequest(inputReq);
trans.execute();
ReadInputRegistersResponse inputResp =
(ReadInputRegistersResponse) trans.getResponse();
for (int i = 0; i < inputResp.getWordCount(); i++) {
System.out.println("输入寄存器" + i + ":" + inputResp.getRegisterValue(i));
}
connection.close();
System.out.println("\n连接已关闭");
} catch (Exception e) {
e.printStackTrace();
}
}
}
七、常见问题与最佳实践
7.1 常见问题
- 端口502被占用:Linux下1024以下端口需要root权限,可使用sudo运行或改用其他端口
- 客户端连接被拒绝:检查服务器是否启动,防火墙是否开放502端口
- 数据不更新:确认进程映像中的数据是否被正确更新,SimpleProcessImage是内存存储
- 多从站不支持:Jamod的ModbusCoupler是单例,原生只支持一个从站地址
- 并发问题:多线程访问进程映像时需要注意线程安全,SimpleProcessImage基本是线程安全的
- RTU模式报错:确认RXTX库已正确安装,串口设备名和权限正确
7.2 最佳实践
- 合理设置线程数:ModbusTCPListener构造参数为处理连接的线程数,建议3-5个
- 使用关闭钩子:注册Runtime.addShutdownHook确保服务器优雅关闭
- 数据更新独立线程:传感器数据更新放在独立线程,不阻塞Modbus请求处理
- 日志记录:添加SLF4J日志,记录连接、读写、异常等信息
- 配置外部化:端口、从站地址、寄存器数量从配置文件读取
- 数据持久化:保持寄存器值定期保存到文件或数据库,重启后恢复
- 心跳检测:定期检查客户端连接状态,清理死连接
八、Jamod与其他Java Modbus库对比
| 库名 | 协议支持 | 服务端支持 | 活跃度 | 适用场景 |
|---|---|---|---|---|
| Jamod | TCP/RTU/ASCII | 支持 | 低(更新慢) | 遗留项目、稳定优先 |
| EasyModbus | TCP/RTU/UDP | 支持 | 高 | 新项目、功能丰富 |
| j2mod | TCP/RTU/ASCII | 支持 | 中 | 需要ASCII协议 |
| Modbus4J | TCP/RTU | 支持 | 中 | 复杂项目、SCADA系统 |
Jamod作为Java Modbus开发的经典库,虽然更新较慢,但稳定性和可靠性经过了多年工业现场验证。本文提供的代码可直接用于项目开发,建议先在本地测试通过,再部署到实际环境中。对于新项目,也可以考虑使用EasyModbus等更活跃的库。
发表回复