快速确认模式
(1) 进入快速确认模式
设置快速确认模式标志,设置在快速确认模式中可以发送的ACK数量。
- static void tcp_enter_quickack_mode (struct sock *sk)
- {
- struct inet_connection_sock *icsk = inet_csk(sk);
-
- tcp_incr_quickack(sk);
- icsk->icsk_ack.pingpong = 0;
- icsk->icsk_ack.ato = TCP_ATO_MIN;
- }
在快速确认模式中,可以发送的ACK数量是有限制的,具体额度为icsk->icsk_ack.quick。
所以进入快速确认模式时,需要设置可以快速发送的ACK数量,一般允许快速确认半个接收窗口的数据量,
但最多不能超过16个,最少为2个。
- static void tcp_incr_quickack (struct sock *sk)
- {
- struct inet_connection_sock *icsk = inet_csk(sk);
-
-
- unsigned int quickacks = tcp_sk(sk)->rcv_wnd / (2 * icsk->icsk_ack.rcv_mss);
-
- if (quickacks == 0)
- quckacks = 2;
-
- if (quickacks > icsk->icsk_ack.quick)
- icsk->icsk_ack.quick = min(quickacks, TCP_MAX_QUICKACKS);
- }
-
-
- #define TCP_MAX_QUICKACKS 16U
(2) 检查是否处于快速确认模式。
如果设置了快速确认标志,且快速确认模式中可以发送的ACK数量不为0,就判断连接处于快速确认模式中,
允许立即发送ACK。
-
- static inline bool tcp_in_quickack_mode (const struct sock *sk)
- {
- const struct inet_connectionsock *icsk = inet_csk(sk);
-
-
- return icsk->icsk_ack.quick && ! icsk->icsk_ack.pingpong;
- }
快速ACK的发送
在tcp_rcv_established()中,如果没有通过TCP的首部预测,就会执行慢速路径来处理接收到的报文。
处理完接收到的报文之后,会调用tcp_data_snd_check()来检查是否需要发送数据,以及是否需要扩大发送缓存。
然后调用tcp_ack_snd_check()来检查是否需要发送ACK,以及是使用快速确认还是延迟确认。
同样的在通过TCP首部预测的快速路径中,也会调用__tcp_ack_snd_check()来发送快速确认或延迟确认。
- static inline void tcp_ack_snd_check(struct sock *sk)
- {
-
- if (! inet_csk_ack_scheduled(sk)) {
-
- return;
- }
-
- __tcp_ack_snd_check(sk, 1);
- }
如果此时符合以下任一条件,可以立即发送ACK,即进行快速确认:
1. 接收缓冲区中有一个以上的全尺寸数据段仍然是NOT ACKed,并且接收窗口变大了。
所以一般收到了两个数据包后,会发送ACK,而不是对每个数据包都进行确认。
2. 此时处于快速确认模式中。
3. 乱序队列不为空。
-
-
- static void __tcp_ack_snd_check (struct sock *sk, int ofo_possible)
- {
- struct tcp_sock *tp = tcp_sk(sk);
-
-
-
-
-
-
-
- if (((tp->rcv_nxt - tp->rcv_wup) > inet_csk(sk)->icsk_ack.rcv_mss &&
-
-
-
- __tcp_select_window(sk) >= tp->rcv_wnd) ||
-
-
- tcp_in_quickack_mode(sk) ||
-
-
- (ofo_possible && skb_peek(&tp->out_of_order_queue))) {
-
-
- tcp_send_ack(sk);
-
- } else {
-
- tcp_send_delayed_ack(sk);
- }
- }
ACK的发送函数为tcp_send_ack(),如果发送失败会启动ACK延迟定时器。
-
-
- void tcp_send_ack (struct sock *sk)
- {
- struct sk_buff *buff;
-
-
- if (sk->sk_state == TCP_CLOSE)
- return;
-
-
-
-
- buff = alloc_skb(MAX_TCP_HEADER, sk_gfp_atomic(sk, GFP_ATOMIC));
-
- if (buff == NULL) {
- inet_csk_schedule_ack(sk);
- inet_csk(sk)->icsk_ack.ato = TCP_ATO_MIN;
-
-
- inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK, TCP_DELACK_MAX, TCP_RTO_MAX);
- return;
- }
-
-
- skb_reserve(buff, MAX_TCP_HEADER);
-
-
- tcp_init_nondata_skb(buff, tcp_acceptable_seq(sk), TCPHDR_ACK);
-
-
- TCP_SKB_CB(buff)->when = tcp_time_stamp;
-
- tcp_transmit_skb(sk, buff, 0, sk_gfp_atomic(sk, GFP_ATOMIC));
- }
-
-
- static inline void inet_csk_schedule_ack (struct sock *sk)
- {
- inet_csk(sk)->icsk_ack.pending |= ICSK_ACK_SCHED;
- }
-
-
-
-
- #define TCP_DELACK_MAX ((unsigned) (HZ/5))
-
-
- #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));
- #define TCP_QUICKACK 12
-
- static int do_tcp_setsockopt(struct sock *sk, int level, int optname, char __user *optval,
- unsigned int optlen)
- {
- ...
- case TCP_QUICKACK:
- if (! val) {
- icsk->icsk_ack.pingpong = 1;
-
- } else {
- icsk->icsk_ack.pingpong = 0;
-
-
- if (1 << sk->sk_state) & (TCPF_ESTABLISHED | TCPF_CLOSE_WAIT) &&
- inet_csk_ack_scheduled(sk)) {
-
- icsk->icsk_ack.pending |= ICSK_ACK_PUSHED;
-
-
-
-
-
- tcp_cleanup_rbuf(sk, 1);
-
-
-
-
- if (! (val & 1))
- icsk->icsk_ack.pingpong = 1;
- }
- }
- break;
- ...
- }
当接收队列中有数据复制到用户空间时,会调用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,且比原来接收窗口的剩余量增大了一倍。
-
-
-
-
-
-
- void tcp_cleanup_rbuf (struct sock *sk, int copied)
- {
- struct tcp_sock *tp = tcp_sk(sk);
- bool time_to_ack = false;
-
-
- struct sk_buff *skb = skb_peek(&sk->sk_receive_queue);
-
-
-
-
- WARN(skb && !before(tp->copied_seq, TCP_SKB_CB(skb)->end_seq),
- "cleanup rbuf bug: copied %X seq %X rcvnxt %X\n", tp->copied_seq,
- TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt);
-
-
- if (inet_csk_ack_scheduled(sk)) {
- const struct inet_connection_sock *icsk = inet_csk(sk);
-
-
-
-
-
-
- if (icsk->icsk_ack.blocked || tp->rcv_nxt - tp->rcv_wup > icsk->icsk_ack.rcv_mss ||
- (copied > 0 && ((icsk->icsk_ack.pending & ICSK_ACK_PUSHED2) ||
- ((icsk->icsk_ack.pending & ICSK_ACK_PUSHED) && ! icsk->icsk_ack.pingpong)) &&
- ! atomic_read(&sk->sk_rmem_alloc)))
- time_to_ack = true;
- }
-
-
-
-
-
-
- if (copied > 0 && ! time_to_ack && ! (sk->sk_shutdown & RCV_SHUTDOWN)) {
- __u32 rcv_window_now = tcp_receive_window(tp);
-
-
- if (2 * rcv_window_now <= tp->window_clamp) {
-
-
-
-
- __u32 new_window = __tcp_select_window(sk);
-
-
-
-
-
-
-
-
-
-
- if (new_window && new_window >= 2 * rcv_window_now)
- time_to_ack = true;
- }
- }
-
- if (time_to_ack)
- tcp_send_ack(sk);
- }
-
-
-
-
-
- static inline u32 tcp_receive_window (const struct tcp_sock *tp)
- {
- s32 win = tp->rcv_wup + tp->rcv_wnd - tp->rcv_nxt;
-
- if (win < 0)
- win = 0;
-
- return (u32) win;
- }