• 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 }
  • 相关阅读:
    二分查找
    django 中间件
    logging 模块
    linux ssh keys
    spark(一) build
    hadoop中遇到的问题。
    算法----字符串拷贝
    phpmailer 实现发送邮件
    thinkphp操作数据库
    thinkphp 使用过程中遇到的一个小函数
  • 原文地址:https://www.cnblogs.com/wanpengcoder/p/11749461.html
Copyright © 2020-2023  润新知