粘包现象
TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据。
问题分析
发送方
TCP默认会使用Nagle算法。Nagle算法主要做两件事:
- 只有上一个分组得到确认,才会发送下一个分组
- 收集多个小分组,在一个确认到来时一起发送
所以,正是Nagle算法造成了发送方有可能造成粘包现象。
接收方
TCP接收到分组时,并不会立刻送至应用层处理,或者说,应用层并不一定会立即处理;实际上,TCP将收到的分组保存至接收缓存里,然后应用程序主动从缓存里读收到的分组。这样一来,如果TCP接收分组的速度大于应用程序读分组的速度,多个包就会被存至缓存,应用程序读时,就会读到多个首尾相接粘到一起的包。
根本原因
- TCP 协议是基于字节流的传输层协议,其中不存在消息和数据包的概念
- 应用层协议没有使用基于长度或者基于终结符的消息边界,导致多个消息的粘连
解决方案
短连接
最简单的方法就是短连接,也就是需要发送数据的时候建立TCP连接,发送完一个数据包后就断开TCP连接,这样接收端自然就知道数据结束了。但是这样的方法因为会多次建立TCP连接,性能低下。
定长结构
接收端必须每次都严格判断接收到额数据的长度,当收到的数据长度不足时,需要再次接收数据,直到满足长度,当收到的数据多于固定长度时,需要截断数据,并将多余的数据缓存起来,视为长度不足需要再次接收处理。
不定长结构
选一个固定的字符作为数据包结束标志,接收到这个字符就代表一个数据包传输完成了,但是这只能应用于字符数据,因为二进制数据中很难确定结束字符到底是结束还是原本要传输的数据内容。
最通用的做法是在每次发送的数据的固定偏移位置写入数据包的长度。
因为在每次发送的数据的固定偏移位置写入数据包的长度的方法是最通用的一种方法,所以对这种方法实现中的一些容易出错误的地方在此特别说明:
-
通常我们使用2~4个字节来存放数据长度,多字节数据的网络传输需要注意字节序,所以要注意接受者和发送者要使用相同的字节序来解析数据长度。
-
每次新开始接收一段数据时不要急着直接去解析数据长度,先确保目前收到的数据已经足够解析出数据长度,例如数据开头的2个字节存储了数据长度,那么一定确保接收了2个字节以上的数据后才去解析数据长度。
-
有些非法客户端或者有bug的客户端可能会发出错误的数据,导致解析出的数据长度异常的大,一定要对解析出的数据长度做检查,事先规定一个合适的长度,一旦超过果断关闭SOCKET,避免服务器无休止的等待下去浪费资源。
为什么udp不会粘包?
UDP通信是以数据包作为界限的,发送端不会对数据包进行合并发送,服务端只能一次一次的接收数据包,client发送多少次,server就需接收多少次,因此不会发生粘包。