TCP的ACK确认系列 — 快速确认

快速确认模式

 

(1) 进入快速确认模式

设置快速确认模式标志,设置在快速确认模式中可以发送的ACK数量。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. static void tcp_enter_quickack_mode (struct sock *sk)  
  2. {  
  3.     struct inet_connection_sock *icsk = inet_csk(sk);  
  4.   
  5.     tcp_incr_quickack(sk); /* 设置在快速确认模式中可以发送的ACK数量 */  
  6.     icsk->icsk_ack.pingpong = 0/* 快速确认模式的标志 */  
  7.     icsk->icsk_ack.ato = TCP_ATO_MIN; /* ACK超时时间 */  
  8. }  

在快速确认模式中,可以发送的ACK数量是有限制的,具体额度为icsk->icsk_ack.quick。

所以进入快速确认模式时,需要设置可以快速发送的ACK数量,一般允许快速确认半个接收窗口的数据量,

但最多不能超过16个,最少为2个。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. static void tcp_incr_quickack (struct sock *sk)  
  2. {  
  3.     struct inet_connection_sock *icsk = inet_csk(sk);  
  4.   
  5.     /* 可以快速确认半个接收窗口的数据量 */  
  6.     unsigned int quickacks = tcp_sk(sk)->rcv_wnd / (2 * icsk->icsk_ack.rcv_mss);  
  7.   
  8.     if (quickacks == 0)  
  9.         quckacks = 2/* 最少为2个 */  
  10.   
  11.     if (quickacks > icsk->icsk_ack.quick)  
  12.         icsk->icsk_ack.quick = min(quickacks, TCP_MAX_QUICKACKS); /* 最多不能超过16个 */  
  13. }  
  14.   
  15. /* Maximal number of ACKs sent quickly to accelerate slow-start. */  
  16. #define TCP_MAX_QUICKACKS 16U  

 

(2) 检查是否处于快速确认模式。

如果设置了快速确认标志,且快速确认模式中可以发送的ACK数量不为0,就判断连接处于快速确认模式中,

允许立即发送ACK。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /* Send ACKs quickly, if "quick" count is not exhausted and the session is not interactive. */  
  2. static inline bool tcp_in_quickack_mode (const struct sock *sk)  
  3. {  
  4.     const struct inet_connectionsock *icsk = inet_csk(sk);  
  5.   
  6.     /* 如果快速确认模式中可以发送的ACK数量不为0,且设置了快速确认标志 */  
  7.     return icsk->icsk_ack.quick && ! icsk->icsk_ack.pingpong;  
  8. }  

 

快速ACK的发送

 

在tcp_rcv_established()中,如果没有通过TCP的首部预测,就会执行慢速路径来处理接收到的报文。

处理完接收到的报文之后,会调用tcp_data_snd_check()来检查是否需要发送数据,以及是否需要扩大发送缓存。

然后调用tcp_ack_snd_check()来检查是否需要发送ACK,以及是使用快速确认还是延迟确认。

同样的在通过TCP首部预测的快速路径中,也会调用__tcp_ack_snd_check()来发送快速确认或延迟确认。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. static inline void tcp_ack_snd_check(struct sock *sk)  
  2. {  
  3.     /* 如果没有ACK需要发送 */  
  4.     if (! inet_csk_ack_scheduled(sk)) {  
  5.         /* We sent a data segment already. */  
  6.         return;  
  7.     }  
  8.   
  9.     __tcp_ack_snd_check(sk, 1); /* 决定要发送快速确认还是延迟确认 */  
  10. }  

 

如果此时符合以下任一条件,可以立即发送ACK,即进行快速确认:

1. 接收缓冲区中有一个以上的全尺寸数据段仍然是NOT ACKed,并且接收窗口变大了。

    所以一般收到了两个数据包后,会发送ACK,而不是对每个数据包都进行确认。

2.  此时处于快速确认模式中。

3. 乱序队列不为空。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /* Check if sending an ack is needed. */  
  2.   
  3. static void __tcp_ack_snd_check (struct sock *sk, int ofo_possible)  
  4. {  
  5.     struct tcp_sock *tp = tcp_sk(sk);  
  6.   
  7.     /* 符合以下任一条件,可以立即发送ACK: 
  8.      * 1. 接收缓冲区中有一个以上的全尺寸数据段仍然是NOT ACKed,并且接收窗口变大了。 
  9.      * 2.  此时处于快速确认模式中。 
  10.      * 3. 乱序队列不为空。 
  11.      */  
  12.     /* More than one full frame received... */  
  13.     if (((tp->rcv_nxt - tp->rcv_wup) > inet_csk(sk)->icsk_ack.rcv_mss &&   
  14.         /* ... and right edge of window advances far enough. 
  15.          * (tcp_recvmsg() will send ACK otherwise). Or ... 
  16.          */  
  17.          __tcp_select_window(sk) >= tp->rcv_wnd) ||   
  18.   
  19.          /* We ACK each frame or ... */  
  20.          tcp_in_quickack_mode(sk) ||  
  21.   
  22.          /* We have out of order data. */  
  23.          (ofo_possible && skb_peek(&tp->out_of_order_queue))) {  
  24.   
  25.         /* Then ack it now. */  
  26.         tcp_send_ack(sk); /* 立即发送ACK */  
  27.   
  28.     } else {  
  29.         /* Else, send delayed ack. */  
  30.         tcp_send_delayed_ack(sk); /* 延迟ACK的发送,见下一篇blog:) */  
  31.     }  
  32. }  

 

ACK的发送函数为tcp_send_ack(),如果发送失败会启动ACK延迟定时器。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /* This routine sends an ack and also updates the window. */  
  2.   
  3. void tcp_send_ack (struct sock *sk)  
  4. {  
  5.     struct sk_buff *buff;  
  6.   
  7.     /* If we have been reset, we may not send again. */  
  8.     if (sk->sk_state == TCP_CLOSE)  
  9.         return;  
  10.   
  11.     /* We are not putting this on the write queue, so tcp_transmit_skb() 
  12.      * will set the ownership to this sock. 
  13.      */  
  14.     buff = alloc_skb(MAX_TCP_HEADER, sk_gfp_atomic(sk, GFP_ATOMIC));  
  15.   
  16.     if (buff == NULL) { /* 分配skb失败 */  
  17.         inet_csk_schedule_ack(sk); /* 设置标志位,表示有ACK需要发送 */  
  18.         inet_csk(sk)->icsk_ack.ato = TCP_ATO_MIN; /* 重置ATO */  
  19.   
  20.         /* 设置延迟确认定时器,超时时间为200ms */  
  21.         inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK, TCP_DELACK_MAX, TCP_RTO_MAX);  
  22.         return;  
  23.     }  
  24.   
  25.     /* Reserve space for headers and prepare control bits. */  
  26.     skb_reserve(buff, MAX_TCP_HEADER); /* 设置报文头部的空间 */  
  27.   
  28.     /* 初始化不携带数据的skb的一些控制字段 */  
  29.     tcp_init_nondata_skb(buff, tcp_acceptable_seq(sk), TCPHDR_ACK);  
  30.   
  31.     /* Send it off, this clears delayed acks for us. */  
  32.     TCP_SKB_CB(buff)->when = tcp_time_stamp;  
  33.   
  34.     tcp_transmit_skb(sk, buff, 0, sk_gfp_atomic(sk, GFP_ATOMIC));  
  35. }  
  36.   
  37. /* 设置标志位,表示有ACK需要发送。*/  
  38. static inline void inet_csk_schedule_ack (struct sock *sk)  
  39. {  
  40.     inet_csk(sk)->icsk_ack.pending |= ICSK_ACK_SCHED;  
  41. }  
  42.   
  43. /* Maximal time to delay before sending an ACK. 
  44.  * Delayed ACK的最大延迟时间,一般为200ms 
  45.  */  
  46. #define TCP_DELACK_MAX ((unsigned) (HZ/5))  
  47.   
  48. /* Delayed ACK的最小延迟时间,一般为40ms */  
  49. #define TCP_DELACK_MIN ((unsigned) (HZ/25))  

 

TCP_QUICKACK选项

 

TCP_QUICKACK用于让本端立即发送ACK,而不进行延迟确认。

需要注意的是,这个选项并不是持久的,之后还是有可能进入延迟确认模式的。

所以如果需要一直进行快速确认,要在每次调用接收函数后都进行选项设置。

 

int quickack = 1; /* 启用快速确认,如果赋值为0表示使用延迟确认 */

setsockopt(fd, SOL_TCP, TCP_QUICKACK, &quickack, sizeof(quickack));

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #define TCP_QUICKACK 12 /* Block / reenable quick acks */  
  2.   
  3. static int do_tcp_setsockopt(struct sock *sk, int level, int optname, char __user *optval,  
  4.     unsigned int optlen)  
  5. {  
  6.     ...  
  7.     case TCP_QUICKACK:  
  8.         if (! val) {  
  9.             icsk->icsk_ack.pingpong = 1/* 禁用快速确认模式 */  
  10.   
  11.         } else {  
  12.             icsk->icsk_ack.pingpong = 0/* 启用快速确认模式 */  
  13.   
  14.             /* 如果当前有ACK需要发送,就立即发送 */  
  15.             if (1 << sk->sk_state) & (TCPF_ESTABLISHED | TCPF_CLOSE_WAIT) &&   
  16.                 inet_csk_ack_scheduled(sk)) {  
  17.   
  18.                 icsk->icsk_ack.pending |= ICSK_ACK_PUSHED; /* 允许在快速模式中立即发送 */  
  19.   
  20.                 /* 通常当接收队列中有数据复制到用户空间时,会调用此函数来判断是否需要立即发送ACK。 
  21.                  * 这里的用法比较特殊,由于设置了ICSK_ACK_PUSHED标志,且处于快速确认模式中, 
  22.                  * 必然会立即发送ACK。 
  23.                  */  
  24.                 tcp_cleanup_rbuf(sk, 1);               
  25.   
  26.                 /* 如果选项的值为偶数,那么立即退出快速确认模式。 
  27.                  * 原来选项值不限于0和1,还分奇偶的:) 
  28.                  */  
  29.                 if (! (val & 1))   
  30.                     icsk->icsk_ack.pingpong = 1;  
  31.             }  
  32.         }  
  33.         break;  
  34.         ...  
  35. }  

 

当接收队列中有数据复制到用户空间时,会调用tcp_cleanup_rbuf()来判断是否要立即发送ACK。

 

(1) 如果现在有ACK需要发送,满足以下条件之一,就可以立即发送:

1. icsk->icsk_ack.blocked为1,之前有Delayed ACK被用户进程阻塞了。

2. 接收缓冲区中有一个以上的全尺寸数据段仍然是NOT ACKed (所以经常是收到2个全尺寸段后发送ACK)

3. 本次复制到用户空间的数据量大于0,且满足以下条件之一:

    3.1 设置了ICSK_ACK_PUSHED2标志

    3.2 设置了ICSK_ACK_PUSHED标志,且处于快速确认模式中

 

(2) 如果原来没有ACK需要发送,但是现在的接收窗口显著增大了,也需要立即发送ACK通知对端。

这里的显著增大是指:新的接收窗口大小不为0,且比原来接收窗口的剩余量增大了一倍。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /* Clean up the receive buffer for full frames taken by the user, 
  2.  * then send an ACK if necessary. COPIED is the number of bytes 
  3.  * tcp_recvmsg has given to the user so far, it speeds up the calculation 
  4.  * of whether or not we must ACK for the sake of a window update. 
  5.  */  
  6.   
  7. void tcp_cleanup_rbuf (struct sock *sk, int copied)  
  8. {  
  9.     struct tcp_sock *tp = tcp_sk(sk);  
  10.     bool time_to_ack = false;  
  11.   
  12.     /* 获取接收队列的头一个数据段 */  
  13.     struct sk_buff *skb = skb_peek(&sk->sk_receive_queue);  
  14.   
  15.     /* copied_seq: Head of yet unread data,应用程序下次从这里开始复制数据。 
  16.      * 这里检查在发送队列中,已复制到用户空间的数据段是否被清理了。 
  17.      */  
  18.     WARN(skb && !before(tp->copied_seq, TCP_SKB_CB(skb)->end_seq),   
  19.         "cleanup rbuf bug: copied %X seq %X rcvnxt %X\n", tp->copied_seq,  
  20.         TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt);  
  21.   
  22.     /* 如果现在有ACK需要发送,满足以下条件之一则可以立即发送 */  
  23.     if (inet_csk_ack_scheduled(sk)) {  
  24.         const struct inet_connection_sock *icsk = inet_csk(sk);  
  25.   
  26.         /* 1. Delayed ACKs frequently hit locked sockets during bulk receive. 
  27.          * 2. Once-per-two-segments ACK was not sent by tcp_input.c. 
  28.          * 3. copied >0 and ICSK_ACK_PUSHED2 set. 
  29.          * 4. copied > 0 and ICSK_ACK_PUSHED and in quickack mode. 
  30.          */  
  31.         if (icsk->icsk_ack.blocked || tp->rcv_nxt - tp->rcv_wup > icsk->icsk_ack.rcv_mss ||  
  32.             (copied > 0 && ((icsk->icsk_ack.pending & ICSK_ACK_PUSHED2) ||   
  33.              ((icsk->icsk_ack.pending & ICSK_ACK_PUSHED) && ! icsk->icsk_ack.pingpong)) &&  
  34.                ! atomic_read(&sk->sk_rmem_alloc)))  
  35.             time_to_ack = true;  
  36.     }   
  37.   
  38.     /* We send an ACK if we can now advertise a non-zero window which has 
  39.      * been raised "significantly" - at least twice bigger. 
  40.      * Even if window raised up to infinity, do not send window open ACK in states, 
  41.      * where we will not receive more. It is useless. 
  42.      */  
  43.     if (copied > 0 && ! time_to_ack && ! (sk->sk_shutdown & RCV_SHUTDOWN)) {  
  44.         __u32 rcv_window_now = tcp_receive_window(tp); /* 当前接收窗口的剩余量 */  
  45.   
  46.         /* 如果当前接收窗口的剩余量小于最大值的一半 */  
  47.         if (2 * rcv_window_now <= tp->window_clamp) {  
  48.   
  49.            /* 根据剩余的接收缓存,计算新的接收窗口的大小。 
  50.             * 因为这次复制,很可能空出不少接收缓存,所以新的接收窗口也会相应增大。 
  51.             */  
  52.             __u32 new_window = __tcp_select_window(sk);  
  53.   
  54.            /* Send ACK now, if this read freed lots of space in our buffer. 
  55.             * Certainly, new_window is new window. 
  56.             * We can advertise it now, if it is not less than current one. 
  57.             * "Lots" means "at least twice" here. 
  58.             */  
  59.   
  60.             /* 如果新的接收窗口不为0,且比原来接收窗口的剩余量大了一倍以上,就说接收窗口显著增大了。 
  61.              * 而当接收窗口显著增大时,也需要立即发送ACK告知对端。 
  62.              */  
  63.             if (new_window && new_window >= 2 * rcv_window_now)  
  64.                 time_to_ack = true;  
  65.         }  
  66.     }  
  67.   
  68.     if (time_to_ack)  
  69.         tcp_send_ack(sk); /* 发送ACK给对端 */  
  70. }  
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /* 计算当前接收窗口的剩余量 */  
  2. /* Compute the actual receive window we are currently advertising. 
  3.  * Rcv_nxt can be after the window if our peer push more data than 
  4.  * the offered window. 
  5.  */  
  6. static inline u32 tcp_receive_window (const struct tcp_sock *tp)  
  7. {  
  8.     s32 win = tp->rcv_wup + tp->rcv_wnd - tp->rcv_nxt;  
  9.   
  10.     if (win < 0)  
  11.         win = 0;  
  12.   
  13.     return (u32) win;  
  14. }  

猜你喜欢

转载自blog.csdn.net/bin_linux96/article/details/53175475