试论TCP协议下的“粘包”问题

一、为什么很多人都会谈论“TCP粘包问题”?

  1. RFC文档中以及计算机网络相关知识中可以很容易得知:首先,TCP层传输是流式传输,不会发送数据包;其次,数据包是存在于网络层的概念,而非传输层。那为啥还说TCP粘包问题呢?

  1. 自顶而下学习计算机网络相关知识,可以得知应用程序首先要将自己的数据通过套接字发送。应用层交付给TCP的是结构化的数据,结构化的数据到了TCP层做流式传输。

  1. 所谓的“粘包问题”其实所描述的应该是指TCP传输中数据的无边界性问题,这一特性是由TCP协议的特点决定的。而TCP传输的无边界性是由下方列出的“特点5”所决定的,其将应用层交付下来的数据均视为无结构的字节流,使用一次发送的字节长度与另一端一次接收的字节长度没有对应关系。就像管道运输水流一样没有边界;而不像一个一个的箱子在履带上传输,每个箱子就是UDP协议中的一个包。TCP协议有如下5个特点
    (1) TCP是面向连接的运输层协议;
    (2) TCP连接只能有两个端点;
    (3) TCP提供可靠交付;
    (4) TCP提供全双工通信;
    (5) TCP协议面向字节流;

  1. 从原理上来讲TCP是面向字节流的传输,所以其自身其实是不会有所谓的粘包问题的。

  2. 流,最大的问题是没有边界,没有边界就会造成数据粘在一起,这种粘在一起就叫做粘包。当然有人可能就要问了,那咋不叫“粘段”呢?这就是比较尴尬的问题了,这种叫法的具体来源无从知晓,正如鲁迅先生曾说过的——“世界上本没有路,走的人多了,就有了路。” 我想,这种叫法也是叫的人多了,就有了这个叫法。



二、什么叫“粘包”?

答: TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。



三、粘包发生在那些情况下?

TCP是端到端传输的,同时TCP连接是可复用的。什么叫复用呢?复用就是一条连接可以供一台主机上的多个进程使用。


  1. 由TCP连接复用造成的粘包问题:

如果没有复用,一个连接只提供给端到端的两个进程使用,这是数据的传输方和发送方都是约定好了数据的格式的,但是多个进程使用一个TCP连接,此时多种不同结构的数据进到TCP的流式传输,边界分割肯定会出这样或者那样的问题。

如果利用tcp每次发送数据,就与对方建立连接,然后双方发送完一段数据后,就关闭连接,这样就不会出现粘包问题。


  1. 因为TCP默认会使用Nagle算法,此算法会导致粘包问题:

Nagle算法主要做两件事
(1) 只有上一个分组得到确认,才会发送下一个分组;
(2) 收集多个小分组,在一个确认到来时一起发送。

多个分组拼装为一个数据段发送出去,如果没有好的边界处理,在解包的时候会发生粘包问题。


3.数据包过大造成的粘包问题:

比如应用进程缓冲区的一条消息的字节的大小超过了发送缓冲区的大小,就有可能产生粘包问题。因为消息已经被分割了,有可能一部分已经被发送出去了,对方已经接受了,但是另外一部分可能刚放入套接口发送缓冲区里准备进一步发送,就直接导致接受的后一部分,直接导致了粘包问题的出现。


4.流量控制,拥塞控制也可能导致粘包。


5.接收方不及时接收缓冲区的包,造成多个包接收。



四、粘包问题如何处理?

也就是如何处理TCP数据的无边界性带来的问题?

  1. Nagle算法问题导致的,需要结合应用场景适当关闭该算法。每个语言所给出来的接口函数名称不尽相同,比如在Java中,发送数据之前调用(Socket)socket.setTcpNoDelay(true); 就能够关闭Nagle算法;总之,需要记住的是一般通过TCP_NODELAY选项来关闭默认处在开启状态下的Nagle算法

  1. 尾部标记序列。通过特殊标识符表示数据包的边界,例如\n\r,\t,或者一些隐藏字符。

  1. 头部标记分步接收。在TCP报文的头部加上表示数据长度。

  1. 应用层发送数据时定长发送。

  1. (详述“3.”)自定义一个头部,将字节流重新组装,套上头部作为必要的控制信息(其中包含接下来要发送的数据长度),一起传输给TCP的另一端,接收端先接收完整的头部然后解析,再来控制之后的数据接收和存储。源码


参考:
[1] RFC官方文档
[2] RFC文档中文翻译版
[3] Nagle算法百度百科
[4] TCP_NODELAY选项在Java中的实现
[5] HTTP2对TCP_NODELAY的描述


[知识回顾]





作者:艾孜尔江
转载或使用请务必标明出处!

posted @ 2020-10-28 15:46  艾孜尔江  阅读(225)  评论(0编辑  收藏  举报