Chinaunix首页 | 论坛 | 博客
  • 博客访问: 614589
  • 博文数量: 30
  • 博客积分: 125
  • 博客等级: 民兵
  • 技术积分: 1871
  • 用 户 组: 普通用户
  • 注册时间: 2013-01-03 11:29
文章分类

全部博文(30)

文章存档

2014年(9)

2013年(21)

分类: LINUX

2014-03-25 19:51:55

作者:henrystark henrystark@126.com
Blog: http://henrystark.blog.chinaunix.net/
日期:20140325
本文遵循CC协议:署名-非商业性使用-禁止演绎 2.5()。可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接。如有错讹,烦请指出。


写作目的:本篇文章是TCP拥塞控制的核心讲解,写给专业人士参阅,如果不懂,不建议硬读。由于我水平有限,对TCP核心机制了解不完善,因此本文侧重于讨论、推断和逻辑分析。

TCP丢包减窗和两大状态机

TCP拥塞控制的核心在于丢包处理,窗口如何调节等。在Linux源码中,负责TCP拥塞控制核心的代码可以概括为两大状态机:TCP拥塞控制状态机和SACK标记重传状态机。前者尤为重要,负责TCP正常状态和拥塞状态的转换,后者负责标记丢包时的重传队列。本篇文章侧重TCP拥塞控制状态机的分析,两大状态机一起讲解会十分复杂,因此暂时不讨论SACK状态机。

1.TCP拥塞控制状态机


其中,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;
        }
    }

2.丢包时的窗口变化

由于我未能看懂全部拥塞状态机处理的源码,这部分只是出于自己的理解,我在这部分也卡了很久,不得寸进,还望高手指点。
假设正常传输的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,窗口处理会十分复杂。有兴趣者可以研究下。
这篇文章仍不完善,是为了解答某些专业人士的疑问而写出来,恐有错讹。最近很忙,短时期内不会再更新博客。

阅读(10027) | 评论(2) | 转发(3) |
给主人留下些什么吧!~~

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,则表示可以开始发送新的包。

goingstudy2017-07-18 15:42:21

这个是rate halving吧,不过好像没有standard rfc 只是一个draft,但是linux好像用了