极短时间内重发DUP ACK,为什么?

这篇文章写的有点过于细节,因此考虑到可读性和日后的可查阅性,我以两个问题作为引子。作为TCP相关项目的招聘,也可以作为面试题,不过,我敢肯定,大多数人都不能回答第一个问题,第二个问题可能会模棱两可。

假设TCP进入快速重传时有以下的序列:


你能给出重传的序列吗?


这里到了问题1的正文。
TCP在快速重传的时候,会依照以下的优先级来传输数据段:

优先级1:标记为LOST的数据段
优先级2:新的尚未发送的数据段
优先级3:在UNA和HIGH区间内没有标记为LOST,没有标记为SACKed,没有重传过的数据段

我们以下仍然以问题1的场景描述中的图示来解析,时不时的可能会变动一下细节,但不伤大雅。
        Linux在实现快速重传的时候,依照RFC3517的建议维护了一个计分板,该计分板中统计了哪些数据段是SACKed的,哪些数据段是LOST的,我们发现,上图正好是一个计分板的绝佳体现。
        在计分板上标记LOST数据段的时候,其要求是要满足RFC3517的IsLost例程,简单点说,就是任何一个被标记为LOST的数据段后面都要有至少DupThresh个被SACKed的数据段。为了满足这个要求,我们在计分板(也就是上图)中标记LOST数据段的方法也就简单了:
从前往后遍历,只要碰到没有被SACKed的数据段就标记为LOST,遍历的过程以SACKed数据段剩余DupThresh个时停止!
因此, 可以在遍历的过程中数SACKed数据段的数量,记为cnt,直到cnt的数量达到SACKed数据段总量与DupThresh的差为止退出

。在Linux中,这个逻辑正是由函数tcp_mark_head_lost来实现的,可以查看Linux源码详细研究该函数,本文就不再赘述了。但是还是给出伪代码,如下:

现在我们的计分板更新了,如下所示:


现在看优先级2传输的数据段,即尚未发送的新数据,也就是说在图中HIGH标记之后的数据段。这里也许你会有一点困惑,为什么新数据的传输优先级会更高。简单的解释就是,LOST标记的数据段数量是由SACKed数据段的数量决定的,理论上在数据包守恒的原则下二者是相等的,但考虑到网络乱序的存在,LOST标记的数据段数量会比SACKed数据段数量少一些,二者之差就是一个乱序度的度量值,也就是RFC3517中的DupThresh。因此,剩下那些既没有被SACKed,又没有标记为LOST的数据段,我们称为未决数据。理论上它们没有丢失,只是还在路上,之所以还没有收到ACK或者SACK的原因有三类:

不管是哪一个原因,它们并没有丢,起码是理论上认为它们并没有丢失,所以此时还需要再等待一下,不管是乱序了,慢了,还是ACK丢了,再等一下都是可以等到确认的,因此此时这类数据段的传输优先级自然就没有新的数据更高,于是在传输完LOST标记的数据段后,就要传输新数据,而不是上述的未决数据。
        最后,我们想一下最坏的可能性,即那些未决数据真的丢失了,这很可能是我们的预判过于乐观了,当这些未决数据真的丢失了时,滑动窗口会因为它们而被阻滞无法前行,因此必须在合适的时候去重传它们,这个时机可以尽可能地保守向后,于是它们的传输优先级自然而然也就排在了新数据之后了。

如果仅仅是上面的理论分析,可能会比较枯燥,另外也不便于彻底理解,你只有看到它实实在在就是那样运作的,才能放心。本节用实例来打消最后的疑惑。依然采用packetdrill,脚本如下:

// 此时窗口已经塞满(初始窗口为10),仅仅pending到发送队列,未实际发送 // 我关闭了fack,以下仅仅触发sack的快速重传 // 这里预期会重传,由于窗口还够传1个段,预期还会传输尚未发送的 // 由于前面又SACK了2个新段,按照计分板标记LOST的规则,会将标记为LOST进而重传 // 请注意:如果上述SACK中的SACK段是 而不是 ,则依然会重传,只不过是作为未决数据的前向重传! // partial ACK后,无新数据,但依然有未决数据,它们会不会被重传呢?这个问题下面在正文中解答! // 直到write过后,发送新的10字节数据 // 最后,发语辞,无意义,类似“呜呼”这样的...

我不喜欢用packetdrill本身的“预期”,我觉得这个packetdrill本身的机制就是垃圾(它并没有支持除了它调通的之外的情况!)!于是,我自己来预期...我还是用tcpdump来最终确认,图示如下:


如果说将packetdrill脚本中的第二个SACK段换成第一个SACK段的重复,即sack 而不是sack 的话,tcpdump抓包结果是一致的,只不过关于这个段传输的解释不同而已,这种情况下,该段属于未决数据段的前向传输!

        现在来解释上述packetdrill脚本中那个注释的疑问,即如果收到了partial ACK,并且其后面一直到HIGH再也没有被SACK的数据段,那么这些段会被重传吗?答案是不会!那么这里就有一个风险,万一它们真的丢了怎么办?!答案是只能等后续的SACK或者超时了!我来总结一下关于partial

我已经阐述了理论,但是不足,于是我阐述了上面的一个实例,然而还是不足,不足在哪里呢?不足在于,很多的逻辑会混淆在一起,比如优先级1和优先级3的数据段混淆在一起发送的话,你会认为这是通过一个机制发送的两个数据段还是通过两个机制分别发送的两个数据段呢?于是,我觉得有必要说一说。我先给出一个packetdrill脚本:

// 注意,我只发了9个数据段! // 此时,应该重传这是显然的,然而你会发现,连也重传了,这是为什么呢?难道也被标记为LOST了么?非也! // 最后,发语辞,无意义,类似“呜呼”这样的...

此时,你会看到,数据段和同时被重传了,这是怎么回事呢?如果你初看,那么你会觉得TCP的重传逻辑一次性重传了所有的“空洞”,即以及,然而事实上段和段这两个段,并不是按照同一个机制传输出去的,第一个数据段是优先级1的传输使然,而数据段则是优先级3的传输使然!tcpdump的结果如下:


在抓包上看,它们确实是一起传输出去的,怎么区分呢?非常简单,本文往前看即可,只要有新数据尚未发送,Linux协议栈就优先发送新数据,而不是重传“未决数据段”!如果在packetdrill脚本中再多发2个段的数据,那么你就会发现段和段被新数据段给隔开了。如果你对Linux的代码比较熟悉,那么对协议栈进行probe也能很清除的看到这个结果,你只需要跟踪TCP对象的retransmit_high指针即可确认传输优先级1的数据段到哪里截止。

这个问题相对比较简单,答案如下:
是否触发重传,不仅仅取决于被ACK/SACK的字节数与reordering*MSS的关系,还取决于发送的skb的大小。
假设当前的MSS为1000,reordering的值为3,那么是不是一定要重复确认的字节数超过3000才会触发快速重传呢?答案是否定的!因为协议栈其实是在计数段数与reordering的关系,而不是字节数。运行如下packetdrill脚本并抓包:

// 注意这里只发10个字节,即将10个字节封装在一个数据段中。 // 此时SACK了2001个字节,显然不够1000*3字节,会触发快速重传吗?

如注释中的问题,会触发快速重传吗?显然不会!但是原因真的是因为SACK字节数不够3*1000吗?非也!

再次运行脚本,你会发现,快速重传竟然触发了,数据段被重传!

正如我敢说,玩OpenVPN的人,大多数只是虚玩,根本很少人懂一样。不破而立也是虚的。
        什么叫盲目?对待一个事物根本还没琢磨透就想在上面玩创新,这就是盲目。这种盲目与新文化运动前后中国文化青年内心的迷茫还有大不同,那种迷茫更多的是因为旧规则已然被打碎,新规则尚未确立,在新旧之间的那种夹层里,人的内心是痛苦的。然而,不破而立这种盲目却着实不是这种夹层里的感觉,而是一种自大的狂妄。
如果说你要证明你的东西是好的,首先你要有一个不好的做参照,而不是拿一个你尚未琢磨透的东西做参照。这是当代青年的通病,当然我也是其中一员,然则我认识到这是一个大错。如果你有机会看现在IT领域的求职简历,侧重于研发的那种,你会发现各种精通,其内容往往都是大学必修课甚至选修课的目录,还有就是看过几本书就谈精通,殊不知很多书的书名定义就有大错误!比如我经常吐槽的,中国人写的《JAVA网络编程》,《Windows网络编程》...看完了之后,让人学会了JAVA编程,学会了Windows编程,却完全忘了网络,这些书与网络有关吗?
仅仅知道socket接口的调用,就说自己精通网络,然后大评特评BGP协议怎么不好,VLAN标准应该如何改造...此人太猛。这种态度往往都是初学者携带的特征,我老婆是学日语的,记得刚上大一那会儿,她每到超时,看到各种包装袋上只要写有日语,就会出声朗读一番,后来随着她过了一级,读了研究生...现在再也不会出声朗读了。一般的初学者往往会觉得什么都很简单,其实揭开表明那层膜,下面的水非常深,要想有真正自己的东西,把这水淌浑是必不可少的。


前一篇「」得到了很多读者的认可,在此特别感谢你们的认可,大家都暖暖的。

来了,今天又来图解 TCP 了,小林可能会迟到,但不会缺席。

迟到的原因,主要是 TCP 巨复杂,它为了保证可靠性,用了巨多的机制来保证,真是个「伟大」的协议,写着写着发现这水太深了。。。

本文的全部图片都是小林绘画的,非常的辛苦且累,不废话了,直接进入正文,Go!


相信大家都知道 TCP 是一个可靠传输的协议,那它是如何保证可靠的呢?

为了实现可靠性传输,需要考虑很多事情,例如数据的破坏、丢包、重复以及分片顺序混乱等问题。如不能解决这些问题,也就无从谈起可靠传输。

那么,TCP 是通过序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输的。

今天,将重点介绍 TCP 的重传机制、滑动窗口、流量控制、拥塞控制。

TCP 实现可靠传输的方式之一,是通过序列号与确认应答。

在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回一个确认应答消息,表示已收到消息。

但在错综复杂的网络,并不一定能如上图那么顺利能正常的数据传输,万一数据在传输过程中丢失了呢?

所以 TCP 针对数据包丢失的情况,会用重传机制解决。

接下来说说常见的重传机制:

重传机制的其中一个方式,就是在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的 ACK 确认应答报文,就会重发该数据,也就是我们常说的超时重传

TCP 会在以下两种情况发生超时重传:

超时时间应该设置为多少呢?

我们先来了解一下什么是 RTT(Round-Trip Time 往返时延),从下图我们就可以知道:

RTT 就是数据从网络一端传送到另一端所需的时间,也就是包的往返时间。

假设在重传的情况下,超时时间 RTO 「较长或较短」时,会发生什么事情呢?

上图中有两种超时时间不同的情况:

  • 当超时时间 RTO 较大时,重发就慢,丢了老半天才重发,没有效率,性能差;
  • 当超时时间 RTO 较小时,会导致可能并没有丢就重发,于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发。

精确的测量超时时间 RTO 的值是非常重要的,这可让我们的重传机制更高效。

根据上述的两种情况,我们可以得知,超时重传时间 RTO 的值应该略大于报文往返 RTT 的值

至此,可能大家觉得超时重传时间 RTO 的值计算,也不是很复杂嘛。

好像就是在发送端发包时记下 t0 ,然后接收端再把这个 ack 回来时再记一个 t1,于是 RTT = t1 – t0。没那么简单,这只是一个采样,不能代表普遍情况

实际上「报文往返 RTT 的值」是经常变化的,因为我们的网络也是时常变化的。也就因为「报文往返 RTT 的值」 是经常波动变化的,所以「超时重传时间 RTO 的值」应该是一个动态变化的值

我们来看看 Linux 是如何计算 RTO 的呢?

估计往返时间,通常需要采样以下两个:

  • 需要 TCP 通过采样 RTT 的时间,然后进行加权平均,算出一个平滑 RTT 的值,而且这个值还是要不断变化的,因为网络状况不断地变化。
  • 除了采样 RTT,还要采样 RTT 的波动范围,这样就避免如果 RTT 有一个大的波动的话,很难被发现的情况。

是吧? TCP 巨复杂吧?看完很累吧?

但这还只是 TCP 冰山一脚,它的更深处就由你们自己去探索啦。

本文只是抛砖引玉,若你有更好的想法或文章有误的地方,欢迎留言讨论!

小林是专为大家图解的工具人,Goodbye,我们下次见!


读者问:“整个看完收获很大,下面是我的一些疑问(稍后
1.拥塞避免这一段,蓝色字体:每当收到一个
2.快速重传的拥塞发生算法,步骤一和步骤2是
否写反了?否则快速恢复算法中最后一步【如果
收到新数据的ACK后,设置cwnd为
ssthresh,接看就进入了拥塞避免算法】没什么
3.对ssthresh的变化介绍的比较含糊。”

  1. 没有写反,同样你可以在 RFC2581 第 5 页找到答案
  2. ssthresh 就是慢启动门限,我觉得 ssthresh 我已经说的很清楚了,当然你可以找其他资料补充你的疑惑


「图解网络」文章受到了很多读者的喜爱与支持,为了方便大家阅读,小林把自己原创的图解网络系列文章整理成了pdf,内容涵盖计算机网络的重点知识,比如 HTTP、TCP、UDP、IP等等,pdf 共「300 页 + 9W字 + 30 张图」

并且根据不同读者的阅读习惯,我整了两种风格的图解网络pdf,分别是「亮白版本」和「暗黑版本」。

百度网盘下载链接如下:

我要回帖

更多关于 航班熔断确诊人数怎么计算 的文章

 

随机推荐