一、delay ack机制

        就是延时发送ACK机制,起初设计这种机制是为了提高效率

        http://tools.ietf.org/html/rfc1122#section-4.2.3.2提出了这样做的理由:
        平均每个段回复的ack个数小于1,可以提高主机和网络的效率。【按1】
        给应用层更新窗口的时机并能给出及时回复【注1】
        特殊应用场景下,比如字符界面远程登录,采用delay ack能减少服务端需要发送的数据段。
        我们常用的xshell、ssh等都是这样的应用。发送端通常要发送ack、window更新、返回字符。
        将这三者可以混合到一个包中,节省发送段数,提高效率。
        此外,在多用户共享的大型主机中,delay ack能显著减少主机的处理负担,因为要处理的数据段减少了。
        但是,过多的延迟会对RTT计算和packet clocking alg造成影响。

二、delay ack实现


2.1 _tcp_ack_snd_check()——tcp_input.c,关于数据段的判断

        说了这么多废话,其实delay ack在实现中,一般体现为2个段回复一个ack。
        TCP接收处理流程tcp_input.c中,有两条处理逻辑:快速路径和慢速路径。
        这两条路径的区分和首部预测有关,后续博文会做想写解释。
        无论是快速路径还是慢速路径,都会调用 _tcp_ack_snd_check() 函数判断ack的发送时机。
        
  1. static void __tcp_ack_snd_check(struct sock *sk, int ofo_possible)
  2. {
  3.     struct tcp_sock *tp = tcp_sk(sk);

  4.      /* More than one full frame received... */
  5.     if (((tp->rcv_nxt - tp->rcv_wup) > inet_csk(sk)->icsk_ack.rcv_mss
  6.      /* ... and right edge of window advances far enough.
  7.      * (tcp_recvmsg() will send ACK otherwise). Or...
  8.      */
  9.      && __tcp_select_window(sk) >= tp->rcv_wnd) ||
  10.      /* We ACK each frame or... */
  11.      tcp_in_quickack_mode(sk) ||
  12.      /* We have out of order data. */
  13.      (ofo_possible && skb_peek(&tp->out_of_order_queue))) {
  14.         /* Then ack it now */
  15.         tcp_send_ack(sk);
  16.     } else {
  17.         /* Else, send delayed ack. */
  18.         tcp_send_delayed_ack(sk);
  19.     }
  20. }
        上一段代码摘录自linux-2.6.32.60 net/ipv4/tcp_input.c,有多个判断,我们来逐个解析:

2.1.1 多个段没有确认

        if (((tp->rcv_nxt - tp->rcv_wup) > inet_csk(sk)->icsk_ack.rcv_mss
    这一句说的意思是接收窗口中有大于一个段没有确认,通常情况下就是两个段一个ack了。
    rcv_nxt和rcv_wup两个变量在tcp.h中定义,分别表示期望接收的下一个段、上一个已经确认的段。
    u32rcv_nxt;/* What we want to receive next */
    u32rcv_wup;/* rcv_nxt on last window update sent*/
        两个序号相减,当然就表示有多少段已经接收但没有确认了。
        inet_csk(sk)->icsk_ack.rcv_mss这个值表示一个段大小。
        && __tcp_select_window(sk) >= tp->rcv_wnd)
    这一句讲的是根据空余空间算出的window大小大于等于接收窗口,即有足够的空间容纳接收的段。

2.1.2 快速确认模式

         tcp_in_quickack_mode(sk)
        在TCP进行synsent状态处理、发送dupack、接收到窗口之外的数据段、或者收到ECN标志段时,进入快速确认模式。
        持续快速确认模式的时间是有限的,大概可以连续快送发送8次ack,之后就要退出。

2.1.3 有乱序段

        /* We have out of order data. */
       (ofo_possible && skb_peek(&tp->out_of_order_queue)))
        乱序一般由丢包造成,在有丢包的情况下,网络可能已经拥塞,需要立即发送ack,通告对方降低发送速率。
        至于ofo_possible和skb_peek这些变量和函数,暂时不需要过多关注。如果有必要,后续博文中会详细说明。

2.2 延迟定时器40ms


        上面的逻辑判断,在不满足立即发送ack的条件下,会进入tcp_send_delayed_ack(sk);
    这部分代码有关于delay ack定时器的判断:如果计时器大于TCP_DELACK_MIN,更改计时器的值,后续代码会发送ack。

        

点击(此处)折叠或打开

  1. /* Send out a delayed ack, the caller does the policy checking
  2.  * to see if we should even be here. See tcp_input.c:tcp_ack_snd_check()
  3.  * for details.
  4.  */
  5. void tcp_send_delayed_ack(struct sock *sk)
  6. {
  7.     struct inet_connection_sock *icsk = inet_csk(sk);
  8.     int ato = icsk->icsk_ack.ato;
  9.     unsigned long timeout;

  10.     if (ato > TCP_DELACK_MIN) {
  11.         const struct tcp_sock *tp = tcp_sk(sk);
  12.         int max_ato = HZ / 2;

  13.         if (icsk->icsk_ack.pingpong ||
  14.          (icsk->icsk_ack.pending & ICSK_ACK_PUSHED))
  15.             max_ato = TCP_DELACK_MAX;

  16.         /* Slow path, intersegment interval is "high". */

  17.         /* If some rtt estimate is known, use it to bound delayed ack.
  18.          * Do not use inet_csk(sk)->icsk_rto here, use results of rtt measurements
  19.          * directly.
  20.          */
  21.         if (tp->srtt) {
  22.             int rtt = max(tp->srtt >> 3, TCP_DELACK_MIN);

  23.             if (rtt < max_ato)
  24.                 max_ato = rtt;
  25.         }

  26.         ato = min(ato, max_ato);
  27.     }
        ato的定义在inet_sock_conneciton.h中,是一个时钟计时器。

点击(此处)折叠或打开

  1. struct inet_connection_sock {
  2. ..........................................
  3.         __u32         ato;         /* Predicted tick of soft clock     */
        TCP_DELACK_MIN的定义看这里:

点击(此处)折叠或打开

  1. #define TCP_DELACK_MAX    ((unsigned)(HZ/5))    /* maximal time to delay before sending an ACK */
  2. #if HZ >= 100
  3. #define TCP_DELACK_MIN    ((unsigned)(HZ/25))    /* minimal time to delay before sending an ACK */
  4. #define TCP_ATO_MIN    ((unsigned)(HZ/25))
  5. #else
  6. #define TCP_DELACK_MIN    4U
  7. #define TCP_ATO_MIN    4U
  8. #endif
        关于HZ:
用来定义每一秒有几次timer interrupts。HZ通常为1000【参 1】。
        这样说来,TCP_DELACK_MIN这个值大约就是40ms了,如果HZ值变动,这个值也会改变。
        接着ato那一段代码:

点击(此处)折叠或打开

  1. /* Stay within the limit we were given */
  2.     timeout = jiffies + ato;

  3.     /* Use new timeout only if there wasn't a older one earlier. */
  4.     if (icsk->icsk_ack.pending & ICSK_ACK_TIMER) {
  5.         /* If delack timer was blocked or is about to expire,
  6.          * send ACK now.
  7.          */
  8.         if (icsk->icsk_ack.blocked ||
  9.          time_before_eq(icsk->icsk_ack.timeout, jiffies + (ato >> 2))) {
  10.             tcp_send_ack(sk);
  11.             return;
  12.         }

  13.         if (!time_before(timeout, icsk->icsk_ack.timeout))
  14.             timeout = icsk->icsk_ack.timeout;
  15.     }
         如果ack已经准备好了,定时器又快要超时,这种情况下就立即发送ack。
 

三、关闭delay ack

3.1 暴力方法    

       可以在tcp_ack_snd_check这个函数里面改,比如修改代码为任何情况下,都调用 tcp_send_ack(sk);    
        或者根据自己需要,更改判断条件。
3.2 安全的方法
        使用TCP提供的系统调用接口。
        例如,在recv系统调用后,设置TCP_QUICKACK【参 2】        

点击(此处)折叠或打开

  1. recv(fd, rcvBuf, 132, 0);
  2.   10: setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, (int[]){1}, sizeof(int));
        关于TCP_QUICKACK在TCP源码中的实现,这里不做赘述,如果必要,后续会讲。


四、杂记

        延迟确认的判断流程就是上面这些代码了,逻辑比较简单,总结起来就是普通情况下,大约两个TCP段回复一个ack,异常情况下会立即回复ack。
        除了上面基于数据段的判断,delay ack还有一个定时器,大约40ms,也就是说,在40ms内等不到数据,就立即发送ack,否则会对RTT测量和吞吐率带来负担。

        有意思的是windows协议栈 TCP compound经常是多个段回复一个ack,没有这个协议的源码,这一点暂时不讨论了。

4.1 快速ack V.S. 延迟ack,性能问题
        在协议性能需要优化时,有丢包的情况下,需要考虑启用快速ack,因为这样可以及时通知发送方丢包,避免滑动窗口停等,提升吞吐率。
        ack其实并不会对网络性能有太大的影响,delay ack能减少带宽浪费、快速ack能及时通知,意义也就仅限于此了。
        windows下,可以通过修改注册表,调整ack回复方式,以及接收窗口,这对于速度或许有40%的提升。玩网游的比较熟悉这一招。        

4.2 NAGLE算法
        delay ack和nagle算法一起使用,会引起某些问题,下一篇博文讨论nagle算法和糊涂窗口综合症。

        

注:
【1】没看懂什么叫“给应用层更新窗口的时机”。
        下一篇博客会将“何时更新窗口”,这部分资料还没吃透

按:
【1】这句话应该指的是携带确认,防止浪费带宽。延迟可以等待数据。
        比如普通tcp包头部有40Byte。ack的内容一般很少,可能几十字节。
        如果立即发送,那有效的数据长度所占比例很小,不划算。可以和数据一起发送

参:
【1】http://blog.csdn.net/bdc995/article/details/4144031 HZ定义
【2】http://blog.csdn.net/sctq8888/article/details/7398967 使用TCP_QUICKACK

12-23 21:56