美文网首页
spring-boot 建socket服务器,处理物联网设备的粘

spring-boot 建socket服务器,处理物联网设备的粘

作者: 青岛大桥_Android到后端 | 来源:发表于2021-04-09 17:07 被阅读0次

    首先dis一下某些物联网设备的通信协议。

    您用TCP Socket协议也就罢了,可是您还通信协议只有开始符,没有结束符是怎么回事。
    您还用不可见字符,二进制的,不便于调试。请问这都什么年代了,您的MCU(单片机)性能有那么弱吗?
    来看一下通信协议:

    通信帧格式:
        名称  长度  说明
            帧头
    
         开始标识           2Byte   0xEF3A (高字节在前)
        数据长度                   2 Byte   长度是指信道、协议版本、命令、内容、CRC 五者的字节之和。(高字节在前)
        信道                           1 Byte                            0x01 服务发出    4G/2G
                                                                         0x02 设备发出  
        协议版本                   1 Byte   当前版本 0x01
    
    帧数据 
            命令                             1 Byte   
        数据                            N Byte    
         
              校验    CRC16   2 Byte  将开始标识、长度、信道、协议版本、命令和内容这六部分
    内容的所有字节进行 CRC 计算(高字节在前)
    

    别欺负我不懂MCU, 我以前也是干硬件出身,PCB也画过,MCU软件也写过,机器也量产过。
    以上叨B叨那么多,硬件改不了,还是要对接啊。以下开始:

    1. 来人,上netty
    image.png

    其它的也就不説了,各种文章太多了,
    来看看核心部分:

    public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
    
            //这是用最简单的分割符来分割
            //ByteBuf delimiter = Unpooled.copiedBuffer("#".getBytes());
    
            //添加编解码
            //socketChannel.pipeline().addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
            //socketChannel.pipeline().addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
    
            //前置,处理了普通分隔符作为报文结束符的分包和粘包问题,不过不适合此例子
            //socketChannel.pipeline().addLast(new MyDelimiterBasedFrameDecoder(1024, delimiter));
    
            socketChannel.pipeline().addLast("decoder", new MyBinDecoder(CharsetUtil.UTF_8));
            socketChannel.pipeline().addLast("encoder", new MyBinEncoder(CharsetUtil.UTF_8));
            socketChannel.pipeline().addLast(new NettyServerHandler());
        }
    }
    
    1. 怎么写具体的协议解析

    然后,就是我们定义的MyBinDecoder 最重要:

    public class MyBinDecoder extends MessageToMessageDecoder<ByteBuf> {
        private final Charset charset;
    
        //上次的半包. 用于处理分包情况
        private String lastHalfPacket = "";
    
        public MyBinDecoder() {
            this(Charset.defaultCharset());
        }
    
        public MyBinDecoder(Charset charset) {
            if (charset == null) {
                throw new NullPointerException("charset");
            } else {
                this.charset = charset;
            }
        }
    
        protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
            List<String> cmdList = parseCmdList(msg);
    
            if(cmdList!=null && !cmdList.isEmpty()) {
                log.info("添加list");
                out.addAll(cmdList);
            }else{
                System.out.println("暂未发现指令");
            }
    
        }
    
        byte[] starter = new byte[]{ (byte)0xEF, (byte)0x3A};
        final int CMD_HEADER_LENGTH = 6;
    
        private List<String> parseCmdList(ByteBuf originBuf){
            ByteBuf buf = Unpooled.buffer(1024);
            buf.writeBytes(ByteBufUtil.decodeHexDump(lastHalfPacket));
            buf.writeBytes(originBuf);
            List<String> result = new ArrayList<>();
    
            log.info("开始遍历 buf readableBytes=" + buf.readableBytes());
    
            try {
                while(buf.readableBytes() >= CMD_HEADER_LENGTH) {
                    byte[] bStart = new byte[2];
                    //String hex = ByteBufUtil.hexDump(b);
                    buf.getBytes(buf.readerIndex(), bStart);
                    log.info("读starter:" + ByteBufUtil.hexDump(bStart));
                    if (compareBytes(bStart, starter)) {
                        log.info("找到了start");
                        //判断这次长度够不够读,如果不够读直接退出
                        //如果够读,读进来,插进cmd list
                            byte[] bHeader = new byte[CMD_HEADER_LENGTH];
                            //buf.markReaderIndex();
                            //buf.readBytes(bHeader);
                            buf.getBytes(buf.readerIndex(), bHeader);
                            int dataLength = bHeader[2] * 256 + bHeader[3];
                            //buf.resetReaderIndex();
                            int THEORYLENGTH = 4 + dataLength;
                            log.info("命令总长度: " + THEORYLENGTH);
    
                            if(buf.readableBytes() >= THEORYLENGTH) {
                                log.info("正在读全长");
                                byte[] bCmdFrame = new byte[THEORYLENGTH];
                                buf.readBytes(bCmdFrame);
                                //readerIndex += THEORYLENGTH;
                                String hexCmd = ByteBufUtil.hexDump(bCmdFrame);
                                log.info("找到一个指令啦:" + hexCmd);
                                result.add(hexCmd);
                                log.info("读缓冲区还有:" + buf.readableBytes());
                                continue;
                            }else{
                                log.info("命令总长未达预期,下次再读");
                                int halfPacketNum = buf.readableBytes();
                                byte[] bHalfPacket = new byte[halfPacketNum];
                                buf.readBytes(bHalfPacket);
                                if(halfPacketNum>=1) {
                                    String halfPacketStr = ByteBufUtil.hexDump(bHalfPacket);
                                    log.info("剩下半包"+halfPacketStr);
                                    lastHalfPacket = halfPacketStr==null? "": halfPacketStr;
                                }
                                return result;
                            }
    
                    } else {
                        //读指针向后移动一个
                        log.info("没找到starter,读指针向后移动一个");
                        buf.readBytes(1);
                        continue;
                    }
                }
    
                int halfPacketNum = buf.readableBytes();
                if(halfPacketNum > 0) {
                    log.info("HEADER命令头未达预期,下次再读");
                    byte[] bHalfPacket = new byte[halfPacketNum];
                    buf.readBytes(bHalfPacket);
                    String halfPacketStr = ByteBufUtil.hexDump(bHalfPacket);
                    log.info("剩下半包"+halfPacketStr);
                    lastHalfPacket = halfPacketStr==null? "": halfPacketStr;
                }else{
                    log.info("余下字节恰好为0,本轮读完了");
                }
            }catch (Exception e){
                e.printStackTrace();
                log.warn(e.toString());
                return result;
            }
    
            return result;
        }
    
        private boolean compareBytes(byte[] b1, byte[] b2){
            if(b1==null && b2==null){
                return true;
            }
            if( (b1==null && b2!=null) || (b1!=null && b2==null)){
                return false;
            }
            if(b1.length != b2.length){
                return false;
            }
            for(int i = 0; i<b1.length; i++){
                if( b1[i]!=b2[i]){
                    return false;
                }
            }
            return true;
        }
    
    }
    
    1. 后记
      这是个半成品,只是打印出来了十六进制转成了HEX STRING的结果。后续对指令的处理,自己再加吧。

    伸手党可去CSDN下载源码
    https://download.csdn.net/download/stephenzhu/16593276

    相关文章

      网友评论

          本文标题:spring-boot 建socket服务器,处理物联网设备的粘

          本文链接:https://www.haomeiwen.com/subject/arqwkltx.html