zoukankan      html  css  js  c++  java
  • TCP的核心系列 — ACK的处理(二)

    本文主要内容:tcp_ack()中的一些细节,如发送窗口的更新、持续定时器等。

    内核版本:3.2.12

    Author:zhangskd @ csdn

    发送窗口的更新

    什么时候需要更新发送窗口呢?

    (1)确认了新的数据

    (2)条件1不成立,ACK段的序号是最新的。

              这表示虽然ACK段没有确认了新的数据,但是它携带了新数据。

    (3)条件1和2都不成立,通告窗口变大。

              ACK既没有确认了新的数据,序号也不是最新的。

              虽然如此,但是如果对端的接收窗口变大,我们还是要更新发送窗口。

              此时ack_seq必须等于snd_wl1,而不能小于,因为那可能是乱序的。

    判断是否要更新发送窗口:

    /* Check that window update is acceptable.
     * The function assumes that snd_una <= ack <= snd_nxt.
     * ack和ack_seq分别表示ACK段的确认序号和序号,不要混淆了:)
     */
    static inline int tcp_may_update_window(const struct tcp_sock *tp, const u32 ack,
                            const u32 ack_seq, const u32 nwin)
    {
        return after(ack, tp->snd_una) || after(ack_seq, tp->snd_wl1) ||
                    (ack_seq == tp->snd_wl1 && nwin > tp->snd_wnd);
    }

    更新函数:

    /* Update our send window.
     * Window update algorithm.
     */
    static int tcp_ack_update_window(struct sock *sk, const struct sk_buff, u32 ack, u32 ack_seq)
    {
        struct tcp_sock *tp = tcp_sk(sk);
        int flag = 0;
        u32 nwin = ntohs(tcp_hdr(skb)->window); /* 通告窗口*/
        
        /* 在发送syn包时,通告窗口没有乘窗口扩大因子*/
        if (likely(! tcp_hdr(skb)->syn))
            nwin <<= tp->rx_opt.snd_wscale; /* 乘对端的窗口扩大因子*/
     
        if (tcp_may_update_window(tp, ack, ack_seq, nwin)) { /* 如果可以更新发送窗口*/
            flag |= FLAG_WIN_UPDATE; /* 设置发送窗口更新标志*/
            tcp_update_wl(tp, ack_seq); /* 记录更新发送窗口的ACK段序号*/
     
            if (tp->snd_wnd != nwin) {
                tp->snd_wnd = nwin; /* 更新发送窗口大小*/
     
                /* Note, it is the only place, where
                 * fast path is recovered for sending TCP.
                 * 首部预测标志与接收窗口大小相关,因此需要重新计算。
                 */
                tp->pred_flags = 0; /* 清零首部预测标志*/
    
                /* 检查是否能使用首部预测,如果可以则重新计算首部预测标志*/
                tcp_fast_path_check(sk);
     
                /* Update maximal window ever seen from peer */
                if (nwin > tp->max_window) {
                    tp->max_window = nwin; /* 更新见过的最大接收窗口*/
                    tcp_sync_mss(sk, inet_csk(sk)->icsk_pmtu_cookie); /* 更新MSS */
                }
            }
        }
     
        tp->snd_una = ack; /* 更新发送窗口左端*/
        return flag;
    }
    

    持续定时器

    持续定时器在对端通告接收窗口为0,阻止TCP继续发送数据时设定。由于允许TCP继续发送数据的窗口更新

    有可能丢失,因此,如果TCP有数据要发送,而对端通告窗口为0,则持续定时器启动,超时后向对端发送1

    字节的数据,以判断对端接收窗口是否已经打开。

    #define ICSK_TIME_PROBE0 3 /* Zero window probe timer */
    
    static void tcp_ack_probe(struct sock *sk)
    {
        const struct tcp_sock *tp = tcp_sk(sk);
        struct inet_connection_sock *icsk = inet_csk(sk);
     
        /* Was it a usable window open ?
         * 对端是否有足够的接收缓存,即我们能否发送一个包。
         */
        if (! after(TCP_SKB_CB(tcp_send_head(sk))->end_seq, tcp_wnd_end(tp))) {
            icsk->icsk_backoff = 0; /* 清除退避指数 */
            inet_csk_clear_xmit_timer(sk, ICSK_TIME_PROBE0); /* 清除持续定时器*/
    
            /* Socket must be waked up by subsequent tcp_data_snd_check().
             * This function is not for random using!
             */
    
        } else { /* 否则根据退避指数重置持续定时器*/
            inet_csk_reset_xmit_timer(sk, ICSK_TIME_PROBE0,
                      min(icsk->icsk_rto << icsk->icsk_backoff, TCP_RTO_MAX), TCP_RTO_MAX);
        }
    }
    

    返回发送窗口的最后一个字节序号:

    /* Returns end sequence number of the receiver's advertised window */
    static inline u32 tcp_wnd_end(const struct tcp_sock *tp)
    {
        return tp->snd_una + tp->snd_wnd;
    }
    

    flag标志

    #define FLAG_SND_UNA_ADVANCED 0x400 /* Snd_una was changed (!= FLAG_DATA_ACKED)*/
    #define FLAG_DATA 0x01 /* Incoming frame contained data. 接收的ACK段有负荷,不是纯ACK段*/
    #define FLAG_WIN_UPDATE 0x02 /* Incoming ACK was a window update. 接收的ACK段更新了发送窗口*/
    
    #define FLAG_DATA_ACKED 0x04 /* This ACK acknowledged new data.*/
    #define FLAG_SYN_ACKED 0x10 /* This ACK acnowledged SYN. */
    #define FLAG_ACKED (FLAG_DATA_ACKED | FLAG_SYN_ACKED)
    
    #define FLAG_NOT_DUP (FLAG_DATA | FLAG_WIN_UPDATE | FLAG_ACKED)
     
    #define FLAG_DATA_SACKED 0x20 /* New SACK. */
    #define FLAG_ECE 0x40 /* ECE in this ACK .*/
    #define FLAG_CA_ALERT (FLAG_DATA_SACKED | FLAG_ECE)
    
    #define FLAG_SACK_RENEGING 0x2000 /*snd_una advanced to a sacked seq */
     
    #define FLAG_SLOWPATH 0x100 /* Do not skip RFC checks for window update. 表示在慢速路径中*/
    

    sysctl_tcp_abc选项:

    “Controls Appropriate Byte Count defined in RFC3465. If set to 0 then does congestion avoid once

    per ACK. 1 is conservative value, and 2 is more aggressive. The default value is 1.”

    默认值应该是0,即对每个ACK都进行拥塞避免。

    拥塞控制

    网络中数据包个数的计算:

    /* This determines how many packets are "in the network" to the best of our knowledge.
     * In many cases it is conservative, but where detailed information is available from the
     * receiver (via SACK blocks etc.) we can make more aggressive calculations.
     *
     * Use this for decisions involving congestion control, use just
     * tp->packets_out to determine if the send queue is empty or not.
     *
     * Read this equation as:
     *    packets sent once on transmission queue MINUS
     *    packets left network, but not honestly ACKde yet PLUS
     *    packets fast retransmitted
     */
    static inline unsigned int tcp_packets_in_flight(const struct tcp_sock *tp)
    {
        return tp->packets_out - tcp_left_out(tp) + tp->retrans_out;
    }
    

    判断一个ACK是否可疑:

    返回0表示ACK正常,返回1表示可疑

    什么算是可疑的呢?

    如果已经处于拥塞状态,则会显示可疑;

    如果此ACK为拥塞信号,则会显示可疑。

    可疑的条件(或):

      (1) 不属于以下四种(FLAG_NOT_DUP):

             FLAG_DATA:接收的ACK段是负荷数据携带的

             FLAG_WIN_UPDATE:接收的ACK段更新了发送窗口

             FLAG_DATA_ACKED:接收的ACK确认了新的数据

             FLAG_SYN_ACKED:接收的ACK确认了SYN段

      (2) 属于以下两种(FLAG_CA_ALERT):

             FLAG_DATA_SACKED:是新的SACK

             FLAG_ECE:在ACK中存在ECE标志,显示收到拥塞通知

      (3) 拥塞状态不为Open

    static inline int tcp_ack_is_dubious(const struct sock *sk, const int flag)
    {
        return !(flag & FLAG_NOT_DUP) || (flag & FLAG_CA_ALERT) || 
            inet_csk(sk)->icsk_ca_state != TCP_CA_Open;
    }

    增加拥塞窗口:

    返回1表示可以增加cwnd,返回0便是不能增加。

    那么在什么情况下可以增加cwnd呢?

    下面几个条件必须同时成立才能增加cwnd:

     (1) ACK中没有ECE标志,或者,处于慢启动阶段

     (2) 不能处于Recovery、CWR状态

    static inline int tcp_may_raise_cwnd(const struct sock *sk, const int flag)
    {
        const struct tcp_sock *tp = tcp_sk(sk);
        return  ( !(flag & FLAG_ECE) || tp->snd_cwnd < tp->snd_ssthresh) &&
            !((1 << inet_csk(sk)->icsk_ca_state) & (TCPF_CA_Recovery | TCPF_CA_CWR));
    }

    拥塞窗口的AI接口:

    static void tcp_cong_avoid (struct sock *sk, u32 ack, u32 in_flight)
    {
        const struct inet_connection_sock *icsk = inet_csk(sk);
        icsk->icsk_ca_ops->cong_avoid(sk, ack, in_flight);
        tcp_sk(sk)->snd_cwnd_stamp = tcp_time_stamp;
    }
  • 相关阅读:
    ngx_lua_waf
    一致性hash算法
    BloomFilter理解
    SkipList理解
    es中的一些知识点记录
    普通类、抽象类和接口区别:
    spring中的事件 applicationevent 讲的确实不错(转)
    CMS和G1的区别,以及Parallel
    SpringBoot优化内嵌的Tomcat ---设置MaxConnections
    tomcat启动nio,apr详解以及配置
  • 原文地址:https://www.cnblogs.com/aiwz/p/6333342.html
Copyright © 2011-2022 走看看