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

首先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 {
    @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 {
    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 out) throws Exception {
        List 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 parseCmdList(ByteBuf originBuf){
        ByteBuf buf = Unpooled.buffer(1024);
        buf.writeBytes(ByteBufUtil.decodeHexDump(lastHalfPacket));
        buf.writeBytes(originBuf);
        List 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 
 
  1. 后记
    这是个半成品,只是打印出来了十六进制转成了HEX STRING的结果。后续对指令的处理,自己再加吧。

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

你可能感兴趣的:(spring-boot 建socket服务器,处理物联网设备的粘包分包)