IP:超时,重传,控流,拥塞处理(二)


本文概要:

TCP 重传机制TCP 控流TCP 拥塞处理

TCP 重传机制

1、ACK 携带信息

期待要收到下一个数据包的编号;接收方的接收窗口的剩余容量。

注意:由于 TCP 通信是双向的,所以双方都需要发送 ACK。两方的窗口大小,很可能是不一样的。而且 ACK 只是很简单的几个字段,通常与数据合并在一个数据包里面发送。

上图一共4次通信。

第一次通信,A 主机发给B 主机的数据包编号是1,长度是100字节;

第二次通信 B 主机的 ACK 编号是 1 + 100 = 101,第三次通信 A 主机的数据包编号也是 101。同理

第二次通信 B 主机发给 A 主机的数据包编号是1,长度是200字节;

第三次通信 A 主机的 ACK 是201,第四次通信 B 主机的数据包编号也是201

2、TCP 协议可以保证数据通信的可靠性,做法如下

每一个数据包都带有下一个数据包的编号。如果下一个数据包没有收到,那么 ACK 的编号就不会发生变化。

接收端给发送端的 Ack确认只会确认最后一个连续的包,比如,发送端发了1,2,3,4,5一共五份数据,接收端收到了1,2,于是回 ack 3,然后收到了4(注意此时3没收到),此时的 TCP 会怎么办?

我们要知道,因为正如前面所说的,SeqNum 和 Ack 是以字节数为单位,所以 ack 的时候,不能跳着确认,只能确认最大的连续收到的包,不然,发送端就以为之前的都收到了。

如果发送方发现收到连续的重复 ACK,或者超时了还没有收到任何 ACK,就会确认丢包,从而再次发送这个包。通过重传这种机制,TCP 保证了不会有数据包丢失

3、超时重传机制

一种是不回 ack,死等3,当发送方发现收不到3的 ack 超时后,会重传3。一旦接收方收到3后,会 ack 回 4——意味着3和4都收到了。

但是,这种方式会有比较严重的问题,那就是因为要死等3,所以会导致4和5即便已经收到了,而发送方也完全不知道发生了什么事,因为没有收到 Ack,所以,发送方可能会悲观地认为也丢了,所以有可能也会导致4和5的重传。

对此有两种选择:

1 )仅重传 timeout 的包。也就是第3份数据。2) 重传 timeout 后所有的数据,也就是第3,4,5这三份数据。
第一种会节省带宽,但是慢,第二种会快一点,但是会浪费带宽,也可能会有无用功。但总体来说都不好。因为都在等 timeout,timeout 可能会很长,针对两种情况 TCP/IP 协议给了三种算法供选择:Fast Retransmit,Selective Acknowledgment,Duplicate SACK。后续可针对此类算法进行展开详解

4、超时重传中的超时 TIMEOUT 设置 问题

针对上述情况得知合理的设置 timeout 时间长度对重传很重要

1)设长了,重发就慢,丢了老半天才重发,没有效率,性能差;

2)设短了,会导致可能并没有丢就重发。于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发。

这个超时时间在不同的网络的情况下,不是设置一个固定值。只能动态地设置。 为了动态地设置,TCP 引入了 RTT——Round Trip Time,也就是一个数据包从发出去到回来的时间。

这样发送端就大约知道需要多少的时间,从而可以方便地设置 Timeout——RTO(Retransmission TimeOut),以让我们的重传机制更高效。

二、TCP 控流

TCP 必需要解决的可靠传输以及包乱序(reordering)的问题,所以,TCP 必需要知道网络实际的数据处理带宽或是数据处理速度,这样才不会引起网络拥塞,导致丢包。

TCP 引入了一些技术和设计来做网络流控,Sliding Window 是其中一个技术。 前面我们说过,TCP 头里有一个字段叫 Window,又叫 Advertised-Window,这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。

接收端在给发送端回 ACK 中会汇报自己的可用窗口大小,而发送方会根据这个窗口来控制发送数据的大小,以保证接收方可以处理。

问题1:Window 变成0了,TCP 处理方式如下:

发送端在窗口变成0后,会发 ZWP 的包给接收方,让接收方来 ack 他的 Window 尺寸,一般这个值会设置成3次,第次大约30-60秒(不同的实现可能会不一样)。如果3次过后还是0的话,有的 TCP 实现就会发 RST 把链接断了。

注意:只要有等待的地方都可能出现 DDoS 攻击,Zero Window 也不例外,一些攻击者会在和 HTTP建好链发完 GET 请求后,就把 Window 设置为0,然后服务端就只能等待进行 ZWP,于是攻击者会并发大量的这样的请求,把服务器端的资源耗尽

问题2:当对方 Window 越来越小,接收方处理方式如下:

如果接收方处理速度越来越慢,就导致接收方可用窗口越来越小。到最后,如果接收方腾出几个字节并告诉发送方现在有几个字节的 window,发送方会义无反顾地发送这几个字节,本来一次通信可以传输很大块字节,现在只能几个字节,这就造成资源浪费。

要解决这个问题也不难,就是避免对小的 window size 做出响应,直到有足够大的 window size 再响应,这个思路可以同时实现在 sender 和 receiver 两端:

1)如果这个问题是由 Receiver 端引起的,那么就会使用 David D Clark’s 方案。在 receiver 端,如果收到的数据导致 window size 小于某个值,可以直接 ack(0) 回 sender,这样就把 window 给关闭了,也阻止了 sender 再发数据过来,等到 receiver 端处理了一些数据后 windows size 大于等于了 MSS,或者,receiver buffer 有一半为空,就可以把 window 打开让 send 发送数据过来。

2)如果这个问题是由 Sender 端引起的,那么就会使用著名的 Nagle’s algorithm。这个算法的思路也是延时处理,他有两个主要的条件:

要等到 Window Size>=MSS 或是 Data Size >=MSS,收到之前发送数据的 ack 回包,他才会发数据,否则就是在攒数据。

TCP 拥塞处理

TCP 通过 Sliding Window 来做流控(Flow Control),但是 TCP 觉得这还不够,因为 Sliding Window 需要依赖于连接的发送端和接收端,其并不知道网络中间发生了什么。

TCP 的设计者觉得,TCP 协议仅仅做到流控并不够,因为流控只是网络模型4层以上的事,TCP 还应该更聪明地知道整个网络上的事。

具体一点,我们知道 TCP 通过一个 timer 采样了 RTT 并计算 RTO,但是,如果网络上的延时突然增加,TCP 对这个事做出的应对只有重传数据;

但是,重传会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大。

试想一下,如果一个网络内有成千上万的 TCP 连接都这么行事,那么马上就会形成“网络风暴”,TCP 协议就会拖垮整个网络。

所以,TCP 不能忽略网络上发生的事情,而一个劲地重发数据,对网络造成更大的伤害。

对此TCP 的设计理念是:TCP 不是一个自私的协议,当拥塞发生的时候,要做自我牺牲。就像交通阻塞一样,每个车都应该把路让出来,而不要再去抢路了

最后,拥塞控制主要是四个算法:

慢启动;拥塞避免;拥塞发生;快速恢复。