5.3.9.2 快速重传和快速恢复

上一小节所讲述的网络拥塞,说的是 TCP 发送方发送了一批数据(1个或多个报文),一直等到超时(RTO),也没有收到对方的 ACK 报文。但是上一小节关于网络拥塞的处理,有一个潜台词的假设没有说出来,那就是:网络长时间拥塞,如图5-146所示:

图5-146 网络长时间拥塞

图5-146中,我们假设 B 对 A 所发送的每一个报文都应该回以1个 ACK 报文,但是很遗憾,由于网络拥塞,每一个 ACK报文,A 都没有收到。此之谓网络长时间拥塞。

上一小节中,TCP发送方“感知”到1个报文的超时,就认为网络是长时间拥塞了,所以它来了一个“垂直降落”。

但是有一种场景,那就是网络偶尔拥塞,对应到图5-146中,就是仅仅其中1个 ACK 报文没有收到,那么 TCP 是不是有更好的处理方案呢?答案是肯定的,那就是快速重传和快速恢复。

1)关于报文乱序和丢包的 ACK

在介绍快速重传和快速恢复之前,我们先介绍一个补充知识:如果 TCP 接收方所收到的报文是乱序的或者丢包了,它该如何回应 ACK。

先看乱序,如图5-147所示:

图5-147 报文乱序的 ACK

图5-147中,A 按照顺序向 B 发送了3个报文:SEG1、SEG2、SEG3,但是 B 所收到的报文的顺序却变成了 SEG1、SEG3、SEG2。那么,B 应该这么应答 A 呢?

对于所收到的第1个报文 SEG1,这没有问题,B 就正常的回以 ACK(SEG1, AKN = 200)。

对于所收到的第2个报文 SEG3,B 能够通过 SEG3 的 SEQ(300) 判断出,这不是它所期望的报文(它所期望的报文的 SEQ = 200),而是它所期望的报文的后续报文,此时它仍然回以 ACK(SEG1, AKN = 200)——告诉 A,它所期望的报文的 SEQ = 200。

但是,B 并不会将 SEG3 丢弃,而是保存起来,因为这种场面对于 TCP 来说是一个比较正常的事情——TCP 会认为,很可能是报文乱序了,那就不着急,耐心等待。

对于所收到的第2个报文 SEG2,B 会感慨:耐心终有回报,它所期望的那个报文终于来到了。而且非常凑巧,本次所收到的报文 SEG2,和上次所收到的报文 SEG3,正好可以连在一起(SEG2.SEQ + SEG2.DataLen = SEG3.SEQ),于是 B 就将 SEG2 和 SEG3 一起给确认了,回以 ACK(SEG3, AKN = 400)——告诉 A,从 SEQ = 200,到 SEQ = 399,这么多数据都收到了(对应报文 SEG2、SEG3),下一个所期望收到的报文的 SEQ = 400。

说明:ACK(SEGi)、ACK(SEGi, AKN = N) 是一种简化的表达,其含义是:这个 ACK 报文是针对 SEGi及其之前所有未确认报文的 ACK 报文,其中 AKN 字段的值等于 N,也就是 SEGi.SEQ + SEGi.DataLen。

乱序对于 TCP 的双方来说,仅仅是网络对它们开了一个小小的玩笑,无伤大雅,但是如果丢包呢?如图5-148所示:

图5-148 报文丢包的 ACK

图5-148中,A 发送的报文 SEG2 由于某种原因没有到达 B,可以看到,B 是一直倔强地回以 ACK(SEG1)。这比较好理解,假设 B 收到报文 SEG3 时,回以 ACK(SEG3),那么 A 就会认为 B 也收到了 SEG2,并且是将 SEG2、SEG3 打包 ACK(基于 Delay ACK 机制)。

更一般地,TCP 并不知道(或者并不想知道)报文是乱序了还是丢包了,反正只要它所接收的报文的 SEQ 大于它所期望的报文的 SEQ(SEG.SEQ > ACK.AKN),它就一直倔强地回以 ACK,其中 ACK.AKN 等于它所期望的 SEQ。

由于后面的内容中涉及“重复 ACK”这个概念,我们这里就将“重复 ACK”做一个定义。从图5-148可以看到,B 回应的第1个 ACK 报文是一个正常的回应,而后续的3个 ACK 报文,都是重复 ACK。这是我们直观的感觉,RFC 5681 关于“重复 ACK”的定义是:

(1)收到 ACK 报文的一方(也就是图5-148中的 A),得有已发送未确认的数据

(2)所收到的 ACK 报文中,没有包含数据

(3)所收到的 ACK 报文中,SYN 和 FIN 标记都是关闭的(其值为0)

(4)所收到的 ACK 报文中的 AKN,等于自己发送空间的 UNA

(5)所收到的 ACK 报文,其所通告的 Window 大小与上次 ACK 报文的 Window 的大小相同。

这个定义同时也暗示了,TCP 面对乱序或者丢包时,其所回应的 ACK 报文是不会变化 Window 字段的。

我们以图5-148为例,将这个定义解释如下:

(1)A 发送了报文 SEG1,B 回以 ACK(SEG1),其中 ACK.AKN = 200, ACK.Window = 1000

(2)A 发送了报文 SEG2,假设 SEG2.SEQ = 200,此时 A 的已发送未确认报文的 SEQ等于 200,即 A.UNA = 200

(3)但是由于 SEG2 丢失了,B 并没有收到,所以 B 也不会回以报文 ACK(SEQ2)

(4)A 发送了报文 SEG3,根据前文描述,B 收到 SEG3 以后,会回以 ACK(SEQ1),即 ACK.AKN = 200 = A.UNA, ACK.Window = 1000。我们把这个 ACK 报文记为 ACK1

(5)A 发送了报文 SEG4 ......B 回复的报文,从内容上来说,仍然是 ACK1——这是第1次重复 ACK

(6)A 发送了报文 SEG5 ......B 回复的报文,从内容上来说,仍然是 ACK1——这是第2次重复 ACK

(7)A 发送了报文 SEG6 ......B 回复的报文,从内容上来说,仍然是 ACK1——这是第3次重复 ACK

......

RFC 5681 为什么要对“重复 ACK”做如此比较严格的定义呢?这与快速重传机制有关。

2)快速重传和快速恢复算法

严格来说,单纯的快速重传与拥塞控制没有多大关系,它仅仅是为了提升发送效率。为了方便阅读,我们将图5-148重新贴一下,取名“3次重复 ACK”,如图5-149所示:

图5-149 3次重复 ACK

对于 A 来说,它每收到1次重复 ACK,都会有疑问:为什么会收到重复 ACK?是 SEG2 丢包了?是 SEG2 乱序了(到达 B 的时间比较晚)?还是网络将 ACK 报文错误地重复发送了?

古人云“事不过三”,当 A 收到了3次重复的 ACK,它该怎么做?

一种做法是心中继续那些疑问,但是也仅仅是疑惑而已,还是继续发送它的报文 SEG6、SEG7 ......一直到超时时间(RTO)到,A 发送自己还没有收到 SEG2 的 ACK 报文,这时候才恍然大悟:哦,原来 SEG2 超时了,需要重传。这就是传统的超时重传策略。

还有一种做法是:既然已经收到了3次重复 ACK,那很可能意味着 SEG2 丢包了,那么与其坐等超时,不如就直接认为 SEG2 超时了,干脆就重传 SEG2 吧。这种收到3次重复 ACK 就认为是丢包,并且重传该报文的策略,称为快速重传。

从这个意义上讲,快速重传确实与拥塞控制没有多大关系。但是:

(1)既然认为是丢包了——就算实际上没有丢包,SEG2 还在网络中,那时延也已经很大了——那说明网络发生了拥塞。既然网络发送了拥塞,那么是不是应该象上一小节介绍的那样:垂直降落,然后进入慢速启动/拥塞避免状态呢?

(2)但是,网络拥塞的状况似乎也没有多严重。毕竟随后发送的报文(SEG3~SEG5),对方应该是都收到了,并且也回应了 ACK(虽然是3次重复的 ACK(SEG1))。那么是不是可以认为网络已经不拥塞了(就算曾经拥塞过一小段时间,现在也已经变好了)?是不是就不必“垂直降落/慢速启动”了?是不是就保持当前状态?

这两个想法,前者偏悲观,后者偏乐观,选择哪个,似乎都有点纠结。于是,RFC 5681 进行了一定的折中,提出了“快速重传-快速恢复”算法。

超时重传也好,快速重传也好,它们的本质是一样的:

(1)都是对“丢包”的一种探测。TCP 的世界里没有上帝——没有上帝告诉 TCP,这个报文是真的被网络丢弃了,还是仍然在网络中传输(只是传输的有点慢)——TCP 只能根据自己的“感觉”来“赌”一把,或者通过“超时”,或者通过“3次重复 ACK”。两种“赌”法的概率都比较高,相对而言,通过“3次重复 ACK”的效率更高。

(2)都是对“丢包”的一种“修复”。既然认为丢包了,那就得修复它——这一点两者是一样的,都是重传该报文。

超时重传后,“垂直降落-慢速启动=拥塞避免”算法接管了后续的发送策略,而快速重传后,接管发送策略的快速恢复算法。

“快速重传-快速恢复”的算法如下:

(1)当 TCP 发送方收到第1个和第2个重复 ACK 时,它仍然是按照原来的策略发送新报文,比如5-149中继续发送 SEG4、SEG5。此时,发送方不能调整它的 cwnd。

(2)当 TCP 收到第3个重复 ACK 时,它需要调整 ssthresh:

ssthresh <= max (FlightSize / 2, 2*SMSS)

(3)那个“丢包”被重传(SEG.SEQ = TCP.UNA)——也就是所谓的“快速重传”

(4)将 cwnd 设置为:

cwnd = ssthresh + 3 * SMSS

(5)后续如果还收到重复 ACK(第5次、第6次 ......),每收到1次,cwnd = cwnd + SMSS。而且此时可以继续发送“新”数据(发送空间里的“已允许未发送”数据)。

(6)当收到对方新的 ACK 时(即对方收到了重传的报文,并给予正确的回应),需要重新将 cwnd 设置为:

cwnd = ssthresh

(7)在随后的过程中,TCP 进入拥塞避免状态,也就是采用拥塞避免算法:

cwnd = ssthresh

纵观“快速重传-快速恢复”算法和“慢速启动-拥塞避免”算法,两者的不同点是:

(1)对于丢包的检测方法不同(丢包也就意味着拥塞)。前者使用“3次重复 ACK”当作判断标准,而后者使用“超时”当作判断标准

(2)拥塞(丢包)发生后,前者并没有将 cwnd“垂直降落”,而是将 cwnd 设置为:cwnd = ssthresh + 3 * SMSS,后者则将 cwnd 垂直降落:cwnd <= SMSS

(3)拥塞(丢包)发生后,前者进入“拥塞避免”状态,而后者进入“快速启动”状态。

“快速重传-快速恢复”算法和“慢速启动-拥塞避免”算法,两者的相同点是:

(1)拥塞(丢包)发生后,两者都将 sstrhresh 进行了调整,也都可以简单认为调整公式也相同:ssthresh = max (FlightSize / 2, 2*SMSS)

我们把以上的比较,继续总结,如表5-30所示:

表5-30  “快速重传-快速恢复”和“慢速启动-拥塞避免”的比较

比较项

比较

结果

快速重传-快速恢复

慢速启动-拥塞避免

拥塞检测

不同

3次重复 ACK

超时

拥塞后,ssthresh 的调整

相同

ssthresh = max (FlightSize / 2, 2*SMSS)

拥塞后,cwnd 的调整

不同

cwnd = ssthresh + 3 * SMSS

cwnd = SMSS

拥塞后的状态

不同

拥塞避免

慢速启动

两者之所以有如此不同,归根结底在于两者对于拥塞程度的认识。“慢速启动-拥塞避免”算法比较悲观,因为到了超时。“快速重传-快速恢复”算法相对来说,比较乐观,因为它在感知到丢包的同时,也发现后续的报文好像没有什么问题。

最后再解释一下“快速重传-快速恢复”算法中关于 cwnd 的调整,它一会等于 cwnd = ssthresh + 3 * SMSS,一会又等于 cwnd = ssthresh。这是为什么?

笔者以为,这体现了 TCP 面对网络拥塞时的很多正能量思想。

首先,我们抛开那个变来变去的 cwnd,先说说拥塞控制本身。笔者第一眼看到网络拥塞是崩溃的。试想一下,一个 TCP 连接发送了网络拥塞的心里活动:这网络怎么拥塞了?这是谁造成的?我没有发送多少数据啊?这也不是我造成的啊,我又能怎么办?就算我现在一点数据不发送,网络还是会拥塞啊!我该怎么办?

这样的心里活动确实令人崩溃!但是,TCP 的想法却是很正能量:只要人人都献出一份爱,世界将变成美好的明天。于是我们看到,面对网络拥塞时,TCP 并没有抱怨、并没有迷惘,更没有推卸责任,而是马上进入拥塞控制算法。而且不仅如此,在连接刚开始创建时,TCP 就主动进入了拥塞控制的角色(慢速启动)。

帮助别人,也就是帮助自己。TCP 这种主动而无私的精神,同样也使自己受益,它可以是自己避免陷入无限的重传陷进中。

同时,我们也看到,TCP 在互帮互助、互相谦让的同时,也是齐心协力在充分利用网络的带宽。毕竟,大家的目标还是要传输数据,总不能网络一拥塞时,大家都在家里葛优躺。

齐心协力,充分利用网络带宽,比如“快速重传”等我们就不多说了,这里谈谈它的思想。

TCP 认为网络就是一个大管道,而所谓网络拥塞,就是报文在管道上淤积。TCP 的思想,有一点类似“配额制”:谁在网络上淤积,谁就减少配额;谁的淤积从网络中消失了,谁就可以增加配额。

所以我们看到前文所描述的算法:

(1)收到3次重复 ACK 以后,不仅快速重传、调整 ssthresh,而且将cwnd 调整为 cwnd = ssthresh + 3 * SMSS。

这里的“3 * SMSS”,是TCP 认为自己所发送的报文“SEG3~SEG5”并没有在网络上淤积,因为它收到了3次重复的 ACK。所以,TCP 就增加了自己的配额——增加“3 * SMSS”。

(2)当快速重传并且收到对应的 ACK 以后,TCP 又将 cwnd 调整回来 cwnd = ssthresh,这......我编不下去了,这应该是 TCP 觉得应该进入正常的轨道:进入拥塞避免状态,此时 cwnd 应该等于 ssthresh,然后再利用拥塞避免算法慢慢增加 cwnd。

不要认为 cwnd 这种变来变去是莫名奇妙,事实上正如它的名字所暗示的那样,TCP 不是一个传输协议,而是一个控制协议。无数个 TCP 连接竞争同一个网络,既要公平,又要效率,是 TCP 永远的追求!

传输层协议(13):拥塞控制(2)相关推荐

  1. 传输层协议TCP—拥塞控制(12)

    1 拥塞控制简介 拥塞控制讲述的则是从如何避免网络拥塞的视角或者网络已经拥塞的情形下,TCP 对应的算法和处理机制.TCP 拥塞控制(对应 RFC 5681)包括4个算法(机制):慢速启动.拥塞避免. ...

  2. Linux_网络_传输层协议 TCP通信滑动窗口(快重传),流量控制,拥塞控制(慢启动),延迟应答,捎带应答,TCP常见问题(字节流,粘包),Listen半连接队列

    紧跟Linux_网络_传输层协议 TCP/UDP继续补充 文章目录 1. TCP通信时滑动窗口(效率) 2. 流量控制(可靠性) 3. 拥塞控制(慢启动) 4. 延迟应答 5. 捎带应答(提高通信效率 ...

  3. 前端工程师如何理解 TCP/IP 传输层协议?| 技术头条

    作者 | 浪里行舟 责编 | 郭芮 网络协议是每个前端工程师都必须要掌握的知识,TCP/IP 中有两个具有代表性的传输层协议,分别是 TCP 和 UDP,本文将介绍下这两者以及它们之间的区别. TCP ...

  4. 弱网络环境下最优调度和优化传输层协议方案

    一.背景 与有线网络通信相比,无线网络通信受环境影响比较大(例如高层建筑.用户移动.环境噪音.相对封闭环境等等),网络的服务质量相对来说不是非常稳定,导致用户经常会在弱信号的网络环境下通信.而当用户在 ...

  5. 搬砖:新一代基于UDP的低延时网络传输层协议——QUIC详解

    技术扫盲:新一代基于UDP的低延时网络传输层协议--QUIC详解 本文来自腾讯资深研发工程师罗成的技术分享,主要介绍 QUIC 协议产生的背景和核心特性等. 1.写在前面 如果你的 App,在不需要任 ...

  6. 计算机网络协议的特点,计算机网络传输层协议类型与特点

    我们在上文中给大家简单介绍了计算机网络体系的七层结构,而今天我们就一起来了解一下,计算机网络传输层协议类型与特点. 传输层涉及到两个重要的协议:UDP和TCP,本节我们重点介绍这两个协议. 1.UDP ...

  7. 4-1:TCP协议之传输层的作用及传输层协议TCP和UDP

    文章目录 一:传输层的定义 二:通信处理 三:传输层协议 四:TCP协议的可靠和性能 一:传输层的定义 前面说过,IP首部有一个协议字段用于标识网络层(IP)的上一层采用哪一种传输层协议.根据这个字段 ...

  8. QUIC - 低时延互联网传输层协议

    QUIC - 低时延互联网传输层协议 QUIC(Quick UDP Internet Connection)是谷歌制定的一种基于UDP的低时延的互联网传输层协议.在2016年11月国际互联网工程任务组 ...

  9. TCP/IP中的传输层协议TCP、UDP

    TCP提供可靠的通信传输,而UDP则常用于让广播和细节控制交给应用的通信传输. 传输层协议根据IP数据报判断最终的接收端应用程序. TCP/IP的众多应用协议大多以客户端/服务端的形式运行.客户端是请 ...

  10. 声网传输层协议 AUT 的总结与展望丨Dev for Dev 专栏

    本文为「Dev for Dev 专栏」系列内容,作者为声网大后端传输协议负责人 夏天. 针对实时互动应用对网络传输带来的新需求和新挑战,声网通过将实时互动中的应用层业务需求与传输策略的分层和解耦,于 ...

最新文章

  1. CS0016: 未能写入输出文件的解决方法
  2. 5G 行业专网 — 三大运营商的 5G 专网类型
  3. anaconda的python使用教程-Python,Anaconda简介安装使用教程
  4. Spring+ehcache缓存实例
  5. TensorFlow2.0(八)--tf.function函数转换
  6. python大佬养成计划--协程实现TCP连接
  7. python客户端与服务器端通信数据库原理_python学习之网络部分
  8. selenium+ocr 破解验证码
  9. w ndows7安不上HP1020,惠普1020打印机驱动程序
  10. matlab 设计数字滤波器,基于Matlab的FIR数字滤波器设计
  11. springboot starter自定义实现公共模块
  12. 事后诸葛亮分析(小小大佬带飞队)
  13. 七周数据分析01_数据分析思维
  14. UDP头部结构,UDP校验和计算
  15. CUDA编译(一)---使用nvcc编译cuda
  16. HDU 2340 Obfuscation(dp)
  17. 如何选择适合你的兴趣爱好(四十),美食
  18. STFT使用overlap-add重建信号
  19. 嵌入式开发的架构设计
  20. 浅谈你们根本不懂的区块链游戏 1

热门文章

  1. Android Studio中进行单元测试
  2. Apache搭建web网站服务器
  3. 海洋工作室——网站建设专家:只有十句话,看了十分钟(完整版)[不应该只看十分钟!!!]...
  4. Nginx服务器中的Socket切分,需要的朋友可以参考下
  5. 分享几个Python小技巧函数里的4个小花招 1
  6. 最简单的《域中隔离用户的FTP站点》详解
  7. arg,argmin和argmax理解
  8. 洛谷P3642 [APIO2016]烟火表演
  9. 医疗物联网解决方案提供商“识凌科技”完成C轮融资
  10. 解决Spark集群无法停止