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

무료무료 기술 자료

이 글은 바로 읽을 수 있으며 기초 학습과 검색 유입에 적합합니다.

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

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数字입력(이산 입력)
회원가입레지스터基类
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 와 같은보다 활동적인 라이브러리를 사용하는 것도 고려할 수 있습니다.

실제 프로젝트에 이 자료를 활용하시겠습니까?

도구 센터에서 프레임 분석, CRC 검증, 장비 디버깅을 진행하거나 요청을 제출하여 선정 및 연동 제안을 받아보세요.

엔지니어 회원

이 글을 실행 가능한 디버깅 자료로 활용하세요

가입하면 고급 프레임 분석, 자료 패키지 다운로드, 코드 예제, 엔지니어링 사례, 우선 기술 지원을 이용할 수 있어 실제 프로젝트 납품에 적합합니다.

고급 도구 무제한 이용
자료 패키지와 코드 패키지
완전한 엔지니어링 사례 라이브러리
우선 기술 지원 채널

답글 작성

이메일 주소는 공개되지 않습니다. 必填项已用 * 标注