Netty数据处理:拆包、组包实现

1.前言

        最近一直找第三方产品对接,目前已经集成了40多款产品和20多种传感器,实现了不同协议下的针对粘包、半包数据的拆包、组包处理。

2.为什么会出现粘包与半包数据

        TCP通讯在物联网数据通讯中,目前仍然占据着绝大部分的市场,相比UDP来说,它更加可靠。因为TCP通讯是以二进制流的方式进行,中间是没有间隙的,如果数据传输频率过快则会出现粘包现象。在Netty机制里,传输的数据是存储在ByteBuf中,当我们在接收数据的时候,有可能会一次读取多个包的数据,也可能数据包长度过长,只读取了一部分数据。

        基于这种现象,所以各个产品厂家都会在自己的通讯协议里面都会以特殊字符来标定数据的头尾,这样以便于软件开发过程中对数据进行拆包和组包处理。即使没有协议尾,也会标定出数据的长度,这样也可以进行正常拆包与组包。

3.Netty处理粘包、半包数据

在Netty通道接收到数据,进行协议解析之前,我们可以写个包处理方法,来专门判定数据的完整性与冗余。

1.首先我们按照协议找到其对应的消息头

2.如果协议有定义消息尾的话,我们就找对应的消息尾,如果没有消息尾,则确定是否有消息长度的描述

3.然后将这个拆出来的包存到一个ByteBuf里面交给解析方法进行解析。如果是半包数据,则重置读指针,等待下个数据包的到来,然后继续进行完整包的确定

实现代码:

public static Object decodePacket(ByteBuf in){
        //给读指针打个标记,重置读指针的时候重置到此位置
        in.markReaderIndex();
        if (in.readableBytes()< AnyTrackConstant.MSG_MIN_LENGTH) {
            return null;
        }
        //防止非法码流攻击,数据太大为异常数据
        if (in.readableBytes() > AnyTrackConstant.MSG_MAX_LENGTH) {
            in.skipBytes(in.readableBytes());
            return null;
        }
        //查找消息头,如果未找到则丢弃所有数据
        in.markReaderIndex();
        int headerIndex = in.bytesBefore(AnyTrackConstant.MSG_HEAD);
        if (headerIndex < 0) {
            in.skipBytes(in.readableBytes());
            return null;
        }
        //消息头之后的数据小于基本长度,则等待下一包到达再解包
        in.skipBytes(headerIndex);
        if (in.readableBytes() < AnyTrackConstant.MSG_MIN_LENGTH) {
            in.resetReaderIndex();
            return null;
        }
        //查找消息尾,如果未找到则继续等待下一包
        in.readByte();
        int tailIndex = in.bytesBefore(AnyTrackConstant.MSG_TAIL);
        if (tailIndex < 0) {
            in.resetReaderIndex();
            return null;
        }
        int bodyLen = tailIndex;
        //读取消息体
        byte[] msgBodyArr=new byte[bodyLen];
        in.readBytes(msgBodyArr);
        //读取消息尾
        in.readByte();
        //创建ByteBuf存放反转义后的数据
        ByteBuf frame = ByteBufAllocator.DEFAULT.heapBuffer(bodyLen + 2);
        frame.writeByte(AnyTrackConstant.MSG_HEAD);
        frame.writeBytes(msgBodyArr);
        frame.writeByte(AnyTrackConstant.MSG_TAIL);
        //截取后的消息体不能小于消息的最小长度
        if (frame.readableBytes() < AnyTrackConstant.MSG_MIN_LENGTH) {
            log.error("截取后的消息体的长度不能小于{},数据:{}", AnyTrackConstant.MSG_MIN_LENGTH, ByteBufUtil.hexDump(frame));
            ReferenceCountUtil.release(frame);
            return null;
        }
        return frame;
    }

方法里面是有的常量定义:

public class AnyTrackConstant {
    /**
     * 包头
     */
    public static final byte MSG_HEAD='[';
    /**
     * 包尾
     */
    public static final byte MSG_TAIL = ']';
    /**
     * 消息最大长度
     */
    public static final int MSG_MAX_LENGTH = 2000;
    /**
     * 消息最小长度
     */
    public static final int MSG_MIN_LENGTH = 23;
}

4.说明

以上只是一个拆包方法的处理,是居于人员定位手环协议来做的,协议标准是:

[厂商*设备ID*内容长度*内容]

所以我在包处理的时候是按消息头'['与消息尾']'进行拆包的

这是一个比较简单的协议解析,在对接40多家私标的时候发现还有很多协议头与协议尾是多个字节,以及没有消息尾的等等

有兴趣的通讯可以加我微信,一起学习物联网系统开发

请添加图片描述

 

Guess you like

Origin blog.csdn.net/qq_17486399/article/details/121052426