基於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協議的核心,咱們繼續看。
二、MyInHandler類的實現
MyInHandler類是咱們處理ModbusTCP協議的基礎,下面咱們來看看怎麼實現這個類的。app

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]+" ");
            }
        }
    }

三、界面實現
爲了方便調試,咱們這邊實現了一個簡單的界面。框架

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表示關閉窗體退出程序;二、一、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();
        }
    }

五、主模塊ide

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

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

四、運行結果
PSD-TEST-TOOL.pngoop

五、小結
這是初版代碼,其中在MyHandler類能夠繼續提取代碼。佈局

相關文章
相關標籤/搜索