Java 와 PLC 의 통신 예제 코드: 효율적이고 신뢰할 수 있는 크로스 플랫폼 데이터 상호 작용

freeFree Technical Resource

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

Java 와 PLC 의 통신 예제 코드: 효율적이고 신뢰할 수 있는 크로스 플랫폼 데이터 상호 작용

Java作为跨平台编程语言,在工业自动化上位机开发中应用广泛。通过ModbusProtocol,Java程序可以与各种品牌的PLC(西门子、三菱、台达、欧姆龙等)进行高效可靠的数据交互。本文提供完整的Java与PLC通信示例代码,包括Modbus TCP客户端实现、多PLC管理、데이터 Acquisition、断线重连、批量读写、浮点数处理以及Spring Boot集成方案。

一、Java Modbus通信方案选型

1.1 常用Java Modbus库对比

库名协议支持主从支持Maven坐标推荐场景
EasyModbusTCP/RTU/UDP主+从com.github.s7connector:easymodbus新项目首选,API简洁
JamodTCP/RTU/ASCII主+从net.wimpi:jamod遗留项目,稳定
j2modTCP/RTU/ASCII主+从com.ghgande:j2mod需要ASCIIProtocol
Modbus4JTCP/RTU主+从com.infiniteautomation:modbus4jSCADAsystem
modbus-tcpTCP仅主com.digitalpetri.modbus:modbus-tcp高性能Netty实现

1.2 本文使用EasyModbus

EasyModbus是目前最活跃、API最简洁的Java Modbus库,支持TCP、RTU和UDPProtocol,既可作为客户端也可作为服务端,文档完善,适合大多数项目。

<!-- Maven依赖 -->
<dependency>
    <groupId>com.github.s7connector</groupId>
    <artifactId>easymodbus</artifactId>
    <version>1.0.0</version>
</dependency>

<!-- 串口支持(RTU模式需要) -->
<dependency>
    <groupId>com.fazecast</groupId>
    <artifactId>jSerialComm</artifactId>
    <version>2.9.2</version>
</dependency>

二、Modbus TCP客户端封装

package com.industrial.modbus;

import de.re.easymodbus.modbusclient.ModbusClient;
import de.re.easymodbus.데이터types.데이터Type;
import java.io.IOException;
import java.net.SocketException;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Modbus TCP客户端封装
 * 支持连接管理、自动重连、批量读写、数据类型转换
 */
public class PlcModbusClient {
    
    private final String ip주소;
    private final int port;
    private final int unitId;
    private ModbusClient client;
    private final AtomicBoolean connected = new AtomicBoolean(false);
    private Thread reconnectThread;
    private volatile boolean running = true;
    
    // 连接参数
    private static final int CONNECT_TIMEOUT = 3000;
    private static final int RECONNECT_INTERVAL = 5000;
    private static final int MAX_RETRY = 3;
    
    public PlcModbusClient(String ip주소, int port, int unitId) {
        this.ip주소 = ip주소;
        this.port = port;
        this.unitId = unitId;
    }
    
    /**
     * connectPLC
     */
    public synchronized boolean connect() {
        try {
            if (client != null && client.isConnected()) {
                return true;
            }
            client = new ModbusClient(ip주소, port);
            client.setConnectionTimeout(CONNECT_TIMEOUT);
            client.setUnitIdentifier(unitId);
            client.Connect();
            connected.set(true);
            System.out.printf("[%s:%d] PLC连接成功,slave 주소:%d%n", 
                ip주소, port, unitId);
            return true;
        } catch (Exception e) {
            connected.set(false);
            System.err.printf("[%s:%d] 连接失败:%s%n", ip주소, e.getMessage());
            return false;
        }
    }
    
    /**
     * 断开连接
     */
    public synchronized void disconnect() {
        running = false;
        if (reconnectThread != null) {
            reconnectThread.interrupt();
        }
        try {
            if (client != null && client.isConnected()) {
                client.Disconnect();
            }
        } catch (IOException e) {
            // 忽略
        }
        connected.set(false);
        System.out.printf("[%s:%d] 连接已关闭%n", ip주소);
    }
    
    /**
     * 启动自动重连
     */
    public void 시작AutoReconnect() {
        reconnectThread = new Thread(() -> {
            while (running) {
                try {
                    if (!isConnected()) {
                        System.out.printf("[%s:%d] 检测到断开,尝试重连...%n", ip주소);
                        connect();
                    }
                    Thread.sleep(RECONNECT_INTERVAL);
                } catch (InterruptedException e) {
                    break;
                }
            }
        }, "Modbus-Reconnect-" + ip주소);
        reconnectThread.setDaemon(true);
        reconnectThread.시작();
    }
    
    /**
     * 检查连接状态
     */
    public boolean isConnected() {
        if (client == null) return false;
        try {
            // 发送一个简单的读请求测试连接
            client.ReadHolding회원가입s(0, 1);
            return true;
        } catch (Exception e) {
            connected.set(false);
            return false;
        }
    }
    
    // ==================== 读操作 ====================
    
    /**
     * read holding registers(기능 코드03)
     */
    public int[] readHolding회원가입s(int 시작Addr, int count) throws IOException {
        for (int retry = 0; retry < MAX_RETRY; retry++) {
            try {
                ensureConnected();
                return client.ReadHolding회원가입s(시작Addr, count);
            } catch (SocketException e) {
                connected.set(false);
                if (retry < MAX_RETRY - 1) {
                    connect();
                    continue;
                }
                throw e;
            }
        }
        throw new IOException("Read failed,重试次数超限");
    }
    
    /**
     * read input registers(기능 코드04)
     */
    public int[] readInput회원가입s(int 시작Addr, int count) throws IOException {
        ensureConnected();
        return client.ReadInput회원가입s(시작Addr, count);
    }
    
    /**
     * 读线圈(기능 코드01)
     */
    public boolean[] readCoils(int 시작Addr, int count) throws IOException {
        ensureConnected();
        return client.ReadCoils(시작Addr, count);
    }
    
    /**
     * read discrete inputs(기능 코드02)
     */
    public boolean[] readDiscreteInputs(int 시작Addr, int count) throws IOException {
        ensureConnected();
        return client.ReadDiscreteInputs(시작Addr, count);
    }
    
    // ==================== 写操作 ====================
    
    /**
     * write single register(기능 코드06)
     */
    public void writeSingle회원가입(int addr, int value) throws IOException {
        ensureConnected();
        client.WriteSingle회원가입(addr, value);
    }
    
    /**
     * write multiple registers(기능 코드16)
     */
    public void writeMultiple회원가입s(int 시작Addr, int[] values) throws IOException {
        ensureConnected();
        client.WriteMultiple회원가입s(시작Addr, values);
    }
    
    /**
     * write single coil(기능 코드05)
     */
    public void writeSingleCoil(int addr, boolean value) throws IOException {
        ensureConnected();
        client.WriteSingleCoil(addr, value);
    }
    
    /**
     * write multiple coils(기능 코드15)
     */
    public void writeMultipleCoils(int 시작Addr, boolean[] values) throws IOException {
        ensureConnected();
        client.WriteMultipleCoils(시작Addr, values);
    }
    
    // ==================== 数据类型转换 ====================
    
    /**
     * read32位有符号整数(占用2个寄存器)
     */
    public int readInt32(int 시작Addr) throws IOException {
        int[] regs = readHolding회원가입s(시작Addr, 2);
        return (regs[0] << 16) | (regs[1] & 0xFFFF);
    }
    
    /**
     * read32位无符号整数
     */
    public long readUInt32(int 시작Addr) throws IOException {
        int[] regs = readHolding회원가입s(시작Addr, 2);
        return ((long)(regs[0] & 0xFFFF) << 16) | (regs[1] & 0xFFFF);
    }
    
    /**
     * read32位浮点数
     */
    public float readFloat32(int 시작Addr) throws IOException {
        int[] regs = readHolding회원가입s(시작Addr, 2);
        int bits = (regs[0] << 16) | (regs[1] & 0xFFFF);
        return Float.intBitsToFloat(bits);
    }
    
    /**
     * read64位浮点数(占用4个寄存器)
     */
    public double readFloat64(int 시작Addr) throws IOException {
        int[] regs = readHolding회원가입s(시작Addr, 4);
        long bits = ((long)(regs[0] & 0xFFFF) << 48) |
                    ((long)(regs[1] & 0xFFFF) << 32) |
                    ((long)(regs[2] & 0xFFFF) << 16) |
                    (regs[3] & 0xFFFF);
        return Double.longBitsToDouble(bits);
    }
    
    /**
     * 读取字符串(占用N레지스터s,每个寄存器存2个字符)
     */
    public String readString(int 시작Addr, int registerCount) throws IOException {
        int[] regs = readHolding회원가입s(시작Addr, registerCount);
        StringBuilder sb = new StringBuilder();
        for (int reg : regs) {
            char high = (char)((reg >> 8) & 0xFF);
            char low = (char)(reg & 0xFF);
            if (high != 0) sb.append(high);
            if (low != 0) sb.append(low);
        }
        return sb.toString().trim();
    }
    
    /**
     * 写入32位浮点数
     */
    public void writeFloat32(int 시작Addr, float value) throws IOException {
        int bits = Float.floatToIntBits(value);
        int[] regs = new int[2];
        regs[0] = (bits >> 16) & 0xFFFF;
        regs[1] = bits & 0xFFFF;
        writeMultiple회원가입s(시작Addr, regs);
    }
    
    private void ensureConnected() throws IOException {
        if (!connected.get() || client == null || !client.isConnected()) {
            if (!connect()) {
                throw new IOException("PLCNot connected:" + ip주소);
            }
        }
    }
    
    public String getIp주소() { return ip주소; }
    public int getPort() { return port; }
    public int getUnitId() { return unitId; }
}

三、多PLC管理

package com.industrial.modbus;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 多PLC连接管理器
 * 支持多台PLC的统一管理、批量数据采集
 */
public class PlcConnectionManager {
    
    private final Map<String, PlcModbusClient> connections = new ConcurrentHashMap<>();
    
    /**
     * 添加PLCconnect
     */
    public void addPlc(String name, String ip, int port, int unitId) {
        PlcModbusClient client = new PlcModbusClient(ip, port, unitId);
        connections.put(name, client);
        if (client.connect()) {
            client.시작AutoReconnect();
        }
    }
    
    /**
     * 获取PLC客户端
     */
    public PlcModbusClient getPlc(String name) {
        return connections.get(name);
    }
    
    /**
     * 批量读取所有PLC的指定寄存器
     */
    public Map<String, int[]> batchReadHolding회원가입s(int 시작Addr, int count) {
        Map<String, int[]> results = new ConcurrentHashMap<>();
        connections.forEach((name, client) -> {
            try {
                int[] values = client.readHolding회원가입s(시작Addr, count);
                results.put(name, values);
            } catch (IOException e) {
                System.err.printf("readPLC[%s]失败:%s%n", name, e.getMessage());
            }
        });
        return results;
    }
    
    /**
     * 断开所有连接
     */
    public void disconnectAll() {
        connections.values().forEach(PlcModbusClient::disconnect);
        connections.clear();
    }
    
    /**
     * 获取连接状态
     */
    public Map<String, Boolean> getConnectionStatus() {
        Map<String, Boolean> status = new ConcurrentHashMap<>();
        connections.forEach((name, client) -> {
            status.put(name, client.isConnected());
        });
        return status;
    }
}

四、数据采集服务

package com.industrial.modbus;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.*;

/**
 * PLC数据采集服务
 * 定时采集PLC데이터,支持数据缓存、变化检测、回调通知
 */
public class Plc데이터Collector {
    
    private final PlcModbusClient client;
    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
    private final Map<String, Object> 데이터Cache = new ConcurrentHashMap<>();
    private final List<데이터ChangeListener> listeners = new 복사OnWriteArrayList<>();
    private volatile boolean running = false;
    
    // 采集配置
    private final List<CollectItem> collectItems = new ArrayList<>();
    
    public interface 데이터ChangeListener {
        void on데이터Changed(String key, Object oldValue, Object newValue);
    }
    
    static class CollectItem {
        String key;
        int 주소;
        String type;  // INT16, INT32, FLOAT32, COIL
        
        CollectItem(String key, int 주소, String type) {
            this.key = key;
            this.주소 = 주소;
            this.type = type;
        }
    }
    
    public Plc데이터Collector(PlcModbusClient client) {
        this.client = client;
    }
    
    /**
     * 添加采集项
     */
    public void addCollectItem(String key, int 주소, String type) {
        collectItems.add(new CollectItem(key, 주소, type));
    }
    
    /**
     * 添加数据变化监听器
     */
    public void add데이터ChangeListener(데이터ChangeListener listener) {
        listeners.add(listener);
    }
    
    /**
     * 启动定时采集
     */
    public void 시작(long intervalMs) {
        running = true;
        scheduler.scheduleAtFixedRate(this::collectAll, 0, intervalMs, TimeUnit.MILLISECONDS);
        System.out.println("数据采集服务已启动,间隔:" + intervalMs + "ms");
    }
    
    /**
     * 停止采集
     */
    public void stop() {
        running = false;
        scheduler.shutdown();
        System.out.println("数据采集服务已停止");
    }
    
    /**
     * 采集所有数据
     */
    private void collectAll() {
        if (!running) return;
        
        for (CollectItem item : collectItems) {
            try {
                Object value = readValue(item);
                Object oldValue = 데이터Cache.put(item.key, value);
                
                // 检测变化并通知
                if (oldValue == null || !oldValue.equals(value)) {
                    final Object finalOldValue = oldValue;
                    final Object finalValue = value;
                    listeners.forEach(l -> l.on데이터Changed(item.key, finalOldValue, finalValue));
                }
            } catch (Exception e) {
                System.err.printf("采集[%s]失败:%s%n", item.key, e.getMessage());
            }
        }
    }
    
    private Object readValue(CollectItem item) throws IOException {
        switch (item.type) {
            case "INT16":
                return client.readHolding회원가입s(item.주소, 1)[0];
            case "INT32":
                return client.readInt32(item.주소);
            case "FLOAT32":
                return client.readFloat32(item.주소);
            case "COIL":
                return client.readCoils(item.주소, 1)[0];
            default:
                throw new IllegalArgumentException("不支持的数据类型:" + item.type);
        }
    }
    
    /**
     * 获取缓存数据
     */
    public Map<String, Object> getAll데이터() {
        return new HashMap<>(데이터Cache);
    }
    
    /**
     * 获取单个数据
     */
    public Object get데이터(String key) {
        return 데이터Cache.get(key);
    }
}

五、完整使用示例

package com.industrial.modbus;

import java.util.Arrays;
import java.util.Map;

public class PlcCommunicationDemo {
    
    public static void main(String[] args) {
        // 1. 创建PLC客户端
        PlcModbusClient plc = new PlcModbusClient("192.168.1.100", 502, 1);
        
        try {
            // 2. connectPLC
            if (!plc.connect()) {
                System.err.println("无法连接到PLC");
                return;
            }
            plc.시작AutoReconnect();
            System.out.println("PLC连接成功!\n");
            
            // 3. Read and hold register
            System.out.println("=== Read and hold register ===");
            int[] regs = plc.readHolding회원가입s(0, 10);
            System.out.println("D0-D9:" + Arrays.toString(regs));
            
            // 4. 读取浮点数
            System.out.println("\n=== 读取浮点数 ===");
            float temperature = plc.readFloat32(10);
            float humidity = plc.readFloat32(12);
            System.out.printf("temperature:%.2f℃%n", temperature);
            System.out.printf("湿度:%.2f%%%n", humidity);
            
            // 5. read32位整数
            System.out.println("\n=== read32位整数 ===");
            int counter = plc.readInt32(20);
            System.out.println("计数器值:" + counter);
            
            // 6. 读取线圈
            System.out.println("\n=== 读取线圈 ===");
            boolean[] coils = plc.readCoils(0, 8);
            for (int i = 0; i < coils.length; i++) {
                System.out.printf("M%d:%s%n", i, coils[i] ? "ON" : "OFF");
            }
            
            // 7. Read discrete inputs
            System.out.println("\n=== Read discrete inputs ===");
            boolean[] inputs = plc.readDiscreteInputs(0, 4);
            for (int i = 0; i < inputs.length; i++) {
                System.out.printf("X%d:%s%n", i, inputs[i] ? "ON" : "OFF");
            }
            
            // 8. 写入寄存器
            System.out.println("\n=== 写入寄存器 ===");
            plc.writeSingle회원가입(100, 1234);
            System.out.println("D100 写入 1234");
            
            int check = plc.readHolding회원가입s(100, 1)[0];
            System.out.println("D100 回读:" + check);
            
            // 9. 批量写入
            plc.writeMultiple회원가입s(200, new int[]{100, 200, 300, 400});
            System.out.println("D200-D203 批量写入完成");
            
            // 10. 写入浮点数
            plc.writeFloat32(300, 25.5f);
            System.out.println("D300 写入浮点数 25.5");
            float readBack = plc.readFloat32(300);
            System.out.printf("D300 回读:%.2f%n", readBack);
            
            // 11. 控制线圈
            System.out.println("\n=== 控制线圈 ===");
            plc.writeSingleCoil(0, true);
            System.out.println("M0 置为 ON");
            Thread.sleep(500);
            plc.writeSingleCoil(0, false);
            System.out.println("M0 置为 OFF");
            
            // 12. 批量控制线圈
            plc.writeMultipleCoils(10, new boolean[]{true, false, true, false});
            System.out.println("M10-M13 批量写入完成");
            
            // 13. 读取字符串
            System.out.println("\n=== 读取字符串 ===");
            String device이름 = plc.readString(400, 10);
            System.out.println("Device 이름:" + device이름);
            
        } catch (Exception e) {
            System.err.println("通讯异常:" + e.getMessage());
            e.printStackTrace();
        } finally {
            plc.disconnect();
            System.out.println("\nPLC连接已关闭");
        }
    }
}

六、数据采集服务使用示例

package com.industrial.modbus;

public class 데이터CollectorDemo {
    
    public static void main(String[] args) throws InterruptedException {
        // 创建PLC客户端
        PlcModbusClient plc = new PlcModbusClient("192.168.1.100", 502, 1);
        plc.connect();
        plc.시작AutoReconnect();
        
        // 创建数据采集服务
        Plc데이터Collector collector = new Plc데이터Collector(plc);
        
        // 添加采集项
        collector.addCollectItem("temperature", 10, "FLOAT32");
        collector.addCollectItem("humidity", 12, "FLOAT32");
        collector.addCollectItem("pressure", 14, "FLOAT32");
        collector.addCollectItem("counter", 20, "INT32");
        collector.addCollectItem("status", 0, "COIL");
        collector.addCollectItem("mode", 50, "INT16");
        
        // 添加数据变化监听器
        collector.add데이터ChangeListener((key, oldValue, newValue) -> {
            System.out.printf("[数据变化] %s: %s -> %s%n", key, oldValue, newValue);
        });
        
        // 启动采集,每1秒采集一次
        collector.시작(1000);
        
        // run30秒
        Thread.sleep(30000);
        
        // 打印当前所有数据
        System.out.println("\n=== 当前数据快照 ===");
        collector.getAll데이터().forEach((k, v) -> System.out.printf("%s = %s%n", k, v));
        
        // 停止采集
        collector.stop();
        plc.disconnect();
    }
}

七、Spring Boot集成

package com.industrial.config;

import com.industrial.modbus.PlcModbusClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ModbusConfig {
    
    @Value("${modbus.plc.ip:192.168.1.100}")
    private String plcIp;
    
    @Value("${modbus.plc.port:502}")
    private int plcPort;
    
    @Value("${modbus.plc.unit-id:1}")
    private int unitId;
    
    @Bean(destroyMethod = "disconnect")
    public PlcModbusClient plcModbusClient() {
        PlcModbusClient client = new PlcModbusClient(plcIp, plcPort, unitId);
        if (client.connect()) {
            client.시작AutoReconnect();
        }
        return client;
    }
}

// Service层
package com.industrial.service;

import com.industrial.modbus.PlcModbusClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Service
public class Plc데이터Service {
    
    @Autowired
    private PlcModbusClient plc;
    
    /**
     * 获取设备运行数据
     */
    public Map<String, Object> getDevice데이터() throws IOException {
        Map<String, Object> 데이터 = new HashMap<>();
        데이터.put("temperature", plc.readFloat32(10));
        데이터.put("humidity", plc.readFloat32(12));
        데이터.put("pressure", plc.readFloat32(14));
        데이터.put("runningHours", plc.readInt32(20));
        데이터.put("status", plc.readCoils(0, 1)[0]);
        return 데이터;
    }
    
    /**
     * 设置温度设定值
     */
    public void setTemperature(float value) throws IOException {
        plc.writeFloat32(100, value);
    }
    
    /**
     * 启动设备
     */
    public void 시작Device() throws IOException {
        plc.writeSingleCoil(0, true);
    }
    
    /**
     * 停止设备
     */
    public void stopDevice() throws IOException {
        plc.writeSingleCoil(0, false);
    }
}

// 컨트롤러层
package com.industrial.controller;

import com.industrial.service.Plc데이터Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.util.Map;

@Rest컨트롤러
@RequestMapping("/api/plc")
public class Plc컨트롤러 {
    
    @Autowired
    private Plc데이터Service plcService;
    
    @GetMapping("/데이터")
    public Map<String, Object> get데이터() throws IOException {
        return plcService.getDevice데이터();
    }
    
    @PostMapping("/temperature")
    public String setTemperature(@RequestParam float value) throws IOException {
        plcService.setTemperature(value);
        return "温度设定值已更新为:" + value;
    }
    
    @PostMapping("/시작")
    public String 시작() throws IOException {
        plcService.시작Device();
        return "设备已启动";
    }
    
    @PostMapping("/stop")
    public String stop() throws IOException {
        plcService.stopDevice();
        return "设备已停止";
    }
}

八、常见问题与最佳实践

: 4}

8. 1 자주 묻는 질문

  • 连接超时
  • 返回异常码02
  • 返回异常码03
  • 浮点数解析错误
  • 并发冲突
  • 连接断开
  • 中文乱码

8. 2 Best Pra cti ces

  • 批量读取
  • 合理轮询间隔
  • 自动重连
  • 超时设置
  • 异常处理
  • 数据缓存
  • 日志记录
  • 连接池
  • 地址规划
  • 测试验证

브랜드 별 P LC Mod bus 주소 참조

品牌
西门子S7-200
三菱FX
台达DVP
欧姆龙CP
施耐德M221

이 문서 에서 제공 되는 Java 코 드는 연결 관리 , 자동 재 연 결 , 대 량 읽기 및 쓰 기 , 다양한 데이터 형 식 변환 , 데이터 수집 서비스 및 Spring Boot 통합 을 포함한 P LC 와 의 완전한 Mod bus TCP 통신 기능을 구현 합니다 .이 코 드는 산업 자동 화 프로젝트 개발 에 직접 사용할 수 있으며 , Mod bus Poll 도구를 사용하여 P LC 통신 매 개 변 수 및 주소 매 핑 을 확인 한 다음 Java 코드를 실제 프로젝트에 통합 하는 것이 좋습니다 .

Put this resource to use in a real project?

Go to the Tool Center for message parsing, CRC 검증 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 주소 will not be published. Required fields are marked *.