1. TCP通讯“粘包问题”
使用TCP通讯时,需要考虑TCP通讯的“粘包问题”。
“粘包问题”的本质就是数据读取边界错误所致。例如发送端发送了3次数据,分别是“1111”, “2222”, “3333”,接收端可能接受2次数据,分别是“111122”,“223333”,抑或“11112222”和“3333”。
产生上述“问题”的本质原因是TCP是基于字节流而不是消息包的协议,TCP会把数据变成字节流发到对方去,而且保证顺序不会乱。
具体原因为
1)发送方原因
TCP默认使用Nagle算法(主要作用:减少网络中报文段的数量),而Nagle算法主要做两件事:
a.只有上一个分组得到确认,才会发送下一个分组
b. 收集多个小分组,在一个确认到来时一起发送
Nagle算法造成了发送方可能会出现粘包问题
2)接收方原因
TCP接收到数据包时,并不会马上交到应用层进行处理,或者说应用层并不会立即处理。实际上,TCP将接收到的数据包保存在接收缓存里,然后应用程序主动从缓存读取收到的分组。这样一来,如果TCP接收数据包到缓存的速度大于应用程序从缓存中读取数据包的速度,多个包就会被缓存,应用程序就有可能读取到多个首尾相接粘到一起的包。
综上,这就需要在应用层确定数据边界,搞定字节流解析,解决“粘包问题”(上文“粘包问题”4字一直加上了引号,是因为这其实不是问题,是TCP基于字节流通讯的特性,本来就应该在应用层完成字节流的解析)。
2. 解决办法
如上所述,需要在应用层搞定字节流解析,确定数据边界,解决“粘包问题”。
在应用层确定数据边界,完成字节流解析主要有3种方法,分别是
(1)发送固定长度的消息
(2)把消息的尺寸与消息一块发送
(3)使用特殊标记来区分消息间隔
第一种“发送固定长度的消息”的方法,一般局限性较大,不推荐使用该方法。下面给出将第二种和第三种结合起来确定数据边界,做字节流解析的方法。
双方约定通讯格式为:HEAD××数据包,其中
a.HEAD为特殊标记,用来做消息分割(双方要约定数据包不包含消息分隔符HEAD,否则会造成数据解析错误)
b. xx为N个字节的数据长度
c. 数据包为实际的数据内容。
发送方按照该格式做数据封包,接受者按照该格式解析数据。
HEAD作为特殊标记分割了各个消息,之后解析出数据长度后,按照该长度解析后面的数据,通过数据长度也可以知道本次接收到的消息是不是完整的消息。
3. 问题扩展
1)什么时候需要处理“粘包问题”
a. 如果发送方发送的多组数据本来就是同一块数据的不同部分,比如说一个文件被分成多个部分发送,这时当然不需要处理粘包现象
b. 如果多个分组毫不相干,甚至是并列关系,那么这个时候就一定要处理粘包现象了
2) UDP通信会不会粘包
TCP为了保证可靠传输并减少额外的开销(每次发包都要验证),采用了基于流的传输,基于流的传输不认为消息是一条一条的,是无保护消息边界的(保护消息边界:指传输协议把数据当做一条独立的消息在网上传输,接收端一次只能接受一条独立的消息)。UDP则是面向消息传输的,是有保护消息边界的,接收方一次只接受一条独立的信息,所以不存在粘包问题。
3)TCP可靠、UDP不可靠的原因
a. TCP可靠数据传输原理
每个Tcp socket在内核中都有一个发送缓冲区和一个接受缓冲区。tcp协议要求对端在接受到tcp数据报之后,要对其序号进行ACK,只有当接受到一个tcp数据报的ACK之后,才可以把这个tcp数据报从socket的发送缓冲区清除,另外tcp还有一个流量控制功能,tcp的socket接受缓冲区接受到网络上来的数据缓存起来后,如果应用程序一直没有读取,socket接受缓冲区满了之后,发生的动作是:通知对端TCP协议中的窗口关闭,这便是滑动窗口的实现,保证TCP socket接受缓冲区不会溢出,因为对方不允许发送超过所通知窗口大小的数据, 这就是TCP的流量控制,如果对方无视窗口大小而发出了超过窗口大小的数据,则接收方TCP将丢弃它。这两点保证了tcp是可靠传输的。
b. UDP不可靠数据传输原理
UDP只有一个socket接受缓冲区,没有socket发送缓冲区,即只要有数据就发,不管对方是否可以正确接受。而在对方的socket接受缓冲区满了之后,新来的数据报无法进入到socket接受缓冲区,此数据报就会被丢弃,udp是没有流量控制的,故UDP的数据传输是不可靠的。