基于Netty实现ModbusTCP协议的测试工具
- 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、运行结果
5、小结
这是第一版代码,其中在MyHandler类可以继续提取代码。
相关推荐
fengshantao 2020-10-29
arctan0 2020-10-14
爱传文档 2020-07-28
gzx0 2020-07-05
fengshantao 2020-07-04
fengshantao 2020-07-02
jannal 2020-06-21
arctan0 2020-06-19
arctan0 2020-06-16
gzx0 2020-06-14
fengshantao 2020-06-13
gzx0 2020-06-12
arctan0 2020-06-11
fengshantao 2020-06-11
mbcsdn 2020-05-19
arctan0 2020-05-16
爱传文档 2020-05-08
爱传文档 2020-05-04