在网络上很容易搜到关于延时ACK相关的信息以及触发条件,一般网上有一种说法是神秘的40ms的延时ACK反馈机制,但是在一般网上所说的都是在单独的一来一回的情景下展开的,很少有文章会讲关于客户端多个报文发送的条件下,除了40ms的延时返回外,是否还有什么机制触发延时ACK。
总体上说延时ACK发送的触发条件有两个:
(1) TCP中的延时ACK定时器触发
(2) 接收包时调用tcp_rcv_estalibshed
其中第一种情况在这里先不作讨论(都是定时器指定时间间隔触发的东西),我们讨论下tcp_rcv_estalibshed函数
int tcp_rcv_established(struct sock *sk, struct sk_buff *skb, struct tcphdr *th, unsigned len) {
struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
tp->saw_tstamp = 0;
// 触发进入条件:
// 1、预测标识一致
// 2、接收包的seq是顺序(乱序一般都是网络原因导致的)
if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pref_flags &&
TCP_SKB_CB(skb)->seq == tp->rcv_nxt) {
... ...
if (len <= tcp_header_len) {
... ...
}
else {
int eaten = 0;
// 触发进入条件:
// 1、当前进程进行的动作
// 2、将要拷贝的seq为下次接收的seq,即表明为每次都很快就将数据读取完
// 3、读取的缓存大小大于等于接收到的包的大小
if (tp->ucopy.task == current && tp->copied_seq == tp->rcv_nxt &&
len - tcp_header_len <= tp->ucopy.len && sk->lock.users) {
// 这个eaten设置为重点
eaten = 1;
NET_INC_STATS_BH(TCPHPHitsToUser);
__set_current_state(TASK_RUNNING);
if (tcp_copy_to_iovec(sk, skb, tcp_header_len))
goto csum_error;
__skb_pull(skb, tcp_header_len);
tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
}
else {
... ...
}
// 计算ACK超时(ATO)时长
tcp_event_data_recv(sk, tp, skb);
if (TCP_SKB_CB(skb)->ack_seq != tp->snd_una) {
... ...
}
if (eaten) {
if (tcp_in_quick_mode(tp))
tcp_send_ack(sk);
else
tcp_send_delayed_ack(sk);
}
else {
__tcp_ack_snd_check(sk, 0);
}
no_ack:
if (eaten)
__kfree_skb(skb);
else
sk->date_ready(sk, 0);
return 0;
}
}
slow_path:
... ...
return 0;
}
我们看到eaten为1时都会触发延时ACK的函数流程(除了QUICKACK控制外),可以看到控制eaten为1的条件是这样的:
(1) tp->ucopy.task == current --> 接收到的包跟当前调用recv对应的进程是一致的
(2) tp->copied_seq == tp->rcv_nxt --> 当前的进程每次都会把接收缓存的信息拿光(拿的很快)
(3) len - tcp_header_len <= tp->ucopy.len --> 用户层调用recv的缓存大小比实际包大小大
(4) sk->lock.users --> 调用recv就会把sk->lock.users置为1
满足了上述方面的条件后会就触发tcp_send_delayed_ack函数:
void tcp_send_delayed_ack(struct sock *sk) {
struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
int ato = tp->ack.ato;
unsigned long timeout;
if (ato > TCP_DELACK_MIN) {
int max_ato = HZ/2;
if (tp->ack.pingpong || (tp->ack.pending & TCP_ACK_PUSHED))
max_ato = TCP_DELACK_MAX;
if (tp->srtt) {
int rtt = max(tp->srtt>>3, TCP_DELACK_MIN);
if (rtt < max_ato)
max_ato = rtt;
}
ato = min(ato, max_ato);
}
timeout = jiffies + ato;
if (tp->ack.pending & TCP_ACK_TIMER) {
if (tp->ack.blocked || time_before_eq(tp->ack.timeout, jiffies + (ato>>2))) {
tcp_send_ack(sk);
return ;
}
if (!time_before(timeout, tp->ack.timeout))
timeout = tp->ack.timeout;
}
tp->ack.pending | TCP_ACK_SCHED | TCP_ACK_TIMER;
tp->ack.timeout = timeout;
if (!mod_timer(&tp->delack_timer, timeout))
sock_hold(sk);
... ...
}
这个函数重点是在触发tcp_send_ack函数(该函数会立刻发ack)的逻辑,触发条件如下:
(1) tp->ack.blocked --> 该功能会在定时ACK上会置1
(2) time_before_eq(tp->ack.timeout, jiffies + (ato>>2)) --> 已经到达超时时间
如果eaten为0时,则走__tcp_ack_snd_check函数,函数详细信息如下:
void __tcp_ack_snd_check(struct sock *sk, int ofo_possible) {
struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
if (((tp->rcv_nxt - tp->rcv_wup) > tp->ack.rcv_mss && __tcp_select_window(sk) >= tp->rcv_wnd) ||
tcp_in_quickack_mode(tp) ||
(ofo_possible && skb_peek(&tp->out_of_order_queue) != NULL)) {
tcp_send_ack(sk);
}
else {
tcp_send_delayed_ack(sk);
}
}
从中我们可以看到满足下面的几个条件的一个就会立刻触发ACK,条件如下:
(1) tp->rcv_nxt - tp->rcv_wup > tp->ack.rcv_mss --> 收到的seq跟已经确认的seq大于mss
(2) __tcp_select_window(sk) >= tp->rcv_wnd --> 新的接收窗口比旧的大
(3) tcp_in_quickack_mode(sk)
(4) ofo_possible && skb_peek(&tp->out_of_order_queue) != NULL --> seq乱序了,不过这里参数ofo_possible为0,该条件忽略
总结如下:
其实延时ACK的触发有部分是按照书上说的40ms的定时器触发的,但是还有一部分是由于函数__tcp_ack_snd_check触发ACK动作的,主要由触发ACK的点如下:
(1) tp->rcv_nxt - tp->rcv_wup > tp->ack.rcv_mss:
该条件下如果发送端发送数据很多且很频繁,那么累计到mss字节后接收端就会发ACK
(2) __tcp_select_window(sk) >= tp->rcv_wnd:
新的接收窗口大于等于旧的接收窗口,该条件下如果应用层recv接收数据及时一般都会触发的