分类: LINUX
2014-03-25 19:51:55
作者:henrystark henrystark@126.com
Blog: http://henrystark.blog.chinaunix.net/
日期:20140325
本文遵循CC协议:署名-非商业性使用-禁止演绎 2.5()。可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接。如有错讹,烦请指出。
写作目的:本篇文章是TCP拥塞控制的核心讲解,写给专业人士参阅,如果不懂,不建议硬读。由于我水平有限,对TCP核心机制了解不完善,因此本文侧重于讨论、推断和逻辑分析。
TCP拥塞控制的核心在于丢包处理,窗口如何调节等。在Linux源码中,负责TCP拥塞控制核心的代码可以概括为两大状态机:TCP拥塞控制状态机和SACK标记重传状态机。前者尤为重要,负责TCP正常状态和拥塞状态的转换,后者负责标记丢包时的重传队列。本篇文章侧重TCP拥塞控制状态机的分析,两大状态机一起讲解会十分复杂,因此暂时不讨论SACK状态机。
其中,Disorder和Recovery状态是TCP丢包时进入的拥塞状态,十分关键。关于拥塞控制状态机的变换,请参阅Linux/net/ipv4/tcp_input.c, tcp_fastretrans_alert()函数。Disorder和Recovery两个状态最关键的窗口处理函数是tcp_moderate_cwnd和tcp_cwnd_down。Disorder状态基本不做窗口调整,滑动窗口相当于卡死状态。Recovery状态做降窗动作。
tcp_fastretrans_alert() { ………………………………………………………………………………………………………………………………………………………………………… if (icsk->icsk_ca_state < TCP_CA_CWR) { //disorder open < cwr if (!(flag & FLAG_ECE)) tp->prior_ssthresh = tcp_current_ssthresh(sk); tp->snd_ssthresh = icsk->icsk_ca_ops->ssthresh(sk); //这一句很关键,用当前选定的拥塞控制算法来调节阈值,多个拥塞控制算法可以切换,C语言实现多态。 TCP_ECN_queue_cwr(tp); } } tcp_moderate_cwnd(struct tcp_sock *tp) {//最多可以发送三个新的数据包,窗口基本不做调整,卡死状态 tp->snd_cwnd = min(tp->snd_cwnd, tcp_packets_in_flight(tp) + tcp_max_burst(tp)); tp->snd_cwnd_stamp = tcp_time_stamp; } tcp_cwnd_down(struct sock *sk, int flag) { struct tcp_sock *tp = tcp_sk(sk); int decr = tp->snd_cwnd_cnt + 1; //每收到两个ACK窗口减去1 if ((flag & (FLAG_ANY_PROGRESS | FLAG_DSACKING_ACK)) || (tcp_is_reno(tp) && !(flag & FLAG_NOT_DUP))) { tp->snd_cwnd_cnt = decr & 1; decr >>= 1; if (decr && tp->snd_cwnd > tcp_cwnd_min(sk)) tp->snd_cwnd -= decr; tp->snd_cwnd = min(tp->snd_cwnd, tcp_packets_in_flight(tp) + 1); tp->snd_cwnd_stamp = tcp_time_stamp; } }
由于我未能看懂全部拥塞状态机处理的源码,这部分只是出于自己的理解,我在这部分也卡了很久,不得寸进,还望高手指点。
假设正常传输的cwnd为10,left表示窗口左侧,right表示窗口右侧,假设这时候1号包丢失:
left_____________________________right 1 2 3 4 5 6 7 8 9 10
丢包发生后,窗口左侧卡死(因为没有收到新的ACK,窗口不能向右滑动,右侧也不能扩展),这是disorder状态,窗口不做调整,不发送新数据:
left_____________________________right 1 2 3 4 5 6 7 8 9 10
重传1号包后,依次收到后续ACK: (进入Recovery状态,窗口降低)
收到1号包ACK后,窗口左侧移动,整体向右滑动,cwnd仍为大小10:
left_______________________________right 2 3 4 5 6 7 8 9 10 11
收到2号包ACK,左侧向右滑动,cwnd减去1,遵循cwnd_down函数逻辑,cwnd变为9:
left___________________________right 3 4 5 6 7 8 9 10 11
3号包ACK:
left____________________________right 4 5 6 7 8 9 10 11 12
4号包ACK:
left_________________________right 5 6 7 8 9 10 11 12
5号包ACK:
left__________________________right 6 7 8 9 10 11 12 13
6号包ACK:
left_______________________right 7 8 9 10 11 12 13
7号包ACK:
left________________________right 8 9 10 11 12 13 14
8号包ACK:
left_____________________right 9 10 11 12 13 14
9号包ACK:
left______________________right 10 11 12 13 14 15
10号包ACK:
left__________________right 11 12 13 14 15
如上,窗口降低是通过减慢窗口右端的滑动速度实现的,右侧的滑动速度是左侧的二分之一。
判断cwnd已经降低到了cwnd_loss/2,则降窗过程结束。
在丢包发生后,发送了cwnd_loss/2的新数据包进入网络。cwnd_loss是丢包发生时的窗口。ACK到达是很快的,一个RTT内就有一个整窗的ACK回复,因此,上面的减窗过程很快就完成了,相当于一个RTT内,窗口降低了二分之一。
以上分析了丢包时,TCP拥塞状态的处理。由于没有考虑SACK,以上的分析只是最简单的窗口调整模型,接近于TCP reno,如果考虑SACK,窗口处理会十分复杂。有兴趣者可以研究下。
这篇文章仍不完善,是为了解答某些专业人士的疑问而写出来,恐有错讹。最近很忙,短时期内不会再更新博客。
codewarrior2019-08-16 05:31:32
最后那图表演示的过程是newreno?看上去不像啊,如果收到连续3个重复ack,则进入快重传和快恢复阶段,ssthresh=cwnd/2,cwnd=ssthresh+3,也就是说如果包1丢了,则ssthresh=10/2=5,cwnd=5+3=8,然后每收到一个partial ack,表明有一个数据包离开网络,根据在途包守恒原则,cwnd++,如果cwnd右边界越过了snd_max,则表示可以开始发送新的包。