- 0
- 7 浏览
粘包和半包的来源,得从操作系统底层说起。
大家都知道,底层网络是以二进制字节报文的形式来传输数据的,并且数据在进入传输阶段之前,还会发生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/