TCP拥塞控制图解 不包括RTO,因为它太简单了 勘误1

Posted ksiwnhiwhs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TCP拥塞控制图解 不包括RTO,因为它太简单了 勘误1相关的知识,希望对你有一定的参考价值。

熬过了几个夜晚,终于把TCP的拥塞处理的Linux撸了一遍,仓促中也总结了一幅巨大的图,然而今天下午的例会讨论后,我自己说着说着发现还有一些值得商榷的地方,有的是笔误,也有的是一些细节依然没有搞清楚,因此特此勘误,原文我只修改了文字,因为重新贴图代价实在太大,再者,我希望留下一些错误的印记,这样也能看清楚整个发展的历程,希望两篇一起看。

 

虽然在大师级的神看来,这不算什么,但是对于我,这是一个比较浩大的工程,错误之处在所难免,请谅解!谢谢!如果能共同进步,这就是我分享的最终目的和动力。

 

PS:
 1.今天是我的生日,我觉得就这么睡去不符合这个特殊日子的风格,因此就趁着二两酒把这篇勘误补齐吧。
 2.这是第一篇勘误,我相信原图还有很多问题,以后会持续更新,风格和方式与这个一致。

 问题1:mark lost的时候到底是怎么进行的

 这个涉及到了原图中的What to retransmit,到底Linux内核函数tcp_mark_head_lost是怎么进行的呢?看下面的发送序列:
 UNA|hole1|hole2|sack1|sack2|hole3|hole4|sack3-7|unack1|unack4|snd_nxt|...
 我们得知,sack_out的值是2+5=7,假设默认reordering是3,那么在不启用fack的情况下,应该有7-3=4个数据包被标记为LOST,该标记哪些呢?

 选择:从una开始,标记没有被sack的4个数据包,即UNA,hole1,hole2,hole3。这也是我在原图中的理解,但事实上呢?我们开看代码(Linix 3.10),tcp_mark_head_lost:

static void tcp_mark_head_lost(struct sock *sk, int packets, int mark_head)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct sk_buff *skb;
    int cnt, oldcnt;
    int err;
    unsigned int mss;
    /* Use SACK to deduce losses of new sequences sent during recovery */
    const u32 loss_high = tcp_is_sack(tp) ?  tp->snd_nxt : tp->high_seq;
    ...

    tcp_for_write_queue_from(skb, sk) {
        if (skb == tcp_send_head(sk))
            break;
        /* TODO: do this better */
        /* this is not the most efficient way to do this... */
        tp->lost_skb_hint = skb;
        tp->lost_cnt_hint = cnt;

        if (after(TCP_SKB_CB(skb)->end_seq, loss_high))
            break;

        oldcnt = cnt;
        if (tcp_is_fack(tp) || tcp_is_reno(tp) ||
            // 请注意,被标记为SACKED的数据包也会占据mark lost计数!!如上例,当UNA,hole1,hole2被标记完
            // 以后,下一个本应该标记hole3,但是由于sack1占据可一个“位置”,因此如果只标记4个LOST,那么将会
            // 到sack1为止!!,不会再标记hole3!
            // 但这并不意味着LOST标记仅仅会标记左边数第一系列的空洞,如果标记数为5,那么由于sack2也占据了一个
            // 位置,标记为LOST的依然是UNA,hole1,hole2,但是如果是标记6个LOST,那么结果将是标记UNA,hole1,hole2,
            // hole3为LOST!明白了吗?
            (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED))
            cnt += tcp_skb_pcount(skb);

        if (cnt > packets) {
            ...
        }

        tcp_skb_mark_lost(tp, skb);

        if (mark_head)
            break;
    }
    tcp_verify_left_out(tp);
}

总结:标记LOST的时候,会从左到右,标记的时候并不会跳过已经被SACK的数据包,只是,如果它已经被SACK了,那么将不会被置LOST标志。这有什么意义?在SACK reneging的时候,将会有意义。
这意味着,可以重传多少数据包和空洞的位置有关,一个合理的假设是,如果中间大量连续积累了SACKed的数据包,仅仅说明在这些数据包前面的空洞丢失的可能性比较大,这是真丢包,而不是乱序,然而话如果反着说,如果被SACKed的数据包间隔交替到来,这恰恰说明乱序的可能大于丢包,此时我们到底要重传哪些呢?如果给我安放一个偏置电阻的权力,我就可以完美解决这个问题,然而电阻可以在电子市场一块钱抓一把,跟老板混熟了还可以白拿,偏置算法可就是不是随便百度一下就能用的了....(百度?嗯,百度!)

问题2:关于When to retransmit

原图中,在描述SACK模式的时候,我在图中只画了3个数据包被SACK,但是事实上,要想触发重传,必须有至少4个数据包被SACK,这是一个笔误。

问题3:原图中的乱序检测,在描述重复SACK的时候,我将reordering的右沿滑到了snd_high,但事实上,应该是最高被SACK的那个位置,正如下面的文字描述的那样,犯这样的错误,真不应该!

问题4:严格说,这是我自己想的一个问题,原图原文不一定错,然而人不就是不断否定自己才能进步么?

请看How to retransmit,旁白中有关于RFC3517的描述,规范了传输的优先级,第一优先传输LOST数据包,第二传输新数据,然而我们看到,tcp_xmit_retransmit_queue,发现Linux实现第二优先传输新数据的时候,完全依赖“output路径”,也就是说在传输完LOST数据包之后,只要发现有新数据等待传输,直接就退出了tcp_xmit_retransmit_queue,至于说output路径到底什么时候被触发,
已经可以传输多少新数据,完全不管!
       在解耦合方面,这么做是恰当的(请注意这个函数的名字是retranmit,而不是transmit),但是难道这样不会造成窗口浪费吗?试想当前窗口为100,我有10个LOST数据需要重传,这意味着
       可以发送90个新数据包,然而可能此时没有那么多新数据包,难道这个时候进行“前向重传”不好吗?

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

















以上是关于TCP拥塞控制图解 不包括RTO,因为它太简单了 勘误1的主要内容,如果未能解决你的问题,请参考以下文章

Linux TCP拥塞控制的undo图解

图解 TCP/IP 协议

Linux TCP F-RTO图解

TCP 拥塞控制

图解TCP-IP协议

深入浅出图解计算机网络 之 TCP可靠传输的实现2: 超时重传+拥塞控制