netty自定义协议解决粘包拆包问题

netty的粘包和拆包是很重要的一部分,粘包分如图中的几种情况
在这里插入图片描述
假设客户端分别发送了两个数据包,D1和D2给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下情况:

服务端分别收到了D1和D2,没有粘包和拆包

服务端一次性收到了D1和D2,称为TCP粘包

服务端两次读取到了两个数据包,第一次读到了D1的完整部分和D2的部分数据,第二次读到了D2的剩余部分。 这称为TCP拆包

服务端两次读取到两个数据包,第一次是D1的部分,第二次是D1的剩余部分和D2的完整部分

也有可能D1和D2非常大,期间发生多次拆包。

netty有很多自带的拆包封装类,比如LineBasedFrameDecoder等,但是最多的还是根据自己的帧格式自定义协议来进行拆包,我们拿GB/T18657.1—2002的6.2.4条FT1.2异步式传输帧格式来举例。
格式如图在这里插入图片描述
应对这种比较难的帧格式就不能用自带的拆包类,而要根据帧格式自定义解码器。
先判断枕头,再略过两字节(长度)再判断帧中间标识,再读帧尾标识。
具体代码如下

@Slf4j
public class GateWayDecoder extends ByteToMessageDecoder{
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
       //bytebuf gc出错怎么解决
        log.info("收到报文");

        if(byteBuf == null){
            log.info("收到的报文为空");
            return;
        }
        //判断是否满足字节最小数目
        if(byteBuf.readableBytes() < ProtocolConstant.FRAME_SIZE_MIN){
            log.info("报文不够最小数目");
            return;
        }
        int beginReader;
        while (true) {
            // 获取包头开始的index
            beginReader = byteBuf.readerIndex();
            // 标记包头开始的index
            byteBuf.markReaderIndex();
            // 读到了协议的开始标志,结束while循环
            if (byteBuf.readByte() == ProtocolConstant.FRAME_HEAD_FLAG ) {
                byte[] lengthBytes1 = new byte[2];
                lengthBytes1[0] = byteBuf.readByte();
                lengthBytes1[1] = byteBuf.readByte();
                int length1 = FrameCommUtil.bytes2Int(lengthBytes1,0, lengthBytes1.length);//读取长度
                if(byteBuf.readableBytes() < length1 + 3){//报文不够最小数目
                    log.info("报文不够最小数目3");
                    byteBuf.resetReaderIndex();
                    return;
                }
                if(byteBuf.readByte() == ProtocolConstant.FRAME_MIDDLE_FLAG) {//如果跳过长度字节后一个为标志位
                    byteBuf.readBytes(length1);//跳过数据区
                    byteBuf.readByte();//跳过校验和
                    if(byteBuf.readByte() == ProtocolConstant.FRAME_TAIL_FLAG){
                        byteBuf.resetReaderIndex();//还原到包头位置
                        byteBuf.readByte();
                        break;
                    }
                }
            }

            // 未读到包头,略过一个字节
            // 每次略过,一个字节,去读取,包头信息的开始标记
            byteBuf.resetReaderIndex();
            byteBuf.readByte();

            // 当略过,一个字节之后,
            // 数据包的长度,又变得不满足
            // 此时,应该结束。等待后面的数据到达
            if (byteBuf.readableBytes() < ProtocolConstant.FRAME_SIZE_MIN) {
                log.info("剩余字节数小于帧最小满足数1");
                return;
            }
        }

        log.info("O Length:" + (byteBuf.readableBytes() + 1));
        //读取消息长度 两字节
        byte[] lengthBytes = new byte[2];
        lengthBytes[0] = byteBuf.readByte();
        lengthBytes[1] = byteBuf.readByte();
        int length = FrameCommUtil.bytes2Int(lengthBytes,0, lengthBytes.length);

        if (byteBuf.readableBytes() < length + 3) {//如果剩下可读的信息长度小于帧所需要的长度 +3的意思是校验和和尾帧标识和中间标识的字节数
            // 还原读指针
            byteBuf.readerIndex(beginReader);//还原到帧头开始位置
            log.info("剩余字节数小于帧最小满足数2");
            return;
        }
        byte middle = byteBuf.readByte();//读取中间标志位

        byte[] dataBytes = new byte[length];//创造一个长度为length的容纳数据的数组

        try {
            byteBuf.readBytes(dataBytes);//将数据写入数组
        }catch (Exception e){
            e.printStackTrace();
            return;
        }

        byte check = byteBuf.readByte();//读取校验和
        byte tail = byteBuf.readByte();//读取尾部标识

        ByteBuffer frameBytes = ByteBuffer.allocate(6 + length);//6+length的意思是数据字节总数+标志位+校验和
        frameBytes.put(ProtocolConstant.FRAME_HEAD_FLAG);//放入头部标识
        frameBytes.put(FrameCommUtil.int2Bytes(length), 0, ProtocolConstant.FRAME_LENGTH_BYTESIZE);//放入长度
        frameBytes.put(ProtocolConstant.FRAME_MIDDLE_FLAG);//放入中间标识
        frameBytes.put(dataBytes);//放入数据
        frameBytes.put(check);//放入校验和
        frameBytes.put(ProtocolConstant.FRAME_TAIL_FLAG);//放入尾部标识
        byte[] bytesData = frameBytes.array();//转换为数组

        log.info("A Length:" + bytesData.length);
        Frame frame = FrameUtil.parse(bytesData);//转换为帧实体类
        if (frame != null){
            list.add(frame);//交给vastlistener解析
        }

        System.gc();

    }
}
  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值