为什么收到三个重复的ACK意味着发生拥塞?

三次重复的ACK,可能是丢包引起的,丢包可能是网络拥塞造成的,也可能是信号失真造成的。

三次重复的ACK,也有可能是乱序引起的,而乱序和网络拥塞没有直接关系。

如果就写这两行,感觉什么都没写,接下来的文字详细解释这两行文字。

TCP背景知识
客户端有1M的文件需要上传到服务器上,问题来了,这个大文件能否用一个TCP报文传输?

肯定不能啊,因为网络路径有最大传输单元(MTU = 1500)的限制,所以IP报文不能大于1500字节,那怎么办啊?

那就让TCP将1M砍成N个1460字节的segment,这里N=685,问题又来了,这685个TCP报文如果发送方不编号,到达接收方之后,接收方依靠什么方法知道谁是第1个segment,谁是第2个segment。。。谁又是第685个segment?

为何要编号呢?不编号难道不可以吗?
比如发送方只要将685个segment按序发送, 1、2、3、4、5。。。681、682、683、684、685,这些segment排着队伍,到达终点顺序也应该是1、2、3。。。683、684、685,然后再将685个segment按序拼接在一起,就会复原成1M 字节的文件。

理想很丰满,现实很骨感,现实会把这个理想击打成粉粹。现在作者提出三个问题,读者分析一下如何解决:
(1) 乱序
假如第4、5 segment在传输过程中发生了乱序,即5比4先到达,接收方拼接的文件将为“12354。。。”,拼接文件就不是发送方的文件了,而是一个完全没有意义的废文件了,拼了老命吭哧吭哧,结果到对方却是废文件,这是无法接受的。

(2) 丢包
假如第4个segment在传输过程中不幸丢了,接收方无法完整地拼接出原始文件。

(3) 重复包
假如第4个segment在传输过程被网络设备多发送了一次,接收方拼接出的文件将是“123445。。”

如果TCP真是这么脆弱不堪,压根不会垄断可靠传输层协议,也不会有今天的地位。

那么TCP是如何解决以上三个问题的?

很简单,就两个字:编号!

编号可以解决以上三个常见问题,详细解释见下文。

乱序
接收方发现5先到,而4没有到,TCP先将5用仓库缓存下来,耐心等待4的到来,4到达之后,再按照顺序将4、5重写排列好,这很好理解吧?

丢包
接收方对于接收到的segment,会发送一个确认ACK,告诉发送方已经成功接收的编号数字,对于迟迟没有确认的segment 4,发送方的闹钟(timer)一响(timeout),会自动重传segment 4,最终接收方也可以收到segment 4。

同学们肯定会说,由于segment 5、6、7没有丢,可能早就到达了接收方,在segment 4重传到达之前,TCP如何处理它们?

也是用仓库临时堆放一下,等4到来之后,就可以按照4567。。。的顺序让应用程序取走数据了,这个和快递公司的工作模式很相似。

重复包
既然segment 编号了,接收方发现segment 4已经收到过,再次收到segment 4已经没有任何意义了,直接丢弃就可以了。

TCP确认机制
发送方发送segment 1,接收方收到之后需要ACK确认,这里需要敲黑板提醒还在睡觉同学的注意,ACK确认号是多少?

ACK = 2 ,什么意思呢? 意思是说,segment 1已经成功接收,寡人等待segment 2的到来。每次写到这里,都会想起中学语文课本里那句经典名言:让暴风雨来得更猛烈些吧! 亚当夏娃仍玉米棒子的故事正是受这句名言的启发。

如果发送方发送的segment是“1、2、3、4、5、6”,那么接收方应该如何ACK确认呢?
同学们会说,那不是很简单吗? 收到6个segment,那就发6次ACK,应该是“2、3、4、5、6、7”,当接收方接到ACK =7,就意味着编号7前面所有segment都成功接收,在这里就是“1、2、3、4、5、6”都成功接收了。

问题是ACK的次数有点多,接收方发送一次ACK耗费CPU,接收方接到一次ACK也耗费CPU,另外ACK报文对网络也是一个小小的负担。

于是RFC建议优化ACK方法,每收到2个segment,发送一次ACK,这样就会将ACK数目减半,Good Idea!

那么接收方可以这样ACK :“3、5、7。。。”,当发送方接收到ACK=7,表明segment 1、2、3、4、5、6已经到了。

问题来了,如果发送方只有一个segment发送,按照以上规则,接收方接收2个segment才发送一个ACK,既然只有一个segment,那接收方的ACK会不会永远也发不出?

会的,机器是一根筋认死理,这里会造成通信的死锁(deadlock)。但人是活的,只要帮助TCP启动一个定时器就好,只要闹钟响了(timeout)而第二个segment还没有到,这时就顾不了那么多,直接发ACK就好。

TCP 慢启动(slow start)就是先发segment 1,直到接到对方ACK了,才会发segment 2、3。有了这个定时器,就可以避免TCP慢启动期间的通信死锁。

信息不对称
假如发送方发送segment “1234567”,接收方接到的是“1235”,接收方接到5的那一瞬间,知道4有可能丢了,也有可能乱序了,至于是哪种情况,接收方无从知晓。
但发送方对于segment 4的状态是完全空白的,既然接收方知道多一点,为什么不把这个信息同步给发送方呢?

通过什么方法把消息同步给发送方呢?
ACK =4

有同学会迷惑不解,明明收到的是5,应该ACK= 6,为何这里是4?

ACK=6 是什么意思?

表示 “12345”都成功接收,问题是4收到了吗?没有啊!所以只能ACK=4。

稍后,接收方所有接收到的segment为 “12356”,接收方如何做?
ACK=4

再稍后,接收方所有接收到的segment为 “123567”,接收方如何做?
ACK =4

发送方一直记录自己重复收到某个ACK的次数,Duplicated ACK(4) =3,此时发送方意识到segment 4有可能丢了,此时应该立马主动将segment 4重传出去,而不要被动等待segment 4的重传定时器超时再重传segment 4。

由于这种依赖外界刺激的重传方法比超时重传更快、更及时,美其名曰:快速重传(Fast Re-transmit)。

当重传的segment 4到达接收方时,终于将不连续的segment流 “123567” 修复(Restore)成连续的segment流 “1234567”,通常称这种主动修复字节流的方法为快速修复(Fast Restore)。两者合在一起统称Fast Re-transmit / Fast Restore。

上文说的外界刺激,就是发送方连续收到三次duplicated ACK,立马启动Fast Re-transmit / Fast Restore机制。

如果上文中的接收方所有接收到的segment 不是“123567”,而是”123564”,此时就是乱序的发生,接收方会重新排序成”123456” ,接收方发送ACK =7即可,segment是连续的,无需修复。

在这种情况下,发送方只接到了两次duplicated ACK =4,无需启动Fast Retransmit / Fast Restore机制,因为接收方已经成功修复。

总结一下

没有Fast Re-transmit / Fast Restore机制,TCP完全可以凭借超时重传来完成可靠传输,由于是被动地等待,所以传输效率低下。

FastRe-transmit / Fast Restore机制,使得TCP能够快速地通过三次重复的ACK,推测报文有丢失的可能,进而主动重传,传输效率更高。

补充阅读

论修复不连续segment的重要性

上文中的segment 4只要不到达接收方,即使segment 5、6、7。。。早已到达,也只能滞留在接收方的仓库(Receive Buffer)里,而不能被应用程序取走。

Receive Buffer和Advertised Window Size是直接相关的,接收方的receive buffer被占用,势必通告给发送方的window size就会变小,影响发送方CWND,进而影响发送方的发送速率。

Delivery Rate = CWND / SRTT

其中

SRTT = Smooth Round Trip Time,平均往返时间延迟

CWND = Congestion Window,发送方拥塞窗口大小,和接收方的Advertised Window Size有直接关联

Delivery Rate,发送方的发送速率

所以在SRTT不变的前提下,只要CWND变小,发送速率就会下降。

不连续的segment会严重影响TCP的传输效率。而快速修复这种不连续,会释放掉占用的仓库空间,会加快发送方的传输效率。

    原文作者:牛小可
    原文地址: https://blog.csdn.net/niukeming/article/details/80779582
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞