共计 6458 个字符,预计需要花费 17 分钟才能阅读完成。
Netty 搭建服务端 我们首选采用 Netty 框架搭建一个服务端程序。这里在 IDE 中 shiyMaven 创建了一个新的工程。首先写一个 Server 类,先开看看服务端的核心代码:
static class Server{
private int port;
public Server(int port) {
this.port = port;
for(int i=0;i<1024;i++)
buffer[i]=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 类可以继续提取代码。