什么是粘包?
首先明确TCP时面向字节流的协议(当用户消息通过 TCP 协议传输时,消息可能会被操作系统分组成多个的 TCP 报文,也就是一个完整的用户消息被拆分成多个 TCP 报文进行传输),UDP是面向报文的协议(每个 UDP 报文就是一个用户消息的边界,一个用户消息对应一个报文)。
在javaNIO中,如果客户端连续不断的向服务端发送数据包时,服务端接收的数据就会出现两个数据包粘在一起的情况。
- TCP是基于字节流的,虽然应用层和TCP传输层之间的数据交互是大小不等的数据块,但是TCP把这些数据块仅仅看成一连串无结构的字节流,没有边界。
- 从TCP的帧结构也可以看出在TCP首部没有表示数据长度的字段
基于上面两点,在使用TCP传输数据时,才有这种现象发生的可能,一个数据包中包含了发送端发送的两个数据包信息,这种现象称为粘包。
接收端收到了两个数据包,但是这连个数据包要么不完整,要么多出来一块,这种情况就发生了拆包和粘包的问题导致接收端在处理的时候非常困难,因此无法区分一个完整的包。
TCP 黏包是怎么产生的?
发送方粘包
采用 TCP 协议传输数据的客户端与服务器经常是保持一个长连接的状态(一次连接发一次数据不存在粘包),双方在连接不断开的情况下,可以一直传输数据。但当发送的数据包过于的小时,那么 TCP 协议默认的会启用 Nagle 算法,将这些较小的数据包进行合并发送(缓冲区数据发送是一个堆压的过程);这个合并过程就是在发送缓冲区中进行的,也就是说数据发送出来它已经是粘包的状态了。
Nagle算法的基本定义是任意时刻,最多只能有一个未被确认的小段。 所谓“小段”,指的是小于MSS尺寸的数据块,所谓“未被确认”,是指一个数据块发送出去后,没有收到对方发送的ACK确认该数据已收到。
接收方产生粘包
接收方采用 TCP 协议接收数据时的过程是这样的:数据到接收方,从网络模型的下方传递至传输层,传输层的 TCP 协议处理是将其放置接收缓冲区,然后由应用层来主动获取(C 语言用 recv、read 等函数);这时会出现一个问题,就是我们在程序中调用的读取数据,函数不能及时的把缓冲区中的数据拿出来,而下一个数据又到来并有一部分放入的缓冲区末尾,等我们读取数据时就是一个粘包。(放数据的速度大于应用层拿数据的速度)
怎么解决拆包和粘包
粘包的问题出现是因为不知道一个用户消息的边界在哪,如果知道了边界在哪,接收方就可以通过边界来划分出有效的用户消息。
一般有三种方式分包:
- 固定长度的信息
每个用户消息都是固定长度的 - 特殊字符作为边界
在两个用户信息之间插入一个特殊的字符串,接收方在接收数据时读到这个特殊字符就把认为已经读完一个完整的信息。
需要注意如果消息内容里有这个特殊字符,要对这个字符转义,避免被接收方判断成边界点解析到无效数据
- 自定义消息结构
我们可以自定义一个消息结构,由包头和数据组成,其中包头包是固定大小的,而且包头里有一个字段来说明紧随其后的数据有多大。
当接收方接收到包头就可以解析出数据的长度了,然后就继续读取数据,直到读满数据的长度,就可以组装成一个完整到用户消息来处理了。
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net