Jamod 라이브러리를 사용하여 Java 기반 Modbus TCP 서버 구현 예제 코드

freeFree Technical Resource

This content is free to read, suitable for basic learning and search traffic.

Jamod 라이브러리를 사용하여 Java 기반 Modbus TCP 서버 구현 예제 코드

Jamod(Java Modbus)是Java生态中历史最悠久、最经典的Modbusプロトコル実装库,由Dieter Wimberger开发,广泛应用于工业自動化、Data Acquisition、SCADA系统等领域。JamodサポートModbus TCP、RTU和ASCII三种协议,既可作为クライアント側は也可作为サーバー側。。本記事详细介绍使い方は?Jamod库実装Modbus TCP Server(slave),包括环境設定、データストア、リクエスト処理、多クライアント側は管理以及完全的サンプルコード。

一、Jamod库概述

1.1 核心特性

  • 多协议サポート:Modbus TCP、RTU、ASCII3つの伝送モード
  • 主从兼备:既可作为クライアント側は(master),也可作为サーバー側。(slave)
  • 功能完全:サポート01-16すべての標準機能コード
  • 事件驱动:サポートデータ变化监听、连接ステータス回调
  • 跨プラットフォーム:Windows、Linux、Mac OS均可运行
  • 成熟稳定:经过多年工业现场验证,bug少,稳定性高
  • 开源無料:BSDProtocol,可無料用于商业项目

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 核心类説明

类名Function
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);  // temperature
            processImage.getRegister(1).setValue(50);   // 湿度
            processImage.getDigitalOut(0).set(true);    // Relay1
            
            // 3. 作成Modbus耦合器并設定进程映像
            ModbusCoupler.getReference().setProcessImage(processImage);
            ModbusCoupler.getReference().setMaster(false);  // 駅からのモデル
            ModbusCoupler.getReference().setUnitID(1);       // slave address
            
            // 4. 作成TCP监听器
            ModbusTCPListener listener = new ModbusTCPListener(3);  // 3个线程処理连接
            listener.setPort(502);
            listener.start();
            
            System.out.println("Modbus TCP Server已启动,监听ポート:502");
            System.out.println("slave address:1");
            System.out.println("コイル:16个,ショップ型入力:16个");
            System.out.println("レジスタを保持する。:32个,入力レジスタ。:16个");
            
            // 保持主线程运行
            Thread.currentThread().join();
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.2 代码説明

  • SimpleProcessImage:进程映像,存储すべてのModbusdata(コイル、入力、register)
  • SimpleDigitalOut:数字出力,对应Modbusコイル(function code01/05/15)
  • SimpleDigitalIn:数字入力,对应ショップ型入力(function code02)
  • SimpleRegister:レジスタを保持する。(function code03/06/16)和入力レジスタ。(function code04)
  • ModbusCoupler:单例モード,全局管理进程映像和ステーションからの設定
  • ModbusTCPListener:TCP监听器,参数为処理连接的线程数

三、自定义进程映像

在实际项目中,通常需要自定义进程映像,将Modbusデータ与实际硬件IO关联起来。以下サンプル表示如何作成自定义进程映像,実装データ与硬件的連動。

import net.wimpi.modbus.procimg.*;

public class CustomProcessImage extends SimpleProcessImage {
    
    // 硬件接口引用(example)
    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);  // readADC值
        }
        
        @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)};
        }
    }
}

// 硬件接口(example)
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);     // slave address
            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<>();
            
            // slave1:Temperature Sensor
            SimpleProcessImage img1 = new SimpleProcessImage();
            img1.addInputRegister(new SimpleRegister(250));  // temperature
            img1.addInputRegister(new SimpleRegister(600));  // 湿度
            images.put(1, img1);
            
            // slave2:继电器モジュール
            SimpleProcessImage img2 = new SimpleProcessImage();
            for (int i = 0; i < 8; i++) {
                img2.addDigitalOut(new SimpleDigitalOut(false));
            }
            images.put(2, img2);
            
            // slave3: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("slave1:Temperature Sensor");
            System.out.println("slave2:继电器モジュール");
            System.out.println("slave3: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. read holding registers(function code03)
            System.out.println("\n=== read holding registers ===");
            ReadMultipleRegistersRequest readReq = 
                new ReadMultipleRegistersRequest(0, 10);
            readReq.setUnitID(1);
            
            ModbusTCPTransaction trans = new ModbusTCPTransaction(connection);
            trans.setRequest(readReq);
            trans.execute();
            
            ReadMultipleRegistersResponse readResp = 
                (ReadMultipleRegistersResponse) 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 와 같은보다 활동적인 라이브러리를 사용하는 것도 고려할 수 있습니다.

Put this resource to use in a real project?

Go to the Tool Center for message parsing, CRC verification and device debugging, or submit your requirements for selection and integration advice.

Engineer Membership

Turn this article into actionable debugging resources

After activation, you can use advanced message parsing, resource pack downloads, code examples, engineering cases and priority technical support, suitable for real project delivery.

Unlimited Advanced Tools
Resource & Code Packs
Complete Engineering Case Library
Priority Technical Support

Leave a Reply

Your email address will not be published. Required fields are marked *.