• Modbus协议服务端(Netty)


    pom.xml

    
    <dependency>
                <groupId>io.netty</groupId>
                <artifactId>netty-all</artifactId>
                <version>4.0.21.Final</version>
                <scope>compile</scope>
    </dependency>
    
    <dependency>
                <groupId>commons-codec</groupId>
                <artifactId>commons-codec</artifactId>
                <version>1.7</version>
    <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
    </dependency>
    

    Listener

    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.*;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.handler.codec.FixedLengthFrameDecoder;
    import io.netty.handler.timeout.ReadTimeoutHandler;
    import io.netty.util.concurrent.DefaultEventExecutorGroup;
    import io.netty.util.concurrent.EventExecutorGroup;
    import util.Consts;
    
    public class PortalSomCommunicationListener implements Runnable{
        private int port;
    
        public PortalSomCommunicationListener(int port) {
            this.port = port;
        }
    
        @Override
        public void run() {
            startNettyServer(port);
        }
    
        private void startNettyServer(int port) {
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workerGroup = new NioEventLoopGroup();
            final EventExecutorGroup businessGroup = new DefaultEventExecutorGroup(10);
            try {
                // 配置服务器的NIO线程租
                ServerBootstrap b = new ServerBootstrap();
                b.group(bossGroup, workerGroup)
                        .channel(NioServerSocketChannel.class)
                        .option(ChannelOption.SO_BACKLOG, 1024)
                        .childOption(ChannelOption.SO_KEEPALIVE, true)
                        .childHandler(new ChannelInitializer() {
                            @Override
                            protected void initChannel(Channel channel) throws Exception {
                                channel.pipeline().addLast(new FixedLengthFrameDecoder(12));
                                // 设置超时时间,防止连接过多。
                                channel.pipeline().addLast("readtime",new ReadTimeoutHandler(5));
                                channel.pipeline().addLast(businessGroup, "executer", new SomCommunicationHandler());
                            }
                        });
                // 绑定端口,同步等待成功
                ChannelFuture f = b.bind(port).sync();
                // 等待服务端监听端口关闭
                f.channel().closeFuture().sync();
            } catch(Exception e) {
            } finally {
                // 优雅退出,释放线程池资源
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    
        public static void main(String[] args) {
            // 异味MODBUS协议
            new Thread(new PortalSomCommunicationListener(Consts.YIWEI_PORT)).start();
        }
    }
    
    

    Handler

    
    import domain.YiWeiSample;
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.ByteBufUtil;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelFutureListener;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import net.Client;
    import org.apache.commons.codec.binary.Hex;
    import util.Consts;
    import java.io.UnsupportedEncodingException;
    import java.nio.ByteBuffer;
    import java.nio.charset.Charset;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 请求:
     *  00 00   00 00   00 06      FF          03      00 01       00 00
     * | 事务 |协议标识| 长度 | 单元标识 |功能码|起始地址|寄存器地址|
     *
     * 响应:
     *  00 00   00 00   00 06       FF           03     00 01      00 00
     * | 事务 |协议标识| 长度 | 单元标识 |功能码|字节个数|请求的数据|
     */
    public class SomCommunicationHandler extends ChannelInboundHandlerAdapter {
        // 请求指令的长度,此协议指定12个字节长度
        private static final int commandSize = 12;
        private ByteBuf readBuf;
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
    
            ByteBuf m = (ByteBuf) msg;
            Consts.logger.info("SomCommunicationHandler收到SOM的二进制形式指令: " + ByteBufUtil.hexDump(m));
            Consts.logger.info("SomCommunicationHandler收到SOM的ASCII形式指令: " + m.toString(Charset.forName("US-ASCII")));
            readBuf.writeBytes(m);
            m.release();
            byte[] bytes = handleData();
            if (bytes == null) return;
            ctx.channel().writeAndFlush(Unpooled.wrappedBuffer(bytes))
                    .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE)
                    .addListener((ChannelFutureListener) channelFuture -> Consts.logger.info(
                            "SomCommunicationHandler "
                                    + this.getClass().getName()
                                    + "成功发送指令:"
                                    + Hex.encodeHexString(bytes)));
        }
    
        private byte[] handleData() {
            while (readBuf.readableBytes() >= commandSize) {
                List<Byte> result = new ArrayList<>();
                byte[] bytes = new byte[commandSize];
                readBuf.readBytes(bytes);
                // 事务
                result.add(bytes[0]);
                result.add(bytes[1]);
                // 协议标识
                result.add(bytes[2]);
                result.add(bytes[3]);
                // 起始地址
                float startIndex = ByteBuffer.wrap(bytes, 8, 2).getShort();
                // 寄存器个数
                float numberRegister = ByteBuffer.wrap(bytes, 10, 2).getShort();
    
                // 发送指令
                try {
                    // 数据包 AI + DI
                    byte[] dataBytes = getData((int) startIndex, (int) numberRegister);
                    // Length 长度:长度字段以字节计数,包括单元标识和数据字段。
                    int length = 3 + (dataBytes != null ? dataBytes.length : 0);
                    byte[] len = numToBytes(length);
                    addBytes(result, len);
                    //Unit Identifier 单元标识
                    result.add((byte) 0xFF);
                    // Function Code 功能码
                    result.add((byte) 0x04);
                    //Byte Count 字节个数
    //                result.add(numToBytes(dataBytes != null ? dataBytes.length : 0));
                    byte[] len2 = numToHex8((dataBytes != null ? dataBytes.length : 0));
                    addBytes(result, len2);
    
                    addBytes(result, dataBytes);
    
                    return list2byte(result);
                } catch (UnsupportedEncodingException e) {
                    Consts.logger.error(e.getMessage(), e);
                }
            }
            return null;
        }
    
        private byte[] list2byte(List<Byte> list) {
            if (list == null || list.size() < 0)
                return null;
            byte[] bytes = new byte[list.size()];
            int i = 0;
            for (Byte aList : list) {
                bytes[i] = aList;
                i++;
            }
            return bytes;
        }
    
        /**
         * @param startIndex 起始地址是指从数据的第几个字段开始, 1:系统报警, 2。。。12 数据
         * @param size 寄存器的个数 = DI + AI, DI有1个,AI有11个
         */
        private byte[] getData(int startIndex, int size) throws UnsupportedEncodingException {
            // TODO: demo数据。
            YiWeiSample sample = new YiWeiSample(
                    20,
                    70,
                    50,
                    12,
                    1,
                    25,
                    70,
                    20,
                    18,
                    40,
                    10
            );
            //                                                  // 寄存器地址
            // DI:系统报警值,0,1,2,3,4, 1为正常
            byte [] di = numToBytes(1);                         // 1
            // AI 请求的数据data,
            List<Byte> bytes = new ArrayList<>();
            addBytes(bytes, numToBytes(sample.getAvgTemp()));   // 2
            addBytes(bytes, numToBytes(sample.getAvgHumid()));  // 3
            addBytes(bytes, numToBytes(sample.getAvgPm10()));   // 4
            addBytes(bytes, numToBytes(sample.getAvgCo2()));    // 5
            addBytes(bytes, numToBytes(sample.getInYiwei()));   // 6
            addBytes(bytes, numToBytes(sample.getInTemp()));    // 7
            addBytes(bytes, numToBytes(sample.getInPm10()));    // 8
            addBytes(bytes, numToBytes(sample.getInCo2()));     // 9
            addBytes(bytes, numToBytes(sample.getOutTemp()));   // 10
            addBytes(bytes, numToBytes(sample.getOutPm10()));   // 11
            addBytes(bytes, numToBytes(sample.getOutCo2()));    // 12
    
            // 处理一下startIndex, startIndex默认在区间[1,12],1为DI,AI[2,12]
            if (startIndex == 1) {
                startIndex = 0;
            } else if (startIndex >= 2 && startIndex <= 12) {
                startIndex = (startIndex - 2) * 2;
            } else {
                Consts.logger.error("Data (as requested)获取请求数据失败, Starting Address 起始地址小于2或大于12,为:" + size);
                return null;
            }
    
            // 寄存器个数
            if (size < 0 || size > bytes.size()) {
                Consts.logger.error("Data (as requested)获取请求数据失败, Number of Registers 寄存器个数错误,个数为:" + size);
                return null;
            }
    
            bytes = bytes.subList(startIndex, (size - 1) * 2);
    
            List<Byte> result = new ArrayList<>();
            addBytes(result, di);
            result.addAll(bytes);
            return list2byte(result);
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
                throws Exception {
            Consts.logger.error("DTUHandler exception: " + cause.toString());
            ctx.close();
        }
    
        @Override
        public void handlerAdded(ChannelHandlerContext ctx) {
            readBuf = ctx.alloc().buffer();
        }
    
        @Override
        public void handlerRemoved(ChannelHandlerContext ctx) {
            readBuf.release();
            readBuf = null;
            Client.disconnect(ctx.channel());
        }
    
        private void addBytes(List<Byte> list, byte[] arr) {
            if (arr == null || arr.length == 0) return;
            for (byte b : arr) {
                list.add(b);
            }
        }
    
        private static byte[] numToHex8(int b) {
            return hexStringToBytes(String.format("%02x", b));
        }
    
        private static byte[] numToBytes(int b) {
            String hex = String.format("%04x", b);
            return hexStringToBytes(hex);
        }
    
        private static byte[] hexStringToBytes(String hexString) {
            if (hexString == null || hexString.equals("")) {
                return null;
            }
            hexString = hexString.toUpperCase();
            int length = hexString.length() / 2;
            char[] hexChars = hexString.toCharArray();
            byte[] d = new byte[length];
            for (int i = 0; i < length; i++) {
                int pos = i * 2;
                d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
            }
            return d;
        }
    
        private static byte charToByte(char c) {
            return (byte) "0123456789ABCDEF".indexOf(c);
        }
    }
    

    Consts.java

    import org.apache.log4j.Logger;
    
    public class Consts {
    
    	public static final int YIWEI_PORT = 10000;
    	public static Logger logger = Logger.getRootLogger();
    	
    }
    
    

    Client.java

    
    import io.netty.channel.Channel;
    
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    public class Client {
        private static final Map<String, Client> devCode2Client;
    
        static {
            devCode2Client = new ConcurrentHashMap<>();
        }
    
        private static final Map<Channel, String> channel2DevCode = new ConcurrentHashMap<>();
    
    
        public static void disconnect(Channel channel) {
    
            if (channel2DevCode.containsKey(channel)) {
                String devCode = channel2DevCode.get(channel);
                devCode2Client.remove(devCode);
                channel2DevCode.remove(channel);
            }
        }
    
    }
    

    YiWeiSample.java

    public class YiWeiSample {
    
        private int avgTemp;
        private int avgHumid;
        private int avgPm10;
        private int avgCo2;
        private int inYiwei;
        private int inTemp;
        private int inPm10;
        private int inCo2;
        private int outTemp;
        private int outPm10;
        private int outCo2;
        private int warnData;
    
        public YiWeiSample(int avgTemp, int avgHumid, int avgPm10, int avgCo2, int inYiwei, int inTemp, int inPm10, int inCo2, int outTemp, int outPm10, int outCo2) {
            this.avgTemp = avgTemp;
            this.avgHumid = avgHumid;
            this.avgPm10 = avgPm10;
            this.avgCo2 = avgCo2;
            this.inYiwei = inYiwei;
            this.inTemp = inTemp;
            this.inPm10 = inPm10;
            this.inCo2 = inCo2;
            this.outTemp = outTemp;
            this.outPm10 = outPm10;
            this.outCo2 = outCo2;
        }
    
        public int getAvgTemp() {
            return avgTemp;
        }
    
        public void setAvgTemp(int avgTemp) {
            this.avgTemp = avgTemp;
        }
    
        public int getAvgHumid() {
            return avgHumid;
        }
    
        public void setAvgHumid(int avgHumid) {
            this.avgHumid = avgHumid;
        }
    
        public int getAvgPm10() {
            return avgPm10;
        }
    
        public void setAvgPm10(int avgPm10) {
            this.avgPm10 = avgPm10;
        }
    
        public int getAvgCo2() {
            return avgCo2;
        }
    
        public void setAvgCo2(int avgCo2) {
            this.avgCo2 = avgCo2;
        }
    
        public int getInYiwei() {
            return inYiwei;
        }
    
        public void setInYiwei(int inYiwei) {
            this.inYiwei = inYiwei;
        }
    
        public int getInTemp() {
            return inTemp;
        }
    
        public void setInTemp(int inTemp) {
            this.inTemp = inTemp;
        }
    
        public int getInPm10() {
            return inPm10;
        }
    
        public void setInPm10(int inPm10) {
            this.inPm10 = inPm10;
        }
    
        public int getInCo2() {
            return inCo2;
        }
    
        public void setInCo2(int inCo2) {
            this.inCo2 = inCo2;
        }
    
        public int getOutTemp() {
            return outTemp;
        }
    
        public void setOutTemp(int outTemp) {
            this.outTemp = outTemp;
        }
    
        public int getOutPm10() {
            return outPm10;
        }
    
        public void setOutPm10(int outPm10) {
            this.outPm10 = outPm10;
        }
    
        public int getOutCo2() {
            return outCo2;
        }
    
        public void setOutCo2(int outCo2) {
            this.outCo2 = outCo2;
        }
    
        public int getWarnData() {
            return warnData;
        }
    
        public void setWarnData(int warnData) {
            this.warnData = warnData;
        }
    }
    
    有梦为马,游历天涯!
  • 相关阅读:
    手机进水不要慌,四个步骤告诉您正确处理方法!
    2021-08-17:学习项目代码流程
    Docker使用Centos镜像安装Openssh服务
    OpenResty简介、下载流程、简单教学
    go接收者和锁注意事项
    PHPstorm精进
    centos7找回root密码
    功能测试
    删除排序数组中的重复项
    Java多线程
  • 原文地址:https://www.cnblogs.com/qijianguo/p/10686376.html
Copyright © 2020-2023  润新知