起因:
之前遇到的分包问题是由于数据包过大,所以没什么感觉。但最近发现如果客户端发送数据频率过快(异步发送,且短时间内频率很快),也会发生分包,粘包的问题。之后通过LineBasedframeDecoder解决了。
但在随后发现,通过同步阻塞式(inputstream.read())的接收发送数据频率过快的数据,并没有出现粘包、半包的问题,令我百思不得其解。
今天突然在复习tcp/ip协议栈的时候灵感迸发,赶紧记录下。
根本原因:tcp分包
tcp分包原因是由于MSS(Maximum Segment Size,最大报文长度),在tcp3次握手的时候,两端分别获知了对方一次可接受的数据包大小。
于是当应用层向传输层下发的数据包过大时,会产生TCP分包,将一份数据切分成多个数据,每一个数据都有相同的标识,和各自的偏移值。可以在接收端将数据拼起来。
inputstream.read()和netty有何区别
前一个是同步阻塞式I/O模型,netty是多路复用I/O模型,基于select/poll
为什么netty会出现分包、粘包
我们来看多路复用的I/O模型
netty通过一个线程去监控select,只有该线程被阻塞在内核态。而主线程不受影响,依然处于用户态执行代码。当有数据到达时,触发事件,由主线程向内核发起read请求,然后用户态切换成内核态,再做复制。
注意到了吗:当一个数据包的所有分片被拼装完成时,并没有马上被复制,返回用户态,而是等用户态的线程发起read请求后,从用户态切为内核态,才进行的复制。而这时同一个客户端的下一个数据包的一个分片可能已经在这间隔中来了,导致复制的数据多了一个分片!
为什么同步阻塞没有分包、粘包
我们分析了多路复用产生的原因,再来看就简单了
我们来看同步阻塞的I/O模型
当数据包完整到达后,由于此时在内核态,内核立即开始复制。复制完成后即返回用户态。由于中间未有状态切换,所以复制的数据的操作不会被耽搁。