基于Netty实现ModbusTCP协议的测试工具

  1. Netty搭建服务端
    我们首选采用Netty框架搭建一个服务端程序。这里在IDE中使用Maven创建了一个新的工程。首先写一个Server类,先开看看服务端的核心代码:
static class Server{
        private int port;

        public Server(int port) {
            this.port = port;
            Arrays.fill(buffer, (byte) 0);//初始化设置为0
        }

        public void run() throws Exception {
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workerGroup = new NioEventLoopGroup();
            try {
                ServerBootstrap b = new ServerBootstrap();
                b.group(bossGroup, workerGroup)
                        .channel(NioServerSocketChannel.class)
                        .childHandler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            public void initChannel(SocketChannel ch) throws Exception {

                                //ch.pipeline().addLast(new FixedLengthFrameDecoder(12) );
                                ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024,4,2));
                                ch.pipeline().addLast(new MyInHandler());

                            }
                        })
                        .option(ChannelOption.SO_BACKLOG, 128)

                        .childOption(ChannelOption.SO_KEEPALIVE, true);

                // 绑定端口,开始接收进来的连接
                //bind(b,9000);
                ChannelFuture f = b.bind(port).sync();
                System.out.println("Server start listen at " + port );
                f.channel().closeFuture().sync();
            } finally {
                workerGroup.shutdownGracefully();
                bossGroup.shutdownGracefully();
            }
        }

其中的MyInHandler类是我们实现Modbus协议的核心,我们继续看。
2、MyInHandler类的实现
MyInHandler类是我们处理ModbusTCP协议的基础,下面我们来看看怎么实现这个类的。

static class MyInHandler extends ChannelInboundHandlerAdapter{

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            ByteBuf byteBuf = (ByteBuf) msg;
            System.out.println("---------------start process msg--------------------");
            System.out.println("readable bytes is:"+byteBuf.readableBytes());
            short TransActionId = byteBuf.readShort();
            short protocal = byteBuf.readShort();
            short msg_len = byteBuf.readShort();
            byte slave_id = byteBuf.readByte();
            byte funcotion_code = byteBuf.readByte();
            if(funcotion_code ==4 )//如果功能码是4,也就是读请求,我们要返回结果
            {   //输出
                short start_address = byteBuf.readShort();
                short ncount = byteBuf.readShort();
                System.out.println("TransactionID is:"+ TransActionId);
                System.out.println("protocal id is:"+protocal);
                System.out.println("msg len is:"+msg_len);
                System.out.println("slave id  is:"+slave_id);
                System.out.println("function code is:"+funcotion_code);
                System.out.println("start address is:"+start_address);
                System.out.println("count  is:"+ncount);
                //返回响应消息报文
                ByteBuf out = ctx.alloc().directBuffer(110);
                out.writeShort(0);//Transaction ID  2
                out.writeShort(0);//protocal id     2
                out.writeShort(95);//msg len        2
                out.writeByte(1);//slave id         1
                out.writeByte(4);//function code    1
                //out.writeShort(0);//start address 2
                out.writeByte(46);//46个寄存器      46*2
              
                for(int i=0;i<92;i++)
                    out.writeByte(buffer[i]);
                ctx.channel().writeAndFlush(out);
              
            }
            else if(funcotion_code == 0x10)
            {
                short start_address = byteBuf.readShort();

                short nWords = byteBuf.readShort();
                byte  ncount = byteBuf.readByte();
              
                //更新本地buffer
                for(int i=0;i<ncount;i++)
                    buffer[start_address*2+i] = byteBuf.readByte();
                //printMsg();
                //返回响应消息
                ByteBuf out = ctx.alloc().directBuffer(93);
                out.writeShort(0);//Transaction ID  2
                out.writeShort(0);//protocal id     2
                out.writeShort(0);//msg len         2
                out.writeByte(1);//slave id         1
                out.writeByte(0x10);//function code    1
                out.writeShort(start_address);//46个寄存器      46*2
                out.writeShort(ncount);//ncuont  2
                ctx.channel().writeAndFlush(out);
                //System.out.println("response write success,write words is:"+out.readableBytes());
                //out.release();
            }
            else{
                System.out.println("error function");
            }
            //System.out.println("---------------end process msg--------------------");
        }


        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            // 当出现异常就关闭连接
            //cause.printStackTrace();
            ctx.close();
        }
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("客户端已经连接!");
        }
        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("客户端退出!");
            ctx.close();
        }

        private void printMsg(){
            for(int i=0;i<200;i++){
                if(i%20==0)
                    System.out.println();
                System.out.print( buffer[i]+" ");
            }
        }
    }

3、界面实现
为了方便调试,我们这边实现了一个简单的界面。

static class NewFrame{
        NewFrame(){}
        private void start(){
            JFrame frame = new JFrame();
            // 4.设置窗体对象的属性值:标题、大小、显示位置、关闭操作、布局、禁止调整大小、可见、...
            frame.setTitle("PSD-Test-Tool");// 设置窗体的标题
            frame.setSize(400, 450);// 设置窗体的大小,单位是像素
            frame.setDefaultCloseOperation(3);// 设置窗体的关闭操作;3表示关闭窗体退出程序;2、1、0
            frame.setLocationRelativeTo(null);// 设置窗体相对于另一个组件的居中位置,参数null表示窗体相对于屏幕的中央位置
            frame.setResizable(false);// 设置禁止调整窗体大小

            // 实例化FlowLayout流式布局类的对象,指定对齐方式为居中对齐,组件之间的间隔为5个像素
            FlowLayout fl = new FlowLayout(FlowLayout.LEFT, 10, 10);
            // 实例化流式布局类的对象
            frame.setLayout(fl);

            // 5.实例化元素组件对象,将元素组件对象添加到窗体上(组件添加要在窗体可见之前完成)。
            // 实例化ImageIcon图标类的对象,该对象加载磁盘上的图片文件到内存中,这里的路径要用两个\
            ImageIcon icon = new ImageIcon("");
            // 用标签来接收图片,实例化JLabel标签对象,该对象显示icon图标
            JLabel labIcon = new JLabel(icon);
            //设置标签大小
            //labIcon.setSize(30,20);setSize方法只对窗体有效,如果想设置组件的大小只能用
            Dimension dim = new Dimension(400,30);
            labIcon.setPreferredSize(dim);
            // 将labIcon标签添加到窗体上
            frame.add(labIcon);

            //显示寄存器界面
            final JTextArea registView = new JTextArea();
            Dimension d = new Dimension(400,200);
            registView.setPreferredSize(d);

            frame.add(registView);


            // 实例化JLabel标签对象,该对象显示"账号:"
            JLabel labName = new JLabel("地址:");
            // 将labName标签添加到窗体上
            frame.add(labName);



            // 实例化JTextField标签对象
            final JTextField textName = new JTextField();
            Dimension dim1 = new Dimension(350,30);
            //textName.setSize(dim);//setSize这方法只对顶级容器有效,其他组件使用无效。
            textName.setPreferredSize(dim1);//设置除顶级容器组件其他组件的大小
            // 将textName标签添加到窗体上
            frame.add(textName);

            //实例化JLabel标签对象,该对象显示"密码:"
            JLabel labpass= new JLabel("值 :");
            //将labpass标签添加到窗体上
            frame.add(labpass);


            //实例化JPasswordField
            final JTextField textword=new JTextField();
            //设置大小
            textword.setPreferredSize(dim1);//设置组件大小
            //添加textword到窗体上
            frame.add(textword);

            //实例化JButton组件
            JButton button=new JButton();
            //设置按钮的显示内容
            Dimension dim2 = new Dimension(150,30);
            button.setText("发送");
            //设置按钮的大小
            button.setSize(dim2);
            frame.add(button);
            button.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    //textword.getAccessibleContext();

                    int register_index = Integer.parseInt(textName.getText());
                    int value =  Integer.parseInt(textword.getText());
                    System.out.println("register_index="+register_index+";value="+value);
                    if(register_index>200 || register_index<0) return;
                    if(value>255 || value<0) return;
                    buffer[register_index] =(byte)(value&0xff);

                    //registView.setText();
                    //printMsg();
                }
            });
            frame.setVisible(true);// 设置窗体为可视化

            new Timer(1000, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    registView.setText("");
                    StringBuilder sb = new StringBuilder();
                    int time = 0;
                    for (int i = 0; i < 200; i++) {
                        if (i % 20 == 0) {

                            int start = time * 20;
                            int end = time * 20 + 19;
                            sb.append("\r\n reg[" + start + "-" + end + "]");
                            if (time == 0) sb.append("    ");
                            if (start < 100 && time > 0) sb.append("   ");
                            time++;
                        }
                        if (i % 10 == 0)
                            sb.append("    ");

                        sb.append(Integer.toHexString(buffer[i]&0xff) + "  ");//转换成16进制显示
                    }
                    registView.setText(sb.toString());
                }
            }).start();
        }
    }

5、主模块

public static void main(String[] args) throws Exception{

        NewFrame newFrame = new NewFrame();
        newFrame.start();
        Server server = new Server(9000);
        server.run();
    }

4、运行结果
基于Netty实现ModbusTCP协议的测试工具

5、小结
这是第一版代码,其中在MyHandler类可以继续提取代码。

相关推荐