【杂记】 粘包的原理知识 以及本项目如何处理粘包 by TEnth丶

343 阅读3分钟

粘包的知识点

所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

须知:

  • 只有TCP有粘包现象,UDP永远不会粘包

    应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。(因为TCP是流式协议,不知道啥时候开始,啥时候结束)。而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。

  • 粘包不一定会发生

可能发生粘包的地方:

  • 可能是在客户端已经粘了
  • 客户端没有粘,可能是在服务端粘了

发生粘包的2种情况:

  • 发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会当做一个包发出去,产生粘包)
  • 接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

解决粘包的办法:

  • 固定缓冲区大小,使服务端和客户端发送和接受的长度保持一个相同的定长。

  • 自定义请求协议:

    这种解决方案的实现思路是将请求的数据封装为两部分:数据头+数据正文,在数据头中存储数据正文的大小,当读取的数据小于数据头中的大小时,继续读取数据,直到读取的数据长度等于数据头中的长度时才停止。

    因为这种方式可以拿到数据的边界,所以也不会导致粘包和半包的问题,但这种实现方式的编码成本较大也不够优雅,因此不是最佳的实现方案

  • 特殊字符结尾,按行读取

    以特殊字符结尾就可以知道流的边界了,因此也可以用来解决粘包和半包的问题,此实现方案是我们推荐最终解决方案

本项目中遇到的粘包以及解决办法

项目作为一个webserver,当然与服务端互通的便为web浏览器了,那么对于不同的浏览器,他们的http请求对请求url的长度限制也是不同的,并且这个数值通常会比较大(如IE:2093, Firefox:65536,Chrome:8182,Safari:80000),如果使服务端也采用如此大的缓冲区显然是不可行的,所以方案1排除。

对于方案二,http的请求协议是无法改变的,显然也是不行的。

那么对于一个web服务器,解决粘包的问题就需要从第3个方案入手,同时这也正好是http请求url所留给web服务器使用的方案。

对于每个HTTP报文都有固定的格式(详细见本栏目《服务器编程基本框架及原理》),而每个报文都是以 \r\n 来结束的,所以在本项目对http报文处理上也是根据这个规则来进行拆包的。