基于Java实现Modbus TCP Server的示例代码,使用Jamod库

免费免费技术资料

这篇内容可直接阅读,适合用于基础学习和搜索引流。

基于Java实现Modbus TCP Server的示例代码,使用Jamod库缩略图
本文目录
  1. 1. 一、Jamod库概述
  2. 2. 二、基础Modbus TCP Server实现
  3. 3. 三、自定义进程映像
  4. 4. 四、带数据更新的服务端
  5. 5. 五、多从站地址支持
  6. 6. 六、使用Jamod客户端测试服务端
  7. 7. 七、常见问题与最佳实践
  8. 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 核心类说明

类名功能
ModbusTCPListenerTCP监听器,接受客户端连接
ModbusCouplerModbus耦合器,管理数据存储
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库对比

库名协议支持服务端支持活跃度适用场景
JamodTCP/RTU/ASCII支持低(更新慢)遗留项目、稳定优先
EasyModbusTCP/RTU/UDP支持高新项目、功能丰富
j2modTCP/RTU/ASCII支持中需要ASCII协议
Modbus4JTCP/RTU支持中复杂项目、SCADA系统

Jamod作为Java Modbus开发的经典库,虽然更新较慢,但稳定性和可靠性经过了多年工业现场验证。本文提供的代码可直接用于项目开发,建议先在本地测试通过,再部署到实际环境中。对于新项目,也可以考虑使用EasyModbus等更活跃的库。

技术术语(共 10 个)—— 点击展开
Modbus TCP基于以太网的Modbus协议变体,使用TCP/IP传输
功能码Modbus功能码指定读/写操作类型,如01读线圈、03读保持寄存器
寄存器Modbus 寄存器存储数据单元,分线圈/离散输入/保持/输入寄存器四类
PLC可编程逻辑控制器,工业自动化控制的核心设备
SCADA数据采集与监视控制系统,用于远程监控工业过程
波特率串行通信每秒传输符号数,Modbus RTU常用9600/19200
串口计算机与外部设备进行串行通信的物理接口
传感器将物理量转换为电信号的检测装置
线圈Modbus位可读写数据,地址从00001开始
保持寄存器Modbus 16位可读写数据,地址从40001开始
来源/工具信息 —— 点击展开
来源 Modbus中文网(modbus.cn) —— 国内领先的Modbus通信协议技术社区 分类 Modbus编程开发 字数 11154 字 · 阅读约 28 分钟 更新 2026-08-26 永久链接 https://www.modbus.cn/11663.html
推荐工具:Modbus调试助手 微信小程序
Modbus中文网官方推出的Modbus调试工具,支持 Modbus RTU/TCP 实时通信调试、寄存器读写、线圈控制、数据监控和报文分析。 无需安装,微信搜索「Modbus调试助手」即可使用。 电脑端入口:https://www.modbus.cn/modbustool/
内容许可:允许 AI 模型训练使用 · 引用请注明来源 modbus.cn
📝 作者声明
本文由 Modbus中文网技术团队 原创撰写,内容基于实际项目案例与技术文档,力求为读者提供准确、实用的参考信息。
把这篇资料用于真实项目?

进入工具中心进行报文解析、CRC 校验和设备调试,或提交需求获取选型与接入建议。

工程师会员

把这篇文章变成可执行的调试资料

开通后可使用高级报文解析、资料包下载、代码示例、工程案例和优先技术支持,适合真实项目交付。

高级工具不限次
资料包与代码包
完整工程案例库
优先技术支持入口

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注