说一下 TCP 半包和粘包问题?

Sherwin.Wei Lv7

说一下 TCP 半包和粘包问题?

回答重点

因为TCP 协议是面向字节流的,数据在传输过程中没有明确的边界,所以会发生粘包和半包问题。

1)粘包问题

粘包指的是多个应用层的数据包在传输过程中被合并成一个 TCP 数据包,导致接收方无法区分各个独立的数据包。

2)半包问题

半包指的是一个应用层的数据包在传输过程中被拆分成多个 TCP 数据包,接收方需要多次读取才能获取完整的数据。

常见解决粘包与半包问题有三个方案

  • 固定长度:约定每个数据包的长度固定,接收方每次读取固定长度的数据即可。
  • 分隔符: 在每个数据包的末尾添加特定的分隔符(如换行符 \n),接收方以此为标志分割数据包。
  • 固定长度字段+内容:在每个数据包的头部添加一个固定长度的字段,表示数据包的总长度,接收方根据该字段读取完整的数据包。

扩展知识

进一步理解

TCP 是面向流的,数据之间没有界限的,且有发送缓冲区的概念。

如果 TCP 一次传输的数据大小超过发送缓冲区大小,那么一个完整的报文就需要被拆分成两个或更多的小报文,这可能会产生半包的情况,因为接收端收到不完整的数据,是无法解析成功的。

image.png

如果 TCP 一次传输的数据大小小于发送缓冲区,那么可能会跟别的报文合并起来一块发送,这就是粘包。

image.png

关于粘包与半包,我还看到有拿 MTU (最大传输单元)说事的,如果发送的数据大于 MTU 那就会出现拆包,导致半包的情况。

我个人觉得这里有点不对,简单理解下,UDP 也是要遵循 MTU 的呀,对吧?那它咋不会发生半包呢?

实际常见解决粘包与半包问题有三个方案:

  • 固定长度
  • 分隔符
  • 固定长度字段+内容

固定长度

比如现在要传输 ABC、EF 这两个包,如果不做处理接收端很可能收到的是 AB、CEF 或者 ABCE、F 等等。

这时候我们固定长度,我们规定每个报文长度都是 3,如果一个报文实际数据不足 3,那么就用空字符填充一下。

所以我们发送的报文是:

image.png

接收到的情况可能是:

image.png

但我们是按照 3 位来处理的,所以一次只会按照 3 位来解析,所以第一次虽然收到的数据是 ABCE,但我们就解析 3 位,即解析出 ABC,留着了个 E,等我们要继续解析 3 位的时候,发现长度不足 3,所以我们暂时先不管,先等等。

后面等到了 F“”,我们发现当下数据又满足 3 位了,所以我们接着解析 EF“” 。

这样就解决了粘包与半包问题。

固定长度的优点:简单。

缺点:固定长度不易于扩展,如果设置过大来满足业务场景的话,会导致空间浪费,因为不足长度的需要填充。

分隔符

这个应该很好理解, 还是拿 ABC、EF 这两个包举例,我在写完 ABC后,插入一个分号,组成ABC;,EF 同理:

image.png

这样以分隔符为界限来切分无界限的 TCP 流,来解决粘包与半包问题,这个应该很好理解,既然你 TCP 没界限,我业务上给你搞个界限。

分隔符的优点:简单,也不会浪费空间。

缺点:需要对内容本身进行处理,防止内容内出现分隔符,这样就会导致错乱,所以需要扫描一遍传输的数据将其转义,或者可以用 base64 编码数据,用 64 个之外的字符作为分隔符即可。

分隔符的处理方式在业界也是常用的,比如 Redis 就用换行符来分隔。

固定长度字段+内容

比如协议规定固定 4 位存放内容的长度,这样内容就可以伸缩:

image.png

还是拿 ABC、EF 这两个包举例:

image.png

解析流程是:先获取 4 位,如果当前收到的数据不够 4 位,那就再等等,够 4 位之后解析得到长度是 3,所以我再往后取 3 位,同样数据如果不够 3 位就再等等,够了的话就解析,这样就获取一个完整的包了。

然后接着往后获取 4 位,解析得到 2,同理根据 2 往后再取 2 位,解析得到 EF。

这种方式就是先解析固定长度的字段,获得后面内容的长度,根据内容长度来获取内容,从而得到一个完整的报文。

固定长度字段+内容的优点:可以根据固定字段精准定位,也不用扫描转义字符。

缺点:固定长度字段的设计比较困难,大了浪费空间,毕竟每个报文都带这个长度,小了可能不够用。

Comments
On this page
说一下 TCP 半包和粘包问题?