Java usa Netty para implementar el protocolo de comunicación Modbus-RTU

Modbus

Modbus es un protocolo de comunicación en serie. Modbus es un protocolo de comunicación de uso común y una convención de comunicación en la industria. El protocolo Modbus incluye RTU, ASCII, TCP. Entre ellos, MODBUS-RTU es el más utilizado, relativamente simple y fácil de implementar en una microcomputadora de un solo chip.

Análisis sencillo de mensajes Modbus-RTU

37 10 00 14 00 0a 14 00 00 00 00 00 00 00 00 00 00 00 00 3f 80 00 00 3f 80 00 00 00 a0 (hexadecimal) 37: dirección de estación esclava, 10: código de función, 00 14: MODBUS La dirección
inicial es 40021, correspondiente a 20, 14: el número de bytes de datos escritos, 20, 00 a0: código de verificación crc. El resto son los datos transmitidos.

37 10 00 14 00 0a 14… 00 a0, los datos en el medio son datos funcionales y el mensaje anterior se empalma en un número real de acuerdo con dos bytes, que corresponde al tipo de datos básico flotante, entre los cuales 00 00 00 00 es un número real, seguido de Por analogía, el mensaje anterior tiene 5 números reales. Analizado es 0.0, 0.0, 0.0, 1.0, 1.0.

DTU 4G(ZHC4013)

ZHC4012 es una DTU 4G completa de siete modos de Netcom que admite la transmisión transparente de señales 2G/3G/4G. Admite RS232/485 industrial y otras interfaces, conectadas directamente al equipo para transmisión. Este hardware se practica en mi proyecto y el dispositivo puede comunicar datos con servidores remotos a través de la red del operador 4G. Para operaciones específicas, comuníquese con el servicio de atención al cliente en el sitio web oficial. Sitio web oficial del dispositivo 4G DTU (ZHC4013)

El proyecto admite la carga de datos de múltiples dispositivos 4G DTU y admite el control de dispositivos 4G DTU específicos.

Ventajas de Netty

Netty se basa en el modelo de subprocesamiento NIO de epoll. Todo el proceso de llamada de NIO es que Java llama a la función del kernel del sistema operativo para crear un Socket, obtiene el descriptor de archivo del Socket, crea un objeto Selector, corresponde al descriptor Epoll del sistema operativo y obtiene el evento de el descriptor de archivo de la conexión Socket, vincule el descriptor de archivo Epoll correspondiente al Selector para realizar notificaciones asincrónicas de eventos, de modo que se implemente el uso de un hilo, y no haya necesidad de muchos recorridos no válidos, y se entregue el procesamiento de eventos al núcleo del sistema operativo (implementado por el programa de interrupción del sistema operativo), lo que mejora enormemente la eficiencia.

2021年分享过用原生Socket技术实现的服务端代码,其中服务端还是存在一些问题。当时项目运行在生产上,经过几个月的运行,发现服务端监听端口线程不稳定,运行一段时间后,新的客户端就连接不上服务端了。

Código relacionado con el análisis de datos En el blog que se comparte a continuación, el código relacionado con Netty es la implementación técnica.

Código de implementación de socket: Java utiliza la comunicación de socket para implementar el protocolo de comunicación Modbus-RTU

dependencias de importación

<dependency>
	<groupId>io.netty</groupId>
	<artifactId>netty-all</artifactId>
	<version>4.1.72.Final</version>
</dependency>

código del servidor netty

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;

import java.util.concurrent.TimeUnit;

/**
 * TODO
 *
 * @author linfeng
 * @date 2022/12/26 15:57
 */
public class DtuServer implements Runnable {
    
    

    public static void main(String[] args) {
    
    
        new Thread(new DtuServer()).start();
    }

    @Override
    public void run() {
    
    
        EventLoopGroup bossGroup = new NioEventLoopGroup(2);
        EventLoopGroup workGroup = new NioEventLoopGroup(10);
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup,workGroup).channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
    
    

                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
    
    
                        ch.pipeline().addLast(new IdleStateHandler(15, 0, 0, TimeUnit.MINUTES));
                        ch.pipeline().addLast(new DtuServiceHandler());
                    }

                });
        try {
    
    
            ChannelFuture channelFuture = serverBootstrap.bind(9005).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

import com.ruoyi.socket.ModBusUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;

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

/**
 * TODO
 *
 * @author linfeng
 * @date 2022/12/26 15:58
 */
public class DtuServiceHandler extends ChannelInboundHandlerAdapter {
    
    

	// 保存
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

	// 保存客户端channelId与注册包信息
    private static Map<String,String> channelIdMap = new ConcurrentHashMap<>();

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
        Channel channel = ctx.channel();
        ChannelId channelId = channel.id();
        ByteBuf byteBuf = (ByteBuf) msg;
        byte[] bytes = new byte[byteBuf.readableBytes()];
        byteBuf.readBytes(bytes);
        String str = ModBusUtils.bytes2HexString(bytes);
        System.out.println(bytes.length);
        if(bytes.length == 16){
    
    
        	// 注册包长度为16(我这边用到4G DTU(ZHC4013)注册包长度为16,如果有变化需要修改)
        	// 注册包为设备编号,设备编号是唯一的。
            String registerPackage = "";
            for(int i=0;i<bytes.length;i++) {
    
    
                registerPackage=registerPackage+ModBusUtils.byteToASCLL(bytes[i]);
            }
            // channelId.asLongText()获取的是客户端全局唯一ID,根据channelId获取注册包信息,可以判断出数据是哪个节点上传的
            channelIdMap.put(channelId.asLongText(),registerPackage);
            System.out.println("注册包:"+registerPackage);
        }else {
    
    
        	// 从channelIdMap获取注册包(设备编号)
        	String registerPackage = channelIdMap.get(channelId.asLongText());
        	// 获取到客户端注册包,可以根据注册包(设备编号)查询数据库唯一的设备信息。
        	// 数据就不做解析了,数据解析相关代码在上面分享的博客地址中。
        	// 后面就是业务逻辑和数据解析了。
        	/** 如果要服务端向客户端发送信息,可以利用客户端心跳机制发送数据(也可以写发送数据的线程),其中具体业务逻辑就需要利用数据库了。
        		具体思想:首先数据库保存发送状态和发送数据,利用channelIdMap查询注册包,可以通过注册包在数据库中查询相关数据,
        		根据发送状态判断是否需要发送数据。
        		Netty发送数据需要转ByteBuf,具体代码:channel.writeAndFlush(Unpooled.copiedBuffer(new byte[]{1, 2, 3})),
			*/ 
		}
        
        System.out.println("收到的数据:"+str);
        System.out.println("链接数:"+channelGroup.size());
        System.out.println("注册包数:"+channelIdMap.size());
        super.channelRead(ctx, msg);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    
    
        Channel channel = ctx.channel();
        ChannelId channelId = channel.id();
        channelIdMap.remove(channelId.asLongText());
        System.out.println(channelId.asLongText()+" channelInactive客户端关闭连接");
        super.channelInactive(ctx);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    
    
        super.userEventTriggered(ctx, evt);
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    
    
        // 连接建立
        Channel channel = ctx.channel();
        channelGroup.add(channel);
        super.handlerAdded(ctx);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
    
    
        Channel channel = ctx.channel();
        ChannelId channelId = channel.id();
        channelIdMap.remove(channelId.asLongText());
        System.out.println(channelId.asLongText()+" ChannelHandlerContext客户端关闭连接");
        super.handlerRemoved(ctx);
    }
}
package com.ruoyi.socket;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * TODO
 *
 * @author linfeng
 * @date 2022/12/8 16:04
 */
public class ModBusUtils {
    
    

    public static char byteToASCLL(byte b){
    
    
        return (char) b;
    }


    /*
     * 字节数组转16进制字符串
     */
    public static String bytes2HexString(byte[] b) {
    
    
        String r = "";
        for (int i = 0; i < b.length; i++) {
    
    
            String hex = Integer.toHexString(b[i] & 0xFF);
            if (hex.length() == 1) {
    
    
                hex = '0' + hex;
            }
            r += hex.toUpperCase()+" ";
        }
        return r;
    }

Supongo que te gusta

Origin blog.csdn.net/qq_40042416/article/details/128457288
Recomendado
Clasificación