我一直在阅读《计算机网络:自上而下的方法》一书,遇到了一个我似乎不理解的问题。
如我所读,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的发送速率。现在,请记住,对于每个ACK
,cwnd
都会增加MSS
。因此,一旦第一个ACK
到达,cwnd
就会变成2*MSS
,因此现在我们可以发送2个数据包。确认这两个数据包后,我们将cwnd
递增两次,所以现在cwnd
是4*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,我们将有ACK
。 cwnd += 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
的单位是cwnd
,MSS
是以字节为单位的,因此我们必须小心。因此,新值是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,cwnd
和ssthresh = 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
的新值将是ssthresh
和29*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段
...
我希望这一点很清楚。