[转]TCP 快速重传为什么是三次冗余 ACK,这个三次是怎么定下来的?

原文: https://www.zhihu.com/question/21789252

-----------------------------------

两次duplicated ACK肯定是乱序造成的!
丢包肯定会造成三次duplicated ACK!

假定通信双方如下,A发送4个TCP Segment 给B,编号如下,N-1成功到达,因为A收到B的ACK(N),其它按照到达顺序,分别收到ACK(N)的数目:

A ---------> B


A方发送顺序N-1,N,N+1,N+2

B方到达顺序

N-1,N,N+1,N+2 
A收到1个ACK (N)

N-1,N,N+2,N+1 
A收到1个ACK (N)

N-1,N+1,N,N+2 
A收到2个ACK (N)

N-1,N+1,N+2,N 
A收到3个ACK (N)

N-1,N+2,N,N+1 
A收到2个ACK (N)

N-1,N+2,N+1,N 
A收到3个ACK (N)


如果N丢了,没有到达B

N-1,N+1,N+2 
A收到3个ACK (N)

N-1,N+2,N+1 

A收到3个ACK (N)


TCP segment 乱序 有2/5 = 40% 的概率会造成A收到三次 duplicated ACK(N);

而如果N丢了,则会100%概率A会收到三次duplicated ACK(N);

基于以上的统计,当A接收到三次 duplicated ACK(N)启动 Fast Retransmit 算法是合理的,即立马retransmit N,可以起到Fast Recovery的功效,快速修复一个丢包对TCP管道的恶劣影响。

而如果A接收到二次 duplicated ACK(N),则一定说明是乱序造成的,即然是乱序,说明 数据都到达了B,B的TCP负责重新排序而已,没有必要A再来启动Fast Retransmit算法。


补充阅读
--------------------------------------------
TCP segment 乱序的由来

TCP segment 封装在IP包里,如果IP包乱序,则相应TCP也会乱序,乱序的原因一般如下:

1)ECMP 负载均衡

多路径的负载均衡,基于per-packet load balance,比如 packet 1,3,5…走路径1,packet 2,4,6…走路径2,很难保证packet 1 在 packet 2 之前到达目的地。

Per-session load balance 会基于TCP五元组来负载均衡,同一个TCP会话会走同一条路径,克服多路径造成的乱序。

2)路由器内部流量调度

有些路由器采用多个流量处理单元,比如packet 1,3,5…由处理单元1来处理,packet 2,4,6…由处理单元2来处理,也很难保证packet 1 在 packet 2 之前到达目的地。

TCP接收到乱序的segment,会放在自己的接收缓冲区,等所有乱序的segment 都顺利到达,TCP重新排序,并将数据提交给 application。

乱序的segment 会占用接收缓冲区,直接造成B advertised window size 变小,造成对方A发送window 一直在变小,影响A发送效率。

即使A不快速重传,最后也会由retransmit timer timeout 超时重传,但这个时候A的发送window 非常小,发送速率也从天上掉到了地下。

----------///--------
@鹿阳
分析的很好,在没有fast retransmit / recovery 算法之前,重传依靠发送方的retransmit timeout,就是在timeout内如果没有接收到对方的ACK,默认包丢了,发送方就重传,包的丢失原因 1)包checksum 出错 2)网络拥塞 3)网络断,包括路由重收敛,但是发送方无法判断是哪一种情况,于是采用最笨的办法,就是将自己的发送速率减半,即CWND 减为1/2,这样的方法对2是有效的,可以缓解网络拥塞,3则无所谓,反正网络断了,无论发快发慢都会被丢;但对于1来说,丢包是因为偶尔的出错引起,一丢包就对半减速不合理。于是有了fast retransmit 算法,基于在反向还可以接收到ACK,可以认为网络并没有断,否则也接收不到ACK,如果在timeout 时间内没有接收到> 2 的duplicated ACK,则概率大事件为乱序,乱序无需重传,接收方会进行排序工作;而如果接收到三个或三个以上的duplicated ACK,则大概率是丢包,可以逻辑推理,发送方可以接收ACK,则网络是通的,可能是1、2造成的,先不降速,重传一次,如果接收到正确的ACK,则一切OK,流速依然(包出错被丢)。而如果依然接收到duplicated 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://www.cnblogs.com/oxspirt/p/14416438.html