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<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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容