TCP发送端收到ACK后对传输队列的4次扫描

TCP如果收到ACK后,不管是顺序ACK还是重复ACK(可能带有SACK选项),都可能对传输队列进行4次扫描,它们先后顺序分别是:

1.第一遍扫面,分别做以下事情:

故事发生在tcp_ack->tcp_sacktag_write_queue函数

1.1.标记被SACK的数据包

故事发生在
tcp_ack->
tcp_sacktag_write_queue->函数主体

1.2.标记哪些已经重传的包可能丢失

故事发生在
tcp_ack->
tcp_sacktag_write_queue->
tcp_mark_lost_retrans

1.3.更新网络乱序度reordering

故事发生在
tcp_ack->
tcp_sacktag_write_queue->if (...) tcp_update_reordering

整个故事如下图所示:

TCP发送端收到ACK后对传输队列的4次扫描

如果UNA向前推进了,我们就不多说了,如果没有推进UNA,而且有SACK标记(请注意,FACK并不是一种特殊的模式,它只是SACK的一种激进重传方案)的情况下,第一遍扫描会完成上述的1.1,1.2,1.3这三件事,其中第一件事就不说了,就是按照ACK数据包的选项,将传输队列中的被选项选中的数据包标记为SACK;第二件事有点复杂,一般而言,TCP的每一种重传机制除了RTO的退避机制会重传多次之外,只会将数据包重传一次,但是事实上并不绝对,如果可以启发到重传的数据包有很高的概率丢失,那么就可以将该重传过的数据包抹去其“已重传”标志,这意味着如果有哪个执行路径认为其可以被重传,那么它将会被再次重传一次!

        如何判断一个重传的数据包已经丢失呢?请看Linux协议栈之tcp_mark_lost_retrans函数,其主要思想是:

在重传每一个数据包的时候,记录重传当时的snd_nxt,记为其ack_seq字段,如果发现一个被重传的数据包的ack_seq后面的数据包都被SACK了,那么就说明该重传数据包可能又一次没有到达接收端,丢失了!注意,这也是一种启发式算法,因为在重传数据包N的时候,其ack_seq几乎同时也被传输了,那么正常来讲,它们的ACK应该同时到来,然而数据包N的ack_seq后面的都被SACK了,而数据包N却没有被SACK,说明它可能丢失了。如下图所示:

TCP发送端收到ACK后对传输队列的4次扫描

关于1.3,请看《
TCP拥塞控制图解(不包括RTO,因为它太简单了)》 原图中的网络重排序一节。

需要说明的是,对于1.3而言,扫描的时候,总是找到fack(请注意,它仅仅是一个计数器而已)最左边的第一个数据包(fack计数器每次都要从左到右撸一遍,撸的同时,记录从左到右第一个遇到的被SACK的数据包【它可能是被SACK的,也可能是被DSACK的】),然后fack与该数据包之间的距离就是新的reordering的值(如果它比老的reordering大的话)。

2.第二遍扫描:清除已经被顺序接收的数据包,更新诸多计数器

故事发生在tcp_ack->tcp_clean_rtx_queue

完成第一遍扫描之后,紧接着进行的第二遍扫描会根据新的UNA值,清除掉其左边直到prior UNA之间的数据包,同时,更重要的是,更新各个计数器,需要更新的计数器有,包括但不限于:

a).sacked_out:如果该数据包曾经被SACK过,即有SACKed标记,那么sacked_out会递减相应被清除掉的数据包大小;

b).lost_out:如果该数据包曾经被标记为LOST,那么lost_out会递减相应的数据包的大小;

c).packets_out:由于被ACK,那么packets_out会无条件递减被清除掉的数据包的大小;

d).retrans_out:如果该数据包被重传过,那么retrans_out会递减被清除掉的数据包的大小;

e).fackets_out:由于fack计数器仅仅与最右边被SACKed的数据包以及UNA有关,因此如果被清除掉的数据包大于fack计数器,那么fack归零,否则递减被清除掉的数据包大小。如图所示:

TCP发送端收到ACK后对传输队列的4次扫描

第二遍扫面的宗旨有二,其一是向前推进UNA,清除发送队列的skb,其二就是更新TCP的计数器,标准化“在途流量”。

3.第三遍扫描:标记可能已经LOST的数据包

故事发生在tcp_ack->
tcp_fastretrans_alert->
tcp_update_scoreboard

首先第一遍扫描标记了SACKed了哪些数据,并且清除了大概率已经丢失的重传数据标记,第二遍扫描清除了已经被顺序ACK的数据,那么现在该第三遍扫描了,即标记出哪些是已经丢失的数据,这个标记算法是启发式的,并不能表明一定准确,所以Linux的实现专门提供了各种undo机制!具体的细节,还是参见《
TCP拥塞控制图解(不包括RTO,因为它太简单了)

4.第四遍扫描:按照RFC3517 nxtSeg例程传输该传输的数据包

故事发生在tcp_ack->
tcp_fastretrans_alert->tcp_xmit_retransmit_queue

哪些数据包被SACK(已经标记),哪些被ACK(已经清除),哪些被当成LOST(已经标记),接下来该做什么呢?很显然,该重传了!到底怎么重传呢?还是参见《
TCP拥塞控制图解(不包括RTO,因为它太简单了)》以及其开头展示的勘误。

5.结语

本文用很短的篇幅简述了Linux协议栈收到TCP的ack包后的反应,开启了4遍扫描(对于fast路径,没有第一遍!),每一遍扫描都完成很少的特定任务,最终完成TCP的快速重传。

故事还没有结束

原文链接: https://blog.csdn.net/dog250/article/details/51318970

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    TCP发送端收到ACK后对传输队列的4次扫描

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/408159

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月26日 上午10:46
下一篇 2023年4月26日 上午10:46

相关推荐