何为TCP拆包粘包问题,Netty又是如何帮我们解决它
粘包和拆包现象
在网络通信中,客户端向服务端发送两个数据包,C1和C2.如下图

站在客户端的角度,它是发送了两个不同的数据包。但是从服务端接受数据的角度来看就不一定了。可能出现的情况有三种。
第一种也是最符合直觉的,服务端按照客户端发送的顺序收到了数据包:D1和D2.
第二种,服务端第一次读取数据时读到了D1的一部分,再次读取时读取到了D1的剩余部分,第三次读取,读取完毕了D2.用图来表达就是

第三种,服务端读取一次数据,就完全读取到了D1和D2.就如下图

以上的第二种现象被称之为拆包,也就是一个完整的业务数据包被拆分到多次socket读取的结果中。而第三种先想想被称之为粘包,也就是在一次socket数据读取中,读取到了多个业务数据包。
原因分析
了解了现象,我们再来分析下原因。首先,TCP是传输层协议,是无法感知到应用层的数据分包大小的。可想而知,在TCP层面进行的数据传输,其数据切分必然无法和应用层协议保持一致,因此会出现拆包粘包也就不足为奇。如果只是分析到这个层面未免流于表面,下面针对各种可能性进行梳理。
滑动窗口导致的拆包
TCP使用滑动窗口算法进行流量控制。发送方有一个发送窗口,只有处于窗口内的数据才能允许被发送出去。如果发送的tcp报文一直没有收到对象的确认响应,则发送窗口的可用窗口会逐渐变小最终为0.接收方也有一个接收窗口,只有处于接收窗口内的数据,TCP确保其顺序正确才能被应用层读取。
如果客户端在发送数据时,发送窗口不足以容纳完整的业务数据包,则会导致发送窗口内的部分业务数据被发送到了对端。这就是因为发送窗口不足产生的拆包。
超过TCP包大小导致的拆包
网络中传输一个数据包有固定的长度大小。一般而言,在网络上传输数据,最大传输单元为1500字节。这1500字节还需要包含TCP报文头和IP报文头,因此留给业务数据的最大长度一般为1460字节。这个长度一般被称为MSS,最大报文长度。
如果客户端需要发送的一个业务报文超过了MSS,则操作系统会自动的按照MSS拆分成多个TCP数据包在进行发送。自然,这种情况在接收端收到的就不完整的数据报文。
滑动窗口导致的粘包
上面提到过,接收方也有一个滑动窗口。如果接收方应用程序一直没有从TCP连接中读取数据,则该接收窗口会逐渐被数据填满。而此时接收方应用程序从TCP读取数据时,可以一次性全部读取接收窗口内的数据。而这一个窗口的数据可能实际上包含了多个业务数据包。但是对于应用层而言,是无法分辨的。
解决粘包和拆包的手段
TCP是面向流的,无法感知应用层的数据包的分界。应用程序也决定不了传输层是拆包还是粘包。如果直接从TCP连接中读取数据的话,是无法进行数据边界判断的。但是可以从协议设计的角度出发,人为的进行数据包边界的制定和解析。几种常见的协议设计思路有:
- 业务数据使用定长报文:此种情况下,接收方只要读取到足够的数据长度就分离出一个业务数据报文。
- 使用特殊的消息分隔符:此种情况下,接收方检查每一个收到的字节,一旦某段字节序列符合分隔符特征,接收方就可以分离出这段数据报文
- 使用报文头+报文体的设计:这种设计中,一般报文头是一个定长或者不定长的数据,其中会包含报文体的长度或者总报文的长度。这样,接收方应用程序在读取报文头之后就可以知道整体的报文长度或者剩余的报文体的长度,依照长度,就可以对一个数据报文进行分离。
Netty如何解决粘包和拆包
根据上个章节给出的解决粘包拆包的三种协议设计思路,Netty也提供了不同的实现来进行支撑。一般而言,解码器需要放在管道的开头,第一个处理数据,才能实现分割的目的。
使用定长报文协议
针对定长报文协议,Netty提供了io.netty.handler.codec.FixedLengthFrameDecoder解码器。该解码器可以按照指定的字节长度将数据分离出来称为一个报文。
比如其可以将字节流

解码为

的三个业务数据包。
该解码类的使用也很简单,只需要传入一个整型变量作为构造方法入参,如new FixedLengthFrameDecoder(int)。该传入的整型变量就是一个业务数据报文的协议长度。
使用特殊的消息分隔符协议
针对使用特殊的消息分割符作为数据的边界的情况,Netty提供了作为支持。该解码器可以指定一段二进制序列作为
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
<p> 通过本专刊的学习,对网络开发所需掌握的基础理论知识会更加牢固,对网络应用涉及的线程模型,设计模式,高性能架构等更加明确。通过对Netty的源码深入讲解,使得读者对Netty达到“知其然更之所以然”的程度。在遇到一些线上的问题时,具备了扎实理论功底的情况,可以有的放矢而不会显得盲目。 本专刊购买后即可解锁所有章节,故不可以退换哦~ </p> <p> <br /> </p>


