- 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() {
@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
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();
}
5、小结
这是第一版代码,其中在MyHandler类可以继续提取代码。