TCP定时器 之 坚持定时器

坚持定时器在接收方通告接收窗口为0,阻止发送端继续发送数据时设定。

由于连接接收端的发送窗口通告不可靠(只有数据才会确认,ACK不会确认),如果一个确认丢失了,双方就有可能因为等待对方而使连接终止:接收放等待接收数据(因为它已经向发送方通过了一个非0窗口),而发送方在等待允许它继续发送数据的窗口更新。

为了防止上面的情况,发送方在接收到0窗口通告后,启动一个坚持定时器来周期的发送1字节的数据,以便发现接收方窗口是否已经增大。这些从发送方发出的报文段称为窗口探测;

下面来分析坚持定时器的实现代码:

启动定时器:

通过inet_csk_reset_xmit_timer来启动坚持定时器,其类型设置为ICSK_TIME_PROBE0,其最终通过icsk_retransmit_timer重传定时器来实现,只是通过类型来区分当前使用的是哪种定时器,以及超时时需要分给哪个对应类型的回调;

1 inet_csk_reset_xmit_timer(sk, ICSK_TIME_PROBE0, when, TCP_RTO_MAX);

 

 1 /*
 2  *    Reset the retransmission timer
 3  */
 4 static inline void inet_csk_reset_xmit_timer(struct sock *sk, const int what,
 5                          unsigned long when,
 6                          const unsigned long max_when)
 7 {
 8     struct inet_connection_sock *icsk = inet_csk(sk);
 9 
10     if (when > max_when) {
11         when = max_when;
12     }
13 
14     if (what == ICSK_TIME_RETRANS || what == ICSK_TIME_PROBE0 ||
15         what == ICSK_TIME_EARLY_RETRANS || what == ICSK_TIME_LOSS_PROBE ||
16         what == ICSK_TIME_REO_TIMEOUT) {
17         icsk->icsk_pending = what;
18         icsk->icsk_timeout = jiffies + when;
19         sk_reset_timer(sk, &icsk->icsk_retransmit_timer, icsk->icsk_timeout);
20     } else if (what == ICSK_TIME_DACK) {
21         icsk->icsk_ack.pending |= ICSK_ACK_TIMER;
22         icsk->icsk_ack.timeout = jiffies + when;
23         sk_reset_timer(sk, &icsk->icsk_delack_timer, icsk->icsk_ack.timeout);
24     }
25 
26 }

 

定时器回调函数:

因为是共用重传定时器,所以超时会进入到超时定时器的处理流程,在进入tcp_write_timer_handler后才会根据定时器的类型来区分出该定时器是坚持定时器,进而调用tcp_probe_timer来进行对应的处理;

 1 static void tcp_write_timer(unsigned long data)
 2 {
 3     struct sock *sk = (struct sock *)data;
 4 
 5     bh_lock_sock(sk);
 6     /* 没被用户系统调用锁定 */
 7     if (!sock_owned_by_user(sk)) {
 8         /* 调用超时处理函数 */
 9         tcp_write_timer_handler(sk);
10     } else {
11         /* delegate our work to tcp_release_cb() */
12         /* 交给tcp_release_cb处理 */
13         if (!test_and_set_bit(TCP_WRITE_TIMER_DEFERRED, &sk->sk_tsq_flags))
14             sock_hold(sk);
15     }
16     bh_unlock_sock(sk);
17     sock_put(sk);
18 }

 

 1 /* Called with bottom-half processing disabled.
 2    Called by tcp_write_timer() */
 3 void tcp_write_timer_handler(struct sock *sk)
 4 {
 5     struct inet_connection_sock *icsk = inet_csk(sk);
 6     int event;
 7 
 8     /* 连接处于CLOSE或者LISTEN状态或者 没有指定待处理事件类型 */
 9     if (((1 << sk->sk_state) & (TCPF_CLOSE | TCPF_LISTEN)) ||
10         !icsk->icsk_pending)
11         goto out;
12 
13     /* 超时时间未到,则重新设置定时器超时时间 */
14     if (time_after(icsk->icsk_timeout, jiffies)) {
15         sk_reset_timer(sk, &icsk->icsk_retransmit_timer, icsk->icsk_timeout);
16         goto out;
17     }
18 
19     /* 获取事件类型 */
20     event = icsk->icsk_pending;
21 
22     switch (event) {
23     case ICSK_TIME_REO_TIMEOUT:
24         tcp_rack_reo_timeout(sk);
25         break;
26     case ICSK_TIME_LOSS_PROBE:
27         tcp_send_loss_probe(sk);
28         break;
29     /* 重传定时器重传 */
30     case ICSK_TIME_RETRANS:
31         icsk->icsk_pending = 0;
32         tcp_retransmit_timer(sk);
33         break;
34     /* 坚持定时器探测0窗口 */
35     case ICSK_TIME_PROBE0:
36         icsk->icsk_pending = 0;
37         tcp_probe_timer(sk);
38         break;
39     }
40 
41 out:
42     sk_mem_reclaim(sk);
43 }

 

tcp_probe_timer函数主要对是否发送探测进行各种情况的检查,包括无需额外探测,时间戳检查,重试次数检查,孤儿套接字资源检查等,检查失败则关闭连接,成功则发送探测数据段;

 1 static void tcp_probe_timer(struct sock *sk)
 2 {
 3     struct inet_connection_sock *icsk = inet_csk(sk);
 4     struct tcp_sock *tp = tcp_sk(sk);
 5     int max_probes;
 6     u32 start_ts;
 7 
 8     /* 
 9         (1)如果存在发送出去未被确认的段,
10         要么被确认返回窗口,要么重传,无需额外构造探测包
11         (2)或者发送队列没有待发送的段,无数据需要发,
12         不关心窗口情况
13         则无需另外组织探测数据
14     */
15     if (tp->packets_out || !tcp_send_head(sk)) {
16         /* 探测次数清零 */
17         icsk->icsk_probes_out = 0;
18         return;
19     }
20 
21     /* RFC 1122 4.2.2.17 requires the sender to stay open indefinitely as
22      * long as the receiver continues to respond probes. We support this by
23      * default and reset icsk_probes_out with incoming ACKs. But if the
24      * socket is orphaned or the user specifies TCP_USER_TIMEOUT, we
25      * kill the socket when the retry count and the time exceeds the
26      * corresponding system limit. We also implement similar policy when
27      * we use RTO to probe window in tcp_retransmit_timer().
28      */
29     /* 待发送数据的时间戳 */
30     start_ts = tcp_skb_timestamp(tcp_send_head(sk));
31     /* 时间戳为0,设置一下 */
32     if (!start_ts)
33         skb_mstamp_get(&tcp_send_head(sk)->skb_mstamp);
34     /* 有时间戳则判断是否超过了用户设置时间 */
35     else if (icsk->icsk_user_timeout &&
36          (s32)(tcp_time_stamp - start_ts) > icsk->icsk_user_timeout)
37         goto abort;
38 
39     /* 最大探测次数设置为连接状态的重试次数 */
40     max_probes = sock_net(sk)->ipv4.sysctl_tcp_retries2;
41     /* 套接口即将关闭 */
42     if (sock_flag(sk, SOCK_DEAD)) {
43         /* 退避指数计算的超时时间< 最大时间(RTT),大于数据无法返回了 */
44         const bool alive = inet_csk_rto_backoff(icsk, TCP_RTO_MAX) < TCP_RTO_MAX;
45 
46         /* 获取在本端关闭tcp前重试次数上限 */
47         max_probes = tcp_orphan_retries(sk, alive);
48 
49         /* 超过了最大RTO时间或者退避指数达到了探测最大次数 */
50         if (!alive && icsk->icsk_backoff >= max_probes)
51             goto abort;
52 
53         /* 超过资源限制,关闭了连接,无需发送 */
54         if (tcp_out_of_resources(sk, true))
55             return;
56     }
57 
58     /* 探测次数超过了最大探测次数,错误处理,关闭连接 */
59     if (icsk->icsk_probes_out > max_probes) {
60 abort:        tcp_write_err(sk);
61     } else {
62         /* Only send another probe if we didn't close things up. */
63         /* 发送探测 */
64         tcp_send_probe0(sk);
65     }
66 }

 

tcp_send_probe0调用tcp_write_wakeup发送探测段,并且根据发送结果来设定不同的退避指数,探测次数,下一次探测时间等,并重置定时器;

 1 /* A window probe timeout has occurred.  If window is not closed send
 2  * a partial packet else a zero probe.
 3  */
 4 void tcp_send_probe0(struct sock *sk)
 5 {
 6     struct inet_connection_sock *icsk = inet_csk(sk);
 7     struct tcp_sock *tp = tcp_sk(sk);
 8     struct net *net = sock_net(sk);
 9     unsigned long probe_max;
10     int err;
11 
12     /* 发送探测报文 */
13     err = tcp_write_wakeup(sk, LINUX_MIB_TCPWINPROBE);
14 
15     /* 
16         (1)如果存在发送出去未被确认的段,
17         要么被确认返回窗口,要么重传,无需额外构造探测包
18         (2)或者发送队列没有待发送的段,无数据需要发,
19         不关心窗口情况
20         则无需另外组织探测数据
21     */    
22     if (tp->packets_out || !tcp_send_head(sk)) {
23         /* Cancel probe timer, if it is not required. */
24         icsk->icsk_probes_out = 0;
25         icsk->icsk_backoff = 0;
26         return;
27     }
28 
29     /* 发送成功或者非本地拥塞导致的失败 */
30     if (err <= 0) {
31         /* 退避指数未达到重试上限,递增 */
32         if (icsk->icsk_backoff < net->ipv4.sysctl_tcp_retries2)
33             icsk->icsk_backoff++;
34         /* 探测次数递增 */
35         icsk->icsk_probes_out++;
36         /* 探测时间设置为rto_max */
37         probe_max = TCP_RTO_MAX;
38     } else {
39         /* If packet was not sent due to local congestion,
40          * do not backoff and do not remember icsk_probes_out.
41          * Let local senders to fight for local resources.
42          *
43          * Use accumulated backoff yet.
44          */
45         /* 本地拥塞发送失败 */
46 
47         /* 设置探测次数 */
48         if (!icsk->icsk_probes_out)
49             icsk->icsk_probes_out = 1;
50 
51         /* 设置探测时间为probe_interval */
52         probe_max = TCP_RESOURCE_PROBE_INTERVAL;
53     }
54 
55     /* 重置探测定时器 */
56     inet_csk_reset_xmit_timer(sk, ICSK_TIME_PROBE0,
57                   tcp_probe0_when(sk, probe_max),
58                   TCP_RTO_MAX);
59 }

 

posted @ 2019-10-27 22:17  AlexAlex  阅读(999)  评论(0编辑  收藏  举报