粘包和半包的来源,得从操作系统底层说起。

大家都知道,底层网络是以二进制字节报文的形式来传输数据的,并且数据在进入传输阶段之前,还会发生CPU数据复制和DMA数据复制。无论在数据传输阶段,还是在数据复制阶段,都可能存在二进制字节数据的二次分隔。

写数据的过程大致为:编码器将一个Java类型的数据转换成底层能够传输的二进制ByteBuf缓冲数据。发送端的应用层Netty程序以ByteBuf为单位来发送数据,这些数据首先会通过CPU复制的方式,复制到了底层操作系统内核缓冲区;然后通过DMA复制的方式,复制到网卡设备,这个DMA复制过程,会发生二进制数据的二次分隔;数据被复制到网卡设备之后,网卡设备协议栈处理程序会按照TCP/IP协议规范对数据包进行二次封装,封装成传输层TCP层的协议报文之后再进行发送,在这个数据包封装的过程中,也会发生二进制数据的二次分隔。

为什么在数据复制阶段,存在二进制字节数据的二次分隔呢?

发送端在DMA复制阶段,DMA设备会把内核缓冲区(Socket发送缓冲区)中的数据复制到网卡设备中,当TCP内核缓冲区的单个数据包,可能比较小,一次DMA复制的会却不止一个内核缓冲区中的小包,会将多个小数据包一块复制,以便提升效率。这就是数据复制阶段的二进制字节数据的二次分隔。这种数据包的二次分隔操作操作,可能导致复制到网卡设备的数据包,出现粘包现象或者半包现象。

为什么在数据传输阶段,存在二进制字节数据的二次分隔呢?

一个TCP协议报文的有效数据(净菏数据)大小是有限制的,这个报文有效数据的大小被称之为MSS(Maximum Segment Size 最大报文段长度),具体的MSS值会在三次握手阶段会进行协商,但是最大不会超过1460个字节。

正由于一个TCP数据包MSS值最大为1460,协议处理程序会最大限度的利用一个报文的空间。如果原始的ByteBuf太小,协议处理程序会合并多个ByteBuf的二进制数据进行发送;反过来,如果原始的ByteBuf太大,协议处理程序会将ByteBuf的二进制数据分成多个二进制数据包,进行发送。这就是在数据传输阶段二进制字节数据的二次分隔,这种传输阶段的二次分隔操作,可能导致接收端接所收到的数据包,出现粘包现象或者半包现象。

所以,无论数据传输阶段的二进制数据分隔,还是在数据复制阶段的二进制数据分隔,都可能导致最终粘包现象或者半包现象。

如何解决呢?

基本思路是,在接收端,Netty程序需要根据自定义协议,将读取到的进程缓冲区ByteBuf,在应用层进行二次组装,重新组装我们应用层的数据包。

接收端的这个过程通常也称为分包,或者叫做拆包。在Netty中分包的方法,主要有两种方法:

(1)可以自定义解码器分包器:基于ByteToMessageDecoder或者ReplayingDecoder,定义自己的用户缓冲区分包器。

(2)使用Netty内置的解码器。如可以使用Netty内置的LengthFieldBasedFrameDecoder自定义长度数据包解码器,对用户缓冲区ByteBuf进行正确的分包。

除非注明,否则均为风笛原创文章,转载必须以链接形式标明本文链接

本文链接:https://www.lifd.site/tech/ban-bao-wen-ti-de-gen-yin-fen-xi/

“觉得文章还不错?微信扫一扫,赏作者一杯咖啡吧~”
分类
标签
guest

0 评论
最旧
最新 最多投票
内联反馈
查看所有评论

相关文章

TCP的三次握手与四次挥手

TCP的三次握手 TCP连接的建立时,双方需...

为什么生产环境是不建议使用Executors快捷创建线程池?

在生产环境中,使用Executors提供的快...

Redisson分布式锁的watch dog自动续期机制

背景 据Redisson官网的介绍,Redi...

图数据库选型:Neo4j、Janus、HugeGraph

图数据库(Graph Database)是一...

Ubuntu使用Certbot生成https证书

更新包列表 sudo apt update ...