Skip to content

Netty的简单应用

Netty可用于接收RTU的报文。 我们的项目主要需求是开发一款可用于PLC的上位机软件,PLC的报文格式是自定义的,所以我们需要使用Netty来接收PLC的报文,然后进行解析。 报文标准为:SL651-2014 水文监测数据通信规约 (基于Modbus协议) 这里主要实现了《SL651》中的遥测站定时报上行报文的解析。

1.引用插件

xml
    <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.107.Final</version>
    </dependency>

2.创建一个Netty服务

java
package xxx.iot;
 
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;

@Component
public class PNettyServer {

    @Autowired
    /**
     * 解码器
     */
    SL651ModbusDecoder sL651ModbusDecoder;
    @Autowired
    /**
     *   服务端口
     */
    int port = 9995;

    @PostConstruct
    private void init(){
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup(4);
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childOption(ChannelOption.SO_REUSEADDR, true)
                .childHandler(new ChannelInitializer<SocketChannel>(){
            @Override
            public void initChannel(SocketChannel ch)throws Exception{
                ch.pipeline()
                        .addLast(new SL651ModbusDecoder())
                        .addLast(new PChannelHandler());
            }
        });
        bootstrap.bind(port).addListener((ChannelFutureListener) future -> {
            System.out.println("rtu netty bind success in port: " + port);
        });
        System.out.println("rtu netty server started!");
    }
}

3.创建一个适配器接收入站事件与数据

java
package xxx.iot;

import xxx.core.utils.DateUtils;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Date;

public class PChannelHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println(DateUtils.dateTimeNow()+" RTU RADAR: "+msg);
        super.channelRead(ctx, msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        super.channelReadComplete(ctx);
    }
}

4.创建一个解码器

java
package xxx.iot;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;

public class SL651ModbusDecoder extends ByteToMessageDecoder {
    public SL651ModbusDecoder(){}
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        // 这里可以随心读取数据,常用读取方法有三个
        // 1 标记当前读游标位置
        byteBuf.markReaderIndex(); 
        // 2 返回到最近标记的游标位置
        butyfBuf.resetReaderIndex(); 
        // 3 定长读取数据
        byte[] bufferArray = new byte[1];
        byteBuf.readBytes(bufferArray, 0, 1);
    }
}