netty 拆包、粘包解决之道

本文详细介绍了在Netty框架中如何解决网络通信中常见的拆包和粘包问题,探讨了TCP协议特性及三种常见解决方案:定长报文、特殊分隔符和报文头加报文体的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

netty 拆包、粘包解决之道

一、什么是拆包、粘包?

拆包是指接收方接收到一个不完整的数据包,粘包则指接收一次接收到多个数据包

二、拆包、粘包的产生的原因

TCP是一个面向流的协议。TCP是传输层协议,其并不清楚应用层数据的具体含义。TCP使用滑动窗口进行流量控制,所以在业务认为是一个完整的包,很有可能会被TCP拆分为多个数据包进行发送。也有可能会将多个小的包组装成一个大的包发送。

三、如何解决拆包、粘包问题

可以通过认为的进行数据包边界的制定和解析。几种常见的思路如下:

  • 1、业务数据使用定长报文:在这种情况下,接收方只要读取到足够的数据长度就认为是已经获取到一个业务数据报文

  • 2、使用特殊的消息分隔符:此种情况下,接收方检查每一个收到的字节,一旦某段字节序列符合分隔符特征,接收方就分离出这段数据报文

  • 3、使用报文头+报文体:此种此情况下,一般来说报文头是一个定长或者不定长的数据,其中会包含报文体的长度或者总报文的长度。这样,接收方应用程序在读取报文头之后就可以直到整体的报文长度后者剩余的报文体长度。按照长度,就可以堆一个数据报文进行分离。

四、netty如何解决拆包、粘包问题

1、使用定长报文协议

正对定长报文协议,Netty提供了io.netty.handler.codec.FixedLengthFrameDecoder 解码器。如果我们接收到下面所示的四个报文片段。

* +---+----+------+----+
* | A | BC | DEFG | HI |
* +---+----+------+----+

一个定长为3的FixedLengthFrameDecoder 解码器,那么就会将它们解码为如下的三个片段。

* +-----+-----+-----+
* | ABC | DEF | GHI |
* +-----+-----+-----+

该类的核心方法如下:

    @Override
    protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        //解析数据
        Object decoded = decode(ctx, in);
        //如果数据不为null,那么加入到out集合。父类会将消息往下传递
        if (decoded != null) {
            out.add(decoded);
        }
    }

    protected Object decode(
            @SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        //可读字节数小于需要截取的长度 返回null    frameLength  是由入参传入的 
        if (in.readableBytes() < frameLength) {
            return null;
        } else {
            //返回当前ByteBuf的一个分片,在底层共用数据
            return in.readRetainedSlice(frameLength);
        }
    }

2、使用特殊的消息分隔符

针对使用特殊的消息分隔符作为数据的边界情况,Netty提供了io.netty.handler.codec.DelimiterBasedFrameDecoder 解码器作为支撑。该解码器可以指定一段二进制序列作为分隔符,器会不断的累计从通道读取的数据并检查数据是否符合二进制序列。一旦发现符合要求的,则以分隔符作为边界分离数据报文。

常用的构造方法如下:

    /**
     * 创建一个实例
     *
     * @param maxFrameLength  如何读取超过 maxFrameLength   ,还未发现指定的分隔符,则抛出TooLongFrameException
     * @param delimiter  分隔符
     */
    public DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf delimiter) {
        this(maxFrameLength, true, delimiter);
    }

很多协议是以换行符作为消息分隔的,比如FTP协议。如果是这种情况,Netty直接提供了一个LineBasedFrameDecoder 解码器来支持该场景。其工作原理是遍历ByteBuf 中得可读字节,判断是否有“\r\n”或者“\n"。

   public LineBasedFrameDecoder(final int maxLength, final boolean stripDelimiter, final boolean failFast) {
        this.maxLength = maxLength;
        this.failFast = failFast;
        this.stripDelimiter = stripDelimiter;
    }

3、使用报文头+报文体协议

netty提供了io.netty.handler.codec.LengthFieldBasedFrameDecoder 来支持此种场景。这个编码器比较复杂,有四个重要属性,分别是:

  • lengthFieldOffset:该属性意味着报文长度字段在整体报文中的偏移量
  • lengthFieldLength:该属性意味着长度字段本身的字节长度,常见取值有1,2,4。该属性的值默认情况下指代报文体的长度。
  • lengthAdjustment:该属性意味着在读取完毕lengthFieldLength后,剩余需要读取的字节数为lengthFieldLength+lengthAdjustment的长度。
  • initialBytesToStrip:该属性意味着读取到的完整报文需要跳过一段长度的字节后的结果作为报文向后进行传递。

例如:lengthFieldOffset = 0,lengthFieldLength=2,lengthAdjustment=0,initialBytesToStrip=0

那么我们发生编解码的情况如下:

* BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
* +--------+----------------+      +--------+----------------+
* | Length | Actual Content |----->| Length | Actual Content |
* | 0x000C | "HELLO, WORLD" |      | 0x000C | "HELLO, WORLD" |
* +--------+----------------+      +--------+----------------+
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值