TCP发送的数据如果丢失则快速重传算法会立即重传数据而不用等到重传定时器超时,从而快速地恢复数据。如果发送端接收不到足够数量(一般来说是3个)的ACK,则快重传算法无法起作用,这个时候就只能等待RTO超时。ER算法主要是为了解决这个问题。在下面的条件下,就会导致收不到足够的ACK:
(1)拥塞窗口比较小
(2)窗口中一个很大数量的段丢失或者在传输的结尾处发生了丢包
如果满足了上面的两个条件,那么就会发生发送端由于接收不到足够数量的ACK导致快重传算法无法生效。比如拥塞窗口是3,然后第一个段丢失了,那么理论上最多发送段只可能收到2个重复的ACK,此时由于快重传要求3个重复的ack,那么发送端将会等待RTO超时,然后重传第一个段。
在上面的第二个条件中,有两种可能性,其中ER算法是为了解决第一种可能性(也就是当连续的很多段丢失)。而第二种情况则需要TLP(Tail Loss Probe)来解决。
接下来来描述一下ER的算法。ER可以基于两种模式,一种是基于字节的,一种是基于段(segment-based)的,Linux中的ER是基于段的。ER算法会在小窗口下(flight count 小于4)减小触发快重传的重复ACK的阈值,比如减小到1或者2。而在Linux的实现中为了防止假超时会加上一个延迟再重传数据,这个功能就靠ER定时器实现。
TCP在收到ACK时会调用tcp_fastretrans_alert函数判断是否需要快速重传:
2745 static void tcp_fastretrans_alert(struct sock *sk, int pkts_acked, 2746 int prior_sacked, int prior_packets, 2747 bool is_dupack, int flag) 2748 { ... 2828 if (!tcp_time_to_recover(sk, flag)) { 2829 tcp_try_to_open(sk, flag, newly_acked_sacked); 2830 return; 2831 } ...tcp_time_to_recover函数 决定什么时候进入Recovery状态:
2076 static bool tcp_time_to_recover(struct sock *sk, int flag) 2077 { 2078 struct tcp_sock *tp = tcp_sk(sk); 2079 __u32 packets_out; 2080 2081 /* Trick#1: The loss is proven. */ 2082 if (tp->lost_out) 2083 return true; 2084 2085 /* Not-A-Trick#2 : Classic rule... */ 2086 if (tcp_dupack_heuristics(tp) > tp->reordering) 2087 return true; 2088 2089 /* Trick#3 : when we use RFC2988 timer restart, fast 2090 * retransmit can be triggered by timeout of queue head. 2091 */ 2092 if (tcp_is_fack(tp) && tcp_head_timedout(sk)) 2093 return true; 2094 2095 /* Trick#4: It is still not OK... But will it be useful to delay 2096 * recovery more? 2097 */ 2098 packets_out = tp->packets_out; 2099 if (packets_out <= tp->reordering && 2100 tp->sacked_out >= max_t(__u32, packets_out/2, sysctl_tcp_reordering) && 2101 !tcp_may_send_now(sk)) { //检查Nagle算法等 2102 /* We have nothing to send. This connection is limited 2103 * either by receiver window or by application. 2104 */ 2105 return true; 2106 } 2107 2108 /* If a thin stream is detected, retransmit after first 2109 * received dupack. Employ only if SACK is supported in order 2110 * to avoid possible corner-case series of spurious retransmissions 2111 * Use only if there are no unsent data. 2112 */ 2113 if ((tp->thin_dupack || sysctl_tcp_thin_dupack) && 2114 tcp_stream_is_thin(tp) && tcp_dupack_heuristics(tp) > 1 && 2115 tcp_is_sack(tp) && !tcp_send_head(sk)) 2116 return true; 2117 2118 /* Trick#6: TCP early retransmit, per RFC5827. To avoid spurious 2119 * retransmissions due to small network reorderings, we implement 2120 * Mitigation A.3 in the RFC and delay the retransmission for a short 2121 * interval if appropriate. 2122 */ 2123 if (tp->do_early_retrans && !tp->retrans_out && tp->sacked_out && 2124 (tp->packets_out >= (tp->sacked_out + 1) && tp->packets_out < 4) && 2125 !tcp_may_send_now(sk)) 2126 return !tcp_pause_early_retransmit(sk, flag); 2127 2128 return false; 2129 }tcp_pause_early_retransmit函数是唯一能够设置ER定时器的函数:
1947 static bool tcp_pause_early_retransmit(struct sock *sk, int flag) 1948 { 1949 struct tcp_sock *tp = tcp_sk(sk); 1950 unsigned long delay; 1951 1952 /* Delay early retransmit and entering fast recovery for 1953 * max(RTT/4, 2msec) unless ack has ECE mark, no RTT samples 1954 * available, or RTO is scheduled to fire first. 1955 */ 1956 if (sysctl_tcp_early_retrans < 2 || sysctl_tcp_early_retrans > 3 || 1957 (flag & FLAG_ECE) || !tp->srtt) 1958 return false; 1959 1960 delay = max_t(unsigned long, (tp->srtt >> 5), msecs_to_jiffies(2)); 1961 if (!time_after(inet_csk(sk)->icsk_timeout, (jiffies + delay))) <span><span class="comment">/* 如果超时重传定时器更早超时*/</span></span> 1962 return false; 1963 1964 inet_csk_reset_xmit_timer(sk, ICSK_TIME_EARLY_RETRANS, delay, 1965 TCP_RTO_MAX); 1966 return true; 1967 }由代码可知,Linux为设置ER定时器设置了重重障碍,现在来仔细数数:
(1)有丢包事件发生
(2)重复的ACK小于等于乱序的阈值
(3)未开启FACK,或没有未收到确认的包,或队列首包已发送但未超时
(4)已发送的数据 > 乱序的阈值,或被SACK段的数量小于阈值,或允许发送skb
(5)thin_dupack功能未开启,或当前链接并不是"thin"的,或重复ACK的数量大于1,或SACK未开启,或有要发送的数据
(6)do_early_retrans开启
(7)没有重传完毕但没有确认的报文
(8)有被SACK的报文
(9)在网络中的报文数量比被SACK的报文数量多至少1个
(10)在网络中的报文数量少于4
(11)现在不允许发送数据
(12)sysctl_tcp_early_retrans的值是2或3
(13)ACK中没有ECE标记
(14)tp->srtt(smoothed round trip time)的值大于0
(15)icsk->icsk_retransmit_timer超时时间在延迟时间之后
以上条件全部满足后,ER定时器会被设置,其超时时间是一个比重传定时器更小的值。
安装丢失探测定时器、重传定时器、坚持定时器时ER定时器就会被清除。
ER定时器的超时函数是tcp_resume_early_retransmit:
2961 void tcp_resume_early_retransmit(struct sock *sk) 2962 { 2963 struct tcp_sock *tp = tcp_sk(sk); 2964 2965 tcp_rearm_rto(sk); //更新RTO,设置重传定时器 2966 2967 /* Stop if ER is disabled after the delayed ER timer is scheduled */ 2968 if (!tp->do_early_retrans) //ER功能被禁用 2969 return; 2970 2971 tcp_enter_recovery(sk, false); //进入恢复状态 2972 tcp_update_scoreboard(sk, 1); //更新记分板,标记skb丢失 2973 tcp_xmit_retransmit_queue(sk); //重传数据 2974 }tcp_xmit_retransmit_queue函数执行重传数据的功能:
2447 void tcp_xmit_retransmit_queue(struct sock *sk) 2448 { ... 2457 if (!tp->packets_out) //没有在网络中的数据 2458 return; ... 2463 if (tp->retransmit_skb_hint) { 2464 skb = tp->retransmit_skb_hint; 2465 last_lost = TCP_SKB_CB(skb)->end_seq; 2466 if (after(last_lost, tp->retransmit_high)) 2467 last_lost = tp->retransmit_high; 2468 } else { 2469 skb = tcp_write_queue_head(sk); 2470 last_lost = tp->snd_una; 2471 } 2472 2473 tcp_for_write_queue_from(skb, sk) { //从skb开始遍历整个发送队列 2474 __u8 sacked = TCP_SKB_CB(skb)->sacked; 2475 2476 if (skb == tcp_send_head(sk)) 2477 break; ... 2489 if (tcp_packets_in_flight(tp) >= tp->snd_cwnd) //网络中的数据超过拥塞窗口 2490 return; ... 2523 if (sacked & (TCPCB_SACKED_ACKED|TCPCB_SACKED_RETRANS)) //skb被SACK过或被重传过 2524 continue; 2525 2526 if (tcp_retransmit_skb(sk, skb)) { //重传skb 2527 NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPRETRANSFAIL); 2528 return; 2529 } ... 2535 if (skb == tcp_write_queue_head(sk)) 2536 inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, 2537 inet_csk(sk)->icsk_rto, 2538 TCP_RTO_MAX); 2539 } ...值得注意的是在快速重传时不会重传已经被SACK过或被重传过的skb,这些skb也许能够被顺利收到,在这里不重传会减小网络拥塞。