Netty数据存储分析和经典问题之粘包拆包解决方案

1.Java NIO原生ByteBuffer的缺陷分析

ByteBuffer是JAVA NIO原生的,就是搞一个数据缓冲区,把数据弄到缓冲区里进行操作,这块缓冲区可以复用,下次再处理数据时,可以重复使用这块区域,因此就不用频繁的回收短期对象,提升空间的使用效率。

但是原生的NIO ByteBuffer的数据长度是固定不变的,一旦分配完毕,就不能进行动态扩容和收缩了,所以在使用的时候,如果数据大小超出该数据区存储大小,那么这些数据就不能在这个缓冲区操作了。

另外原生的ByteBuffer还有一个缺陷,就是缓冲区内,会有一个指针,这个指针是用来记录数据读取和写入的位置,每次进行读写数据的时候,都会用该指针进行索引位定位。因为他只有一个指针,所以在进行读写的时候,经常需要手工去进行指针位置的调整,这样操作非常麻烦,而且非常容易出问题,一旦程序没写好,就可能直接导致数据错乱的问题,一些高级特性还需要进行扩展实现才行。

针对以上问题,Netty自己又额外开发了ByteBuf,用来当做字节数据缓冲区,这个缓冲区专门解决了上面的两个问题。Netty的底层是通过网卡读取过来的数据,都是扔到这里去的,这样就可以直接让netty自己去管理框架运行中的请求、响应等数据的内存管理了。
如下图所示:在这里插入图片描述

2.Netty经典问题之粘包和拆包

什么是粘包?什么是拆包?

在这里插入图片描述

TCP传输数据的时候,并不会关心数据之间的隔离,只要达到TCP 最大报文段长度,就会将数据发送出去。因此会出现两种情况:
   1.第一种情况:要发送的数据小于TCP 最大报文段长度,那么就会出现多个数据一次发送出去,就如上图的两个数据包粘合的情况,这种情况就是粘包。
   2.第二种情况:要发送的数据大于TCP 最大报文段长度,那么该数据包就会被拆分成多份进行多次发送,就如上图的两个数据包half一样,这种情况就是拆包。

Netty中的解决方法

为了解决粘包和拆包的问题,Netty中提供了三种解码处理器,分别是:
  1.LineBasedFrameDecoder(换行符解码器)
该解码器会将接收到的数据,以换行符为标识,来指定切割数据,只有读取到了换行符,才会认定从读取开始位置到换行符位置为一段完整的数据。
  2.DelimiterBasedFrameDecoder(指定特殊字符解码器)
该解码器是可以自定义特殊符号,用来识别数据包中的特殊字符,识别到指定特殊字符,就会认定特殊字符之前的数据为一段完整数据。
  3.FixedLengthFrameDecoder(固定长度解码器)。
该解码器是可以指定数据长度大小,在读取数据时,会将数据以指定大小分割,并认定每段指定大小数据为一段完整数据。
  4.自定义解码/编码器
如果业务特殊,以上几种不能满足你的需求,可以自定义编码、解码器,实现方式如下:

//自定义解码器
public class MySelfDecoder extends ByteToMessageDecoder {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        System.out.println("自定义解码器被调用");
    }
}

//自定义编码器
public class MySelfEncoder extends MessageToByteEncoder {
    @Override
    protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
        System.out.println("自定义编码器被调用");
    }
}
以LineBasedFrameDecoder为例,我们看下底层实现

此解码器的工作原理就是读取ByteBuf里的数据时,会解析换行符,如果发现换行符,就会将开始读取的索引位置到换行符的索引位置之间的数据获取出来,并将其视为一条完整数据并拆分出来进行处理。
我们来看下源码:
在这里插入图片描述
重点在findEndOfLine方法:

    private int findEndOfLine(ByteBuf buffer) {
        int totalLength = buffer.readableBytes();
         //从readerIndex+offset开始,到totalLength - offset截止,默认offset=0,查找\n的位置
        int i = buffer.forEachByte(buffer.readerIndex() + this.offset, totalLength - this.offset, ByteProcessor.FIND_LF);
        if (i >= 0) {
            this.offset = 0;
            //看前一个字节是否是\r,如果是前移一位 (换行符解码器会识别\n和\r\n)
            if (i > 0 && buffer.getByte(i - 1) == 13) {
                --i;
            }
        } else {
            //如果没找到换行符,offset就等于当前可读字节数
            this.offset = totalLength;
        }
        return i;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值