zoukankan      html  css  js  c++  java
  • 聊一聊tcp 拥塞控制 二

    拥塞窗口的调整撤销

      很多网络不支持ECN,所以追踪丢失包时需要推测。重新排序(reordering)对于发送方来说通常是一个问题,因为它不能分清缺失的ACK是由于丢失还是被延迟了,所以TCP可能会做出错误的判断,不必要的调整了拥塞窗口。这时就需要一种对错误的拥塞调整做出修正的机制——拥塞窗口调整撤销。

    从Recovery状态撤销

    怎么来探测是否不必要重传了数据包呢?

    • D-SACK   在最近一次恢复期间重传的段都被D-SACK确认。这就说明了调整是不必要的。最近一次恢复期间重传的数据包个数记为undo_retrans,如果收到一个D-SACK,则undo_retrans--,直到undo_retrans为0,说明全部的重传都是没必要的,则需要撤销窗口调整。
    • Timestamp   使用该选项时,通过比较收到ACK的时间戳和重发数据包的时间戳,可以判断窗口调整是否没必要。   tcp_may_undo()中的!tp->undo_retrans和tcp_packet_delayed(tp)分别对应以上两种方法。tcp_packet_delayed()中其实也包含两种方法:Timestamp和F-RTO。!tp->retrans_stamp表示已经使用F-RTO进行处理。只有检查出至少有一种成立时,才能进行拥塞窗口调整撤销。
    /* Nothing was retransmitted or returned timestamp is less
     * than timestamp of the first retransmission. ------------传了之后还没有接收到对方发送的确认
    */ static inline bool tcp_packet_delayed(const struct tcp_sock *tp) {
    return !tp->retrans_stamp || tcp_tsopt_ecr_before(tp, tp->retrans_stamp);
    }
    /*
    1、undo_marker表明套接口进入了拥塞状态(TCP_CA_Recovery/TCP_CA_Loss),调整了拥塞窗口;否则就没有必要调整---记录了重传
    2、undo_retrans等于0. 报文重传之后被D-SACK确认,表明这些重传为不必要的,原始报文未丢失。----->没有重传的段数
    3、retrans_stamp等于0(重传报文时间戳retrans_stamp等于零). 在进入拥塞状态后还没有进行过任何重传,或者重传报文都已送达
    4、接收到的ACK确认报文中的回复时间戳(rcv_tsecr)在重传报文的时间戳之前,表明是对于原始报文的确认,而不是对重传报文。-----------》重传了之后还没有接收到对方发送的确认
    */
    static inline bool tcp_may_undo(const struct tcp_sock *tp)
    {
        return tp->undo_marker && (!tp->undo_retrans || tcp_packet_delayed(tp));
    }
    /* People celebrate: "We love our President!" */
    static bool tcp_try_undo_recovery(struct sock *sk)
    {
        struct tcp_sock *tp = tcp_sk(sk);
    
        if (tcp_may_undo(tp)) { /*是否可以进行拥塞撤销*/
            int mib_idx;
    
            /* Happy end! We did not retransmit anything
             * or our original transmission succeeded.
             */
            DBGUNDO(sk, inet_csk(sk)->icsk_ca_state == TCP_CA_Loss ? "loss" : "retrans");
            tcp_undo_cwnd_reduction(sk, false);
            if (inet_csk(sk)->icsk_ca_state == TCP_CA_Loss)
                mib_idx = LINUX_MIB_TCPLOSSUNDO;
            else
                mib_idx = LINUX_MIB_TCPFULLUNDO;
    
            NET_INC_STATS_BH(sock_net(sk), mib_idx);
        }
        if (tp->snd_una == tp->high_seq && tcp_is_reno(tp)) {
            /* Hold old state until something *above* high_seq
             * is ACKed. For Reno it is MUST to prevent false
             * fast retransmits (RFC2582). SACK TCP is safe   这个是什么鬼   虚假快速重传??. 
    ,如果当前窗口中还有重传报文存在于网络中,保留retrans_stamp的值,避免这些重传报文触发dupack,再次引起错误的快速重传,
    此时需要保持拥塞状态不撤销,当再次接收到新的ACK报文(tcp_try_undo_recovery再次运行,但不会再执行以上的撤销拥塞窗口部分)
    */ tcp_moderate_cwnd(tp); if (!tcp_any_retrans_done(sk)) tp->retrans_stamp = 0; return true; }
    //如果SND.UNA大于high_seq,套接口直接恢复到TCP_CA_Open状态
    tcp_set_ca_state(sk, TCP_CA_Open);
    return false;
    }
    static void tcp_undo_cwnd_reduction(struct sock *sk, bool unmark_loss)
    {
        struct tcp_sock *tp = tcp_sk(sk);
    
        if (unmark_loss) {// 是否取消lost 报文丢失 标志
            struct sk_buff *skb;
    
            tcp_for_write_queue(skb, sk) {
                if (skb == tcp_send_head(sk))
                    break;
                TCP_SKB_CB(skb)->sacked &= ~TCPCB_LOST;
            }
            tp->lost_out = 0;
            tcp_clear_all_retrans_hints(tp);
        }
    
        if (tp->prior_ssthresh) {// 根据前一个 慢启动阈值的旧值是否存在来判断是否撤销操作
            const struct inet_connection_sock *icsk = inet_csk(sk);
    
            if (icsk->icsk_ca_ops->undo_cwnd)// 如果当前拥塞算法有 undo_cwnd 接口 则使用他
                tp->snd_cwnd = icsk->icsk_ca_ops->undo_cwnd(sk);
            else// 否则就是当前窗口 和 2倍满启动阈值的 最大值
                tp->snd_cwnd = max(tp->snd_cwnd, tp->snd_ssthresh << 1);
    
            if (tp->prior_ssthresh > tp->snd_ssthresh) {
                tp->snd_ssthresh = tp->prior_ssthresh; //恢复到出现 recovery 状态时 的ssh_thresh
                tcp_ecn_withdraw_cwr(tp);// 取消 TCP_ECN_DEMAND_CWR 
            }
        } else {// 不存在满启动阈值旧值 则在当前拥塞窗口和启动阈值间  选一个较大值作为当前拥塞窗口
            tp->snd_cwnd = max(tp->snd_cwnd, tp->snd_ssthresh);
        }
        tp->snd_cwnd_stamp = tcp_time_stamp; // 记录最近一次检测cwnd 的时间
        tp->undo_marker = 0;/*复位撤销标志*/
    }

     上述是在 !before(tp->snd_una, tp->high_seq)  条件成立的前提下,也就是 ack 的序号已经大于 拥塞开始时的 high_seq

       undo_marker表明套接口进入了拥塞状态(TCP_CA_Recovery/TCP_CA_Loss),调整了拥塞窗口

    撤销TCP_CA_Recovery状态二(undo_partial)

      对于TCP_CA_Recovery拥塞状态,如果ACK报文没有确认全部的进入拥塞时SND.NXT(high_seq)之前的数据,仅确认了一部分(FLAG_SND_UNA_ADVANCED),执行撤销函数tcp_try_undo_partial

    /* Undo during fast recovery after partial ACK.  
    如果ack 确认了部分重传的段,则会调用此函数 进行拥塞窗口撤销。 其中参数acked 为此次确认的段 数目
    重传结束 退出recovery 状态*/
    static bool tcp_try_undo_partial(struct sock *sk, const int acked,
                     const int prior_unsacked, int flag)
    {
        struct tcp_sock *tp = tcp_sk(sk);
        /*没有使用tcp_may_undo进行撤销拥塞窗口的判断,而是使用了其中的一部分,即tcp_packet_delayed判断报文是否仅是被延迟了,
                忽略undo_retrans值的判断。其逻辑是在接收到部分确认ACK的情况下,只要tcp_packet_delayed成立,原始报文就没有丢失而是被延时了,
                就应检查当前的乱序级别设置是否需要更新(tcp_update_reordering),防止快速重传被误触发。*/
        if (tp->undo_marker && tcp_packet_delayed(tp)) {
            /* Plain luck! Hole if filled with delayed
             * packet, rather than with a retransmit.
             */
            tcp_update_reordering(sk, tcp_fackets_out(tp) + acked, 1);
    
            /* We are getting evidence that the reordering degree is higher
             * than we realized. If there are no retransmits out then we
             * can undo. Otherwise we clock out new packets but do not
             * mark more packets lost or retransmit more.
             *///不进行undo_retrans值的判断,但是这里判断变量retrans_out(重传报文数量)是否有值
             //如果网络中还有发出的重传报文,不进行拥塞窗口的撤销操作,而是进行拥塞窗口调整  1 -----      函数结束处理,等待重传报文被确认
            if (tp->retrans_out) {
                tcp_cwnd_reduction(sk, prior_unsacked, 0, flag);
                return true;
            }
    
            if (!tcp_any_retrans_done(sk))
                tp->retrans_stamp = 0;//变量retrans_stamp记录了第一个重传报文的时间戳,如果已经没有了重传报文,清零此时间戳
    
            DBGUNDO(sk, "partial recovery");
            // 网络中没有重传的报文or发送skb缓存中没有报文是被重传过  ------ 则 取消所哟报文lost 状态以及调整拥塞窗口阈值等
            tcp_undo_cwnd_reduction(sk, true);
            NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPPARTIALUNDO);
            //尝试进入TCP_CA_Open状态,但是,如果套接口还有乱序报文或者丢失报文,将进入TCP_CA_Disorder拥塞状态。
            tcp_try_keep_open(sk);
            return true;
        }
        return false;
    }

     上述情况存在尝试进入TCP_CA_Open状态,但是,如果套接口还有乱序报文或者丢失报文,将进入TCP_CA_Disorder拥塞状态。

    撤销TCP_CA_Recovery状态三(dsack)

    对于处在TCP_CA_Recovery拥塞状态的套接口,ACK报文并没有推进SND.UNA序号,或者在partial-undo未执行的情况下,尝试进行DSACK相关的撤销操作,由函数tcp_try_undo_dsack完成。

    /* Try to undo cwnd reduction, because D-SACKs acked all retransmitted data 
    发送方在探测到一个D-SACK块时,可使undo_retrans减一。如果D-SACK块最终确认了在最近窗口
    中的每个不必要的重传,重传计数器因为D-SACK降为0,发送方增大拥塞窗口,恢复最新一次对ssthresh的修改。
    */
    static bool tcp_try_undo_dsack(struct sock *sk)
    {
        struct tcp_sock *tp = tcp_sk(sk);
    //如果undo_marker有值,并且undo_retrans为零,表明所有的重传报文都被D-SACK所确认,即重传是不必要的,执行拥塞窗口恢复操作
        if (tp->undo_marker && !tp->undo_retrans) {
            DBGUNDO(sk, "D-SACK");
            tcp_undo_cwnd_reduction(sk, false);
            NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPDSACKUNDO);
            return true;
        }
        return false;
    }

    撤销TCP_CA_Loss状态

    /* Process an ACK in CA_Loss state. Move to CA_Open if lost data are
     * recovered or spurious. Otherwise retransmits more on partial ACKs.
     如果ACK报文推进了SND.UNA序号,尝试进行TCP_CA_Loss状态撤销,由函数tcp_try_undo_loss完成。
     对于FRTO,如果S/ACK确认了并没有重传的报文(原始报文),同样尝试进入撤销流程,
     因为此ACK报文表明RTO值设置的不够长(并非拥塞导致报文丢失),过早进入了TCP_CA_Loss状态。
     */
    static void tcp_process_loss(struct sock *sk, int flag, bool is_dupack)
    {
        struct tcp_sock *tp = tcp_sk(sk);
        bool recovered = !before(tp->snd_una, tp->high_seq); //SND.UNA不在high_seq之前,表明恢复流程已经结束
    
        if ((flag & FLAG_SND_UNA_ADVANCED) &&
            tcp_try_undo_loss(sk, false))//如果ACK报文推进了SND.UNA序号,尝试使用tcp_try_undo_loss进行TCP_CA_Loss状态撤销
            return;
    
        if (tp->frto) { /* F-RTO RFC5682 sec 3.1 (sack enhanced version). */
            /* Step 3.b. A timeout is spurious if not all data are
             * lost, i.e., never-retransmitted data are (s)acked.
             如果S/ACK确认了并没有重传的报文(原始报文),同样尝试进入撤销流程,因为此ACK报文表明RTO值设置的不够长(并非拥塞导致报文丢失),
             过早进入了TCP_CA_Loss状态。       */
            if ((flag & FLAG_ORIG_SACK_ACKED) &&
                tcp_try_undo_loss(sk, true))
                return;
    
            if (after(tp->snd_nxt, tp->high_seq)) {
                if (flag & FLAG_DATA_SACKED || is_dupack)
                    tp->frto = 0; /* Step 3.a. loss was real  loss 状态是真的 不是误判*/
            } else if (flag & FLAG_SND_UNA_ADVANCED && !recovered) {
                tp->high_seq = tp->snd_nxt;
                __tcp_push_pending_frames(sk, tcp_current_mss(sk),
                              TCP_NAGLE_OFF);
                if (after(tp->snd_nxt, tp->high_seq))
                    return; /* Step 2.b */
                tp->frto = 0;
            }
        }
    
        if (recovered) {//为此ACK报文表明RTO值设置的不够长(并非拥塞导致报文丢失),过早进入了TCP_CA_Loss状态。
            /* F-RTO RFC5682 sec 3.1 step 2.a and 1st part of step 3.a */
            tcp_try_undo_recovery(sk);
            return;
        }
        if (tcp_is_reno(tp)) {
            /* A Reno DUPACK means new data in F-RTO step 2.b above are
             * delivered. Lower inflight to clock out (re)tranmissions.
             */
            if (after(tp->snd_nxt, tp->high_seq) && is_dupack)
                tcp_add_reno_sack(sk);
            else if (flag & FLAG_SND_UNA_ADVANCED)
                tcp_reset_reno_sack(tp);
        }
        tcp_xmit_retransmit_queue(sk);
    }
    http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!! 但行好事 莫问前程 --身高体重180的胖子
  • 相关阅读:
    Flush the AOS cache from code
    EntityConnectionStringBuilder 构造EF连接字符串
    AX中文转拼音
    AX2012 AOT中Web部署显示二级以上菜单
    clearCompanyCache
    AX2009 打印到PDF优化
    AX ODBC读取其他SQL数据库服务器数据
    AX2009报表打印固定长度Barcode条码
    Create Product Variant
    Rename AOT Object
  • 原文地址:https://www.cnblogs.com/codestack/p/15571893.html
Copyright © 2011-2022 走看看