TCP 粘包 | 计算机基础
本文介绍了“粘包”问题的概念、原因以及解决方法。
粘包
由于 TCP “流”的特性以及网络状况,在进行数据传输时假设我们连续调用两次 send 分别发送两段数据 data1 和 data2,在接收端有以下几种接收情况(当然不止这几种情况,这里只列出了有代表性的情况):
- 先接收到 data1,然后接收到 data2
- 先接收到 data1 的部分数据,然后接收到 data1 余下的部分以及 data2 的全部
- 先接收到了 data1 的全部数据和 data2 的部分数据,然后接收到了 data2 的余下的数据
- 一次性接收到了 data1 和 data2 的全部数据
对于第一种情况正是我们需要的,不再做讨论。对于后三种情况就是常说的“粘包”,就需要把接收到的数据进行拆包,拆成一个个独立的数据包。而为了拆包就必须在发送端进行封包。
对于 UDP 来说就不存在拆包的问题,因为 UDP 是个”数据包”协议,也就是两段数据间是有界限的,在接收端要么接收不到数据要么就是接收一段完整的数据,不会少接收也不会多接收。
原因
TCP 协议是基于字节流的传输层协议,其中不存在消息和数据包的概念。应用层协议没有使用基于长度或者基于终结符的消息边界,导致多个消息的粘连。
粘包可发生在发送端也可发生在接收端。原因如下:
- 由 Nagle 算法造成的发送端的粘包。Nagle 算法是一种改善网络传输效率的算法,但也可能造成困扰。简单来说,当要提交一段数据给 TCP 发送时,TCP 并不立刻发送此段数据,而是等待一小段时间,看看在等待期间是否还有要发送的数据,若有则会一次把多段数据发送出去。像上述后两种情况就有可能是 Nagle 算法造成的。
- 接收端接收不及时造成的接收端粘包。TCP 会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层由于某些原因不能及时取出 TCP 的数据,就会造成 TCP 缓冲区中存放了多段数据。
解决方法
最初遇到粘包的问题时,大家可能觉得可以在两次 send 之间调用 sleep 来休眠一小段时间,以此来解决。这个解决方法的缺点是显而易见的:使传输效率大大降低,而且也并不可靠。
对于发送方造成的粘包现象,可以通过关闭 Nagle 算法来解决,使用 TCP_NODELAY 选项来关闭 Nagle 算法。但是无法解决接收方粘包的问题。在应用层对数据包进行封包和拆包,就能解决这个问题。
封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(过滤非法包时会加上“包尾”内容)。包头其实上是个大小固定的结构体,其中有个结构体成员变量表示包体的长度,这是个很重要的变量,其他的结构体成员可根据需要自己定义。根据固定的包头长度以及包头中含有的包体长度的变量值就能正确的拆分出一个完整的数据包。
利用底层的缓冲区来进行拆包时,由于 TCP 也维护了一个缓冲区,所以可以利用 TCP 的缓冲区来缓存发送的数据,这样一来就不需要为每一个连接分配一个缓冲区了,对于利用缓冲区来拆包,也就是循环不停地接收包头给出的数据,直到收够为止,这就是一个完整的 TCP 包。
为了解决粘包的问题,大家通常会在所发送的内容前加上发送内容的长度,所以对方就会先收 4 Byte,解析获得接下来需要接收的长度,再进行收包。
参考
《后台开发:核心技术与应用实践》
为什么 TCP 协议有粘包问题
TCP 粘包 | 计算机基础