Springboot通过netty连接道闸

首先在项目中引入依赖

   <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
        </dependency>

要连接供应商的道闸等设备,所以是作为客户端来连接。

大概的初始化代码如下,SCREEN屏幕用的是UDP协议所以有略微的差异。

重点关注ChannelInitializer~initChannel,在这里面你可以加入自己实现的handler,连接建立后硬件发送指令会通过handler接收处理。

这里可以加入心跳机制和自定义的指令解析(处理半包粘包,处理成自己想要的信息)

 DeviceClient client = this;
        Bootstrap bs = new Bootstrap();
        NioEventLoopGroup group = new NioEventLoopGroup();
        switch (device.getDtype()){
            case "SCREEN": //端口5000   // 接收按钮指令
                bs.group(group);
                bs.channel(NioDatagramChannel.class)
                        .handler(new ChannelInitializer<NioDatagramChannel>() {
                            @Override
                            protected void initChannel(NioDatagramChannel ch) throws Exception {
//                                ch.pipeline().addLast(new IdleStateHandler(15,0,0, TimeUnit.SECONDS)); //心跳
                                ch.pipeline().addLast(new ScreenHandler(client));
                            }
                        });
                this.bootstrap = bs;
                this.loopGroup = group;
                break;
            case "DZ": //端口23
                bs.group(group);
                bs.channel(NioSocketChannel.class)
                        .option(ChannelOption.SO_KEEPALIVE, true)
//                        .option(ChannelOption.SO_RCVBUF,10) //测试粘包半包
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                ch.pipeline().addLast("decoder", new DZDecoder());
                                ch.pipeline().addLast(new DZHandler(client));
                            }
                        });
                this.bootstrap = bs;
                this.loopGroup = group;
                break;
…………
  • 心跳机制 :new IdleStateHandler(15,0,0, TimeUnit.SECONDS) 前三个参数是超时的时间设置即多少秒内没有收到操作分别对应 读/写/读写,如何处理看下面的handler
  • handler的示例代码
public class DZHandler extends SimpleChannelInboundHandler {
    @Override
    public void channelActive(ChannelHandlerContext ctx)//设备加载成功
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) //msg为返回的信息
 @Override
    public void channelInactive(ChannelHandlerContext ctx) //可以加入断线重连的机制

    //这个方法是用来检测心跳,一般检测一个写超时即可
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws   
     Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state() == IdleState.WRITER_IDLE) {
                //写超时的操作        
            }
            else if (event.state() == IdleState.READER_IDLE) {
                //读超时的操作
            }
            else if (event.state() == IdleState.ALL_IDLE) {
              //读写超时的操作
            }
        }
    }
    
}
  • 用decoder处理半包和粘包的问题

            比如现在接收到的指令协议是这样规定的 第4个字节表示数据长度,所以程序检测到指令超过了4字节,开始计算指令长度完全包含了内容,则获取指令并解析,否则从头计算。

public class DZDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> out) throws Exception {
//        同步码 帧类型 数据长度 数据内容
//          2    1      1      N

        int totalLength = in.readableBytes();
        if(totalLength<4){
            return;
        }
        
        in.markReaderIndex();
        byte[] bytes = new byte[3];
        in.readBytes(bytes);
        String msg =  ClientHelp.byteArrayToHexString(bytes);
        if(msg.startsWith("AA5530")){
            bytes = new byte[1];//读取长度
            in.readBytes(bytes);
            int contentByteLength = Integer.parseInt(ClientHelp.byteArrayToHexString(bytes), 16);
            int msgByteLength = contentByteLength + 4;
            in.resetReaderIndex();
            if(totalLength>=msgByteLength){
                byte[] msgByte = new byte[msgByteLength];
                in.readBytes(msgByte);
                out.add(ClientHelp.byteArrayToHexString(msgByte));
            }
        }else{
            in.resetReaderIndex();
        }
    }
}

一般的指令会含有 内容长度 + 验证码(CRC16)之类的,如果需要我们给设备传递指令,比如让音响播放,道闸开启/关闭,都需要程序自动去编译成指令(这个可以结合供应商的协议文档)。

一般涉及到的就是16进制解析CRC16BCD码之类的,附件提供了一个编译协议的工具类,有需要的可以瞅瞅

https://download.csdn.net/download/xuchzhi/88774903