笔者对网络中的传输层尤为热爱,之前有一篇文章已经将传输层中大部分的知识点介绍了一遍,可能可读性较差。这篇主要就一些较为难理解的,课本中未详细说明的概念进行阐述。建议读者还是先将上一篇阅读一下哦~ https://segmentfault.com/a/1190000022272869。主要理解一下其中的GBN和SR。
想必你已经知道TCP是GBN和SR的结合体,分别遗传了他们的特性。比如,遗传了GBN的累积确认、单一计时器;遗传了SR的双窗口模型、选择重传特性、数据缓存。为了能更好的理解,我先将这几个概念解释一下。
累积确认
假设接收方收到了1-4的数据,在收到第4个数据包时,向发送方发送ACK 4(表明4以前的数据已收到,我想要数据5)。此时数据5丢失,同时,发送方还向接收方传了6、7、8的数据。此时当接收方收到数据6、7、8时,它只会向发送方传ACK 4,以此来告知发送方数据5丢失,请给我传数据5。这样有什么好处呢?
- 这样即使在夺么混乱的网络中,只要发送方收到了某个ACK,如ACK 100,那么可以100%推断100以前的数据肯定都收到了,因为若有一个没收到,那么接收方发送的肯定是ACK xx(xx肯定比100小)。
- TCP的一个精巧设计:当接收方收到某个数据时,如数据6。接收方不急着回复ACK 6,而是会等待500ms,此时若收到了数据7,那么便直接发送ACK 7,减少ACK的发送可以减少网络堵塞的可能。因为发送方看到ACK 7之后,他就明白了数据6和7肯定都被收到了。
单一计时器
只有发送方最左边的数据包有一个计时器。在GBN中,若超时了,那么发送方会把窗口内的所有数据全都再发一遍(效率低、明明接收方都收到了却被拒收)。在GBN中,一来是因为接收方对于乱序的数据包直接丢弃,所以导致发送方在超时后无论如何都得重传。二来是因为发送方无法得知哪些数据被接收了,因为一旦乱序,接收方只会传最早丢失的那个数据包的ACK。
此处与SR的多个计时器区分,SR解决了"选择重传"的设计,用的方法是每一个报文自己设定一个单独的计时器,接收方每次返回会返回对应的报文的ACK。这样一来发送方知道了哪些报文是被收到了,哪些是没被收到的。从而可以精准发送,这也就是SR的选择重传。而TCP也有选择重传,设计的方式与SR不同,但是最后的目的是一样的,也就是只会重传那些没有被收到的数据报,具体如何实现可以往下看。
TCP的选择重传
TCP遗传了GBN的累积确认,那发送方如何才能知道哪些数据是被正确收到了呢?
此文讲解十分详细:TCP Selective Acknowledgments (SACK)
https://packetlife.net/blog/2010/jun/17/tcp-selective-acknowledgments-sack/
观察下面这幅图,其中左侧是接收方,右侧是发送方,与我们平常的方向恰好相反,大家注意一下。Seg2在图中处于丢失状态,那么接收方在收到后续的乱序数据时,显然会发送ACK1,表示1之前的数据已经收到了,我想要数据2。从图中可以看到,除了ACK1,还有一个信息是Sack3,这个的含义就是告诉发送端,虽然Seg2丢失了,但是我已经收到了Seg3,所以你之后超时重传的话,只需要把Seg2重传一下就好了。从而实现了TCP的选择重传。
在TCP的拥塞控制中,“慢启动”和“拥塞避免”两个模式很好理解,也容易记住。但是还有一个“快速恢复(Fast Recovery)”相对模糊不清,接下来我将详细讲解快速恢复的机制。
此处有详细的快速恢复的步骤:
Javis in action: Fast Recovery Algorithm
https://www.isi.edu/nsnam/DIRECTED_RESEARCH/DR_WANIDA/DR/JavisInActionFastRecoveryFrame.html
核心内容如下:
After receiving 3 duplicate ACKs in a row:
- set ssthresh to one-half of the current congestion window.
- retransmit the missing segment.
- set cwnd = ssthresh + 3.
- Each time another duplicate ACK arrives, set cwnd = cwnd + 1. Then, send a new data segment if allowed by the value of cwnd.
- Once receive a new ACK (an ACK which acknowledges all intermediate segments sent between the lost packet and the receipt of the first duplicate ACK), exit fast recovery. This causes setting cwnd to ssthresh (the ssthresh in step 1). Then, continue with linear increasing due to congestion avoidance algorithm.
由上面的内容可知,当接收方收到乱序的数据时,会将这个数据缓存下来,但是仍然会发送丢失的包裹的ACK。显然,若乱序的包裹接踵而至,那么发送方便会收到多个丢失包裹的ACK。当收到三个(此处的三个可以参见后面的图比较直观)重复的ACK时,触发快速恢复算法。
- 首先令ssthresh = cwnd / 2。
- 重新传输丢失的包裹。(这个动作叫做快速重传,因为收到三个冗余ACK即开始,而不是等到超时了才开始)
- 将cwnd = ssthresh + 3.(这就是算法本身设置的,无需多疑)
- 之后发送方若仍有重复的ACK收到,收到一个ACK,cwnd = cwnd + 1。(由于窗口变大了,那么又可以有新的数据可以发送)
- 直至收到新的ACK(啥叫新的ACK,详见下图),退出快速恢复模式。
- 将cwnd = ssthresh,进入拥塞避免模式。
在做题和例子中,常常会忽略第4和5步,直接从第三步结束之后进入拥塞避免模式。但是无论怎么说,做题归做题,深入归深入吧。
这幅图中主要看以下两点:
- 重复三次的ACK5其实是第四个ACK5.
- 上文中所说的新的ACK其实就是此处ACK11(在我看来,只要不是ACK5,都是新的ACK),收到这个新的ACK后,即退出快速恢复模式。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。