我一直在阅读《计算机网络:自上而下的方法》一书,遇到了一个我似乎不理解的问题。
如我所读,TCP拥塞控制具有三种状态:缓慢启动,避免拥塞和快速恢复。我对慢速启动和避免拥塞非常了解,但是快速恢复非常模糊。这本书声称TCP的行为是这样的:(cwnd = Congestion Window)

让我们看下图:


如我们所见,在第16轮中,发送方发送了42个段,并且由于拥塞窗口大小已减半(+3),我们可以推断出已有3个Duplicate-ACK。这个问题的答案声称16到22之间的回合处于“拥塞避免”状态。但是为什么不快速恢复呢?我的意思是,在三个重复的ACK之后,TCP会进入快速恢复状态,而每隔一个重复的ACK会进入一次,因为这会增加拥塞窗口。为什么图形没有该表示?我能想到的唯一合理的解释是,在此图中只有三个重复的ACK,并且此后收到的ACK不是重复的,即使是这种情况,如果有超过3个重复的ACK? **

上图中是否有快速恢复的表示?为什么不/是?

**很久以来我一直在努力回答这个问题。我会很高兴收到您的答复,谢谢!
更新是图像。我认为一轮定义为确认窗口中的所有段。在照片中,圆形显示为圆形。

为什么处于快速恢复状态时cwnd呈指数增长? (在我不经意间而不是按指数写的图像中)

最佳答案

更新:我的原始答案同意该解决方案,但经过仔细考虑,我认为该解决方案是错误的。这个答案是从头开始重写的;请仔细阅读。我展示了为什么在T = 16时输入了快速恢复,以及为什么协议一直保持到T = 22。图中的数据支持我的理论,因此,我非常肯定该解决方案是错误的。

让我们从简单的事情开始:慢速启动呈指数增长;避免拥塞呈线性增长,而快速恢复呈线性增长,即使它使用与慢启动相同的公式来更新cwnd的值。

请允许我澄清。

为什么说慢速启动cwnd呈指数增长?

请注意,对于每个收到的ACK,cwnd增加了MSS个字节。

让我们来看一个例子。假设cwnd初始化为1个MSS(MSS的值通常为1460字节,因此实际上这意味着cwnd初始化为1460)。此时,由于拥塞窗口大小只能容纳1个数据包,因此TCP在确认该数据包之前不会发送新数据。假设没有丢失ACK,这意味着每隔RTT秒传送大约一个新数据包(回想RTT是往返时间),因为我们需要(1/2)* RTT来发送数据包,并且( 1/2)* RTT,以使ACK到达。

因此,这导致大约MSS / RTT bps的发送速率。现在,请记住,对于每个ACKcwnd都会增加MSS。因此,一旦第一个ACK到达,cwnd就会变成2*MSS,因此现在我们可以发送2个数据包。确认这两个数据包后,我们将cwnd递增两次,所以现在cwnd4*MSS。大!我们可以发送4个数据包。这4个数据包被确认,因此我们可以递增cwnd 4倍!所以我们有cwnd = 8*MSS。然后我们得到cwnd = 16*MSS。实际上,我们每RTT秒将cwnd加倍(这也解释了为什么在拥塞避免中cwnd = cwnd+MSS*(MSS/cwnd)会导致线性增长)

是的,这很棘手,公式cwnd = cwnd+MSS容易使我们相信它是线性的-这是一种常见的误解,因为人们经常忘记将此应用于每个已确认的数据包。

请注意,在现实世界中,传输4个数据包不一定会生成4个ACK。它可能仅生成1个ACK,但是由于TCP使用累积的ACK,因此单个ACK仍在确认4个数据包。

为什么快速恢复是线性的?

cwnd = cwnd+MSS公式适用于缓慢启动和避免拥塞。有人会认为这会导致两种状态都导致指数增长。但是,快速恢复在另一个上下文中应用该公式:当收到重复的ACK时。区别在于:在慢速启动中,一个RTT确认了很多段,而每个确认的段都通过+ 1MSS贡献了新的cwnd值,而在快速恢复中,重复的ACK浪费了RTT来确认丢失单个段,因此我们不是为每个丢失的段更新cwnd一次,而是每隔RTT秒更新N次(cc是N,即传输的段数)。因此,我们仅用一个段就“浪费”了一次往返行程,因此我们只将cwnd加1。

关于避免拥塞-我将在下面分析图表时对此进行说明。

分析图

好的,让我们逐一查看一下该图中发生的情况。您的图片在某种程度上是正确的。让我先清除一些事情:


当我们说“慢启动”和“快速恢复”呈指数增长时,意味着它如您在图片中所示呈指数增长。因此,这是正确的。您正确地用蓝色圆圈标识了这些回合:请注意cwnd的值如何从一个圆圈到下一个圆圈呈指数增长-1,2,4,4,8,16,...
您的图片似乎表明,在“慢速启动”之后,协议进入“快速恢复”。这不会发生。如果从慢速启动转到快速恢复,我们将看到cwnd减半。这不是图形显示的内容:cwnd的值不会从T = 6减少到T = 7的一半。


好的,现在让我们看看每轮比赛到底发生了什么。请注意,图中的时间单位是一个整数。因此,如果在时间T = X时我们传输了N个段,则假定在时间T = X + 1时已经确认了这N个段(当然,假设它们没有丢失)。

还要注意,仅通过查看图表就可以知道cwnd的值。在T = 6时,ssthresh停止指数增长,并开始线性增长,并且其值不减小。从缓慢启动到不涉及减小cwnd的另一种状态的唯一可能转换是避免拥塞,这是在拥塞窗口大小等于cwnd时发生的。在图中可以看到,当ssthresh为32时会发生这种情况。因此,我们立即知道cwnd初始化为32 MSS。这本书在第276页上显示了一个非常相似的图形(图3.53),作者在其中得出了相似的结论:



在正常情况下,会发生这种情况-当TCP在没有减小窗口大小的情况下首次从指数增长切换为线性增长时,总是因为它达到阈值并切换为拥塞避免。

最后,假设ssthresh至少为1460个字节(由于以太网的MTU = 1500字节,因此通常为1460个字节,我们需要考虑TCP + IP标头的大小,这总共需要40个字节)。当MSS超过cwnd时,这很重要,因为ssthresh的单位为cwnd,并且MSS以字节表示。

所以我们开始:

T = 1:

cwnd = 1 MSS; ssthresh = 32 kB

传输1段

T = 2

已确认1个细分受众群

cwnd + = 1; ssthresh = 32 kB

cwnd的新值:2

传输2段

T = 3

确认了2个细分受众群

cwnd + = 2; ssthresh = 32 kB

cwnd的新值:4

传输4段

T = 4

确认了4个细分受众群

cwnd + = 4; ssthresh = 32 kB

cwnd的新值:8

传输8段

T = 5

确认了8个细分

cwnd + = 8; ssthresh = 32 kB

cwnd的新值:16

传输16段

T = 6

确认了16个细分受众群

cwnd + = 16; ssthresh = 32 kB

cwnd的新值:32

传输32段

好的,让我们看看现在会发生什么。 ssthresh已达到cwnd(32 * 1460 = 46720字节,大于32000)。现在该切换到避免拥塞了。注意ssthresh的值如何在各回合中呈指数增长,因为每个已确认的数据包对新的cwnd值贡献1 MSS,并且在下一轮中确认每个发送的数据包。

切换到避免拥塞

现在,cwnd不会成倍增加,因为每个cwnd将不再贡献1个MSS。相反,每个ACK都用ACK贡献。因此,例如,如果MSS*(MSS/cwnd)是1460个字节,而MSS是14600个字节(因此在每个回合开始时,我们将发送10个分段),则每个cwnd(假设每个分段一个ACK)将增加ACK by cwnd MSS(146字节)。由于我们发送了10个细分,并且在该轮结束时,我们假设每个细分均得到确认,因此在该轮结束时,我们将1/10增加了cwnd。换句话说,每个段对10 * 1/10 = 1的贡献很小,因此我们每轮只将cwnd增加1 MSS。因此,现在每回合将cwnd递增1,而不是传送/确认的段数。

我们将一直避免拥塞,直到检测到某些丢失(3个重复的ACK或超时)。

现在,让时钟恢复原状...

T = 7

确认了32个细分

cwnd + = 1; ssthresh = 32 kB

cwnd的新值:33

传输33段

请注意,即使已确认32个段,cwnd如何从32变为33(每个cwnd因此贡献了1/32)。如果我们的启动速度很慢,例如T = 6,我们将有ACKcwnd += 32的新值也与我们在时间T = 7时在图中看到的一致。

T = 8

确认了33个细分市场

cwnd + = 1; ssthresh = 32 kB

cwnd的新值:34

传输34段

T = 9

确认了34个细分市场

cwnd + = 1; ssthresh = 32 kB

Cwnd的新值:35

传输35段

请注意,这与图形一致:在T = 9处,我们有cwnd。这种情况一直持续到T = 16 ...

T = 10

确认了35个细分

cwnd + = 1; ssthresh = 32 kB

cwnd的新值:36

传输36段

T = 11

确认了36个细分受众群

cwnd + = 1; ssthresh = 32 kB

cwnd的新值:37

传输37段

T = 12

确认了37个细分市场

cwnd + = 1; ssthresh = 32 kB

cwnd的新值:38

传输38段

T = 13

确认了38个细分市场

cwnd + = 1; ssthresh = 32 kB

cwnd的新值:39

传输39段

T = 14

确认了39个细分受众群

cwnd + = 1; ssthresh = 32 kB

cwnd的新值:40

传输40段

T = 15

确认了40个细分

cwnd + = 1; ssthresh = 32 kB

cwnd的新值:41

传输41段

T = 16

承认41个细分市场

cwnd + = 1; ssthresh = 32 kB

cwnd的新值:42

传输42段

暂停

现在会发生什么?该图显示,拥塞窗口的大小减小到其大小的一半左右,然后又在各轮之间线性增长。唯一的可能性是存在3个重复的ACK,并且协议切换到快速恢复。该图显示它不会切换到慢启动,因为这会使cwnd = 35降至1。因此,唯一可能的过渡是快速恢复。

通过进入快速恢复,我们得到cwnd。请记住,ssthresh = cwnd/2的单位是cwndMSS是以字节为单位的,因此我们必须小心。因此,新值是ssthresh

再次,这与图对齐;请注意,当ssthresh = cwnd*MSS/2 = 42*1460/2 = 30660略小于30时,将在不久的将来达到ssthresh(请注意,在MSS = 1460时,该比率不完全是1:1,这就是即使拥塞窗口大小也达到阈值的原因略低于30)。

切换到避免拥塞还会导致cwnd的新值成为cwnd(请记住要注意单位,在这里我再次将ssthresh+3MSS = 21+3 = 24转换为MSS,因为我们的ssthresh值已计入MSS)。

到目前为止,我们处于避免拥塞的状态,T = 17,cwndssthresh = 30660 bytes

输入T = 18时,可能会发生两件事:要么我们收到重复的ACK,要么我们没有。如果不这样做(那么这是一个新的ACK),我们将过渡到避免拥塞。但这会将cwnd = 24降低到cwnd的值,即21。这与图表不符-该图表显示ssthresh保持线性增长。而且,它不会切换到慢启动,因为这会使cwnd降低到1。这意味着无法恢复快速恢复,并且我们得到了重复的ACK。这一直发生到时间T = 22:

T = 18

重复的ACK到达

cwnd + = 1; ssthresh = 30660字节

cwnd的新值:25

T = 19

重复的ACK到达

cwnd + = 1; ssthresh = 30660字节

cwnd的新值:26

T = 20

重复的ACK到达

cwnd + = 1; ssthresh = 30660字节

cwnd的新值:27

T = 21

重复的ACK到达

cwnd + = 1; ssthresh = 30660字节

cwnd的新值:28

T = 22

重复的ACK到达

cwnd + = 1; ssthresh = 30660字节

cwnd的新值:29

**暂停**

我们仍处于快速恢复中,现在cwnd突然降至1。这表明它再次进入慢速启动。 cwnd的新值将是ssthresh29*1460/2 = 21170。这也意味着,尽管我们努力重发该细分受众群,但仍存在超时。

T = 23

cwnd = 1; ssthresh = 21170字节

传输1段

T = 24

已确认1个细分受众群

cwnd + = 1; ssthresh = 21170字节

cwnd的新值:2

传输2段

T = 25

确认了2个细分受众群

cwnd + = 2; ssthresh = 21170字节

cwnd的新值:4

传输4段

T = 26

确认了4个细分受众群

cwnd + = 4; ssthresh = 21170字节

cwnd的新值:8

传输8段

...

我希望这一点很清楚。

09-26 09:45