TCP的无边界性与分片和重组(所谓的“分包粘包”)

在发送或接受 TCP 字节流时存在的分片与重组是个老生常谈的话题了,有些人习惯叫它“分包粘包”。实际上 TCP 是流模式协议,面向字节流的,没有包这么一说,所谓的“分包粘包”是应用层解析不好的事情

TCP 是啥&在做啥

流协议

大学计算机网络都学过,传输控制协议(TCP)是一种面向连接的、可靠的流模式的协议。那么什么是流协议呢?它又为什么可靠呢?

TCP 对上层,也就是应用层,提供一个保证送达且顺序正确的字节流,因此上层看来,他就是一个可靠的流。

传输时,上层将字节丢到 TCP 的缓冲区,到达一定条件后,TCP 会取出字节流进行分割,加上自己的报头组成一个又一个的 TCP 报文,并丢给下层网络层去处理。

报头

TCP 报文中的报头是“可靠传输”的关键部分,它的结构是这样的:

其中序号代表本报文包含的字节流中的第一个字节在这一次传输方向上的全部字节流中的偏移量(初始序号不一定从0开始,随机的),而确认序号代表本报文所在主机已经收到的对方发来的字节流偏移索引。

注意,序号与确认号无关,序号是我方发送的字节流的标识,而确认号是我方已接受到的对方所发送的字节流的标识,为了提高效率,把我方的确认放在了同一个数据报文里。

接收报文与拼接字节流

报文接收后暂存于缓冲区内,TCP 会按照报文头中的序号排列把字节流取出来拼接成正确的顺序,并发送 ACK 报文(不一定实时发送),也就是到达确认。

如果一直没有 ACK 报文返回,说明大概率是丢了,会重传必要的数据报文。当然,重传是个很复杂的策略,滑动窗口、重传计时器之类的都是深坑,不能一概论之。

ACK 延迟发送

ACK 发送不是实时的,为了效率,TCP 会尽量少发纯 ACK 报文。

同时,TCP 可以用一个 ACK 确认之前所有的报文,也就是不一定收一个就 ACK 一次。

为了提高效率,ACK 会等一段时间,这样一旦我方发送数据就可以把 ACK 捎过去,同时如果有新来的报文,也可以省下一次 ACK。如果等待超时,就发个纯 ACK 报文过去。

Nagle 算法

Nagle 算法规定这条链接上同时只能存在一个未得到 ACK 的小分组

举个例子,发送了一个小分组但暂时未得到 ACK,之后未发送的小分组只能在缓冲区等待,如果 ACK 迟迟不到,缓冲区内的小分组越来越多,最终到了 MSS 值,就拼成大分组发出去。

所谓的“分包粘包”到底指什么

开头说了,TCP 没有“包”这么一说,所谓的“分包粘包”是应用层解析不好的事情。

TCP 维护的字节流,保证字节流顺序一致,但是,字节流数据是无边界性的。

应用层发下来的数据,不管是一次发的还是两次发的,通通放缓冲区,拼成一个字节流用滑动窗口处理,只管字节流顺序

长字节流涉及到 MTU 的问题,会被切分;为了保证发送效率或者 ACK 延迟,短数据可能被 Nagle 算法拼成大包再发。

又或者涉及重传和拥塞控制,导致报文到达的时序有问题,还在等报文补齐。

下层 MTU 限制 + Nagle 算法 + ACK 延迟发送 + 重传 + 拥塞控制,就会出现本文所谈的奇妙现象。

最终效果:应用层一次发送 100 个字节,被丢到发送缓冲区里,按策略发;另一端调一次接收方法,只拿到了 10 个字节,又或者拿到了 120 个字节等。

对方应用层取了一次,发现数据不对,拿到的字节不是多了就是少了,或者迟迟接收不到(小分组延迟发送),一些人就管这种现象叫做“TCP 分包粘包”,TCP 表示这锅我不背。

如何解决

1.处理无边界性

TCP 维护的字节流虽然是无边界性的,但我们可以设计一个应用层协议,让字节流变得有边界。

在之前做的项目中,我做过一个简单的应用层协议,该协议结构如下:

字节 内容 含义
0 / 一个数据包的标识符
1-2 unsigned short 该包总长度(包含头与自身)
3-n(n<65538) UTF8 JSON 数据

收到字节流时,先将其暂存,然后找标志’/’,以确定数据包,之后解析头信息,获取长度,最后读取对应长度的数据,将字节转为 UTF-8 字符串。

当时的应用场景是一个数值监控,对实时性要求比较高,但不在乎数据连续性,因此当暂存缓冲区到达一定规模后,直接取最后一个标识符,把前面的字节抛掉,避免更新过慢或滚雪球的问题。

2.处理短字节分组延迟发送

为了提高网络传输效率,当发送缓冲区内容小于 MSS 字节数时,Nagle 等一套 TCP 的控制策略可能会延迟发送消息,而延迟 ACK 会加重这一延迟。

因此,如果对实时性要求极高,可以禁掉 Nagle 策略,以 C# Socket 为例:

socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);

其中 SocketOptionName.NoDelay 即禁止 Nagle 算法。

梓喵出没博客(azimiao.com)版权所有,转载请注明链接:https://www.azimiao.com/6417.html
欢迎加入梓喵出没博客交流群:313732000

我来吐槽

*

*