zoukankan      html  css  js  c++  java
  • TCP定时器 之 连接建立定时器

    当服务器收到新的syn请求,会回复syn+ack给请求端,若某时间内未收到请求端回复的ack,新建连接定时器超时执行回调,重传syn+ack,当超时超过固定次数时,该连接中止;本文主要分析其初始化流程,具体的建立连接和超时重传流程在后续的文章中进行详细讨论;

    request_sock结构中的rsk_timer成员为新建连接计时器;

     1 /* struct request_sock - mini sock to represent a connection request
     2  */
     3 struct request_sock {
     4     struct sock_common        __req_common;
     5         /* 省略了一些字段 */
     6     struct timer_list        rsk_timer;
     7     const struct request_sock_ops    *rsk_ops;
     8     struct sock            *sk;
     9     /* 省略了一些字段 */
    10 };

    函数调用关系如下,其中tcp_rcv_state_process中判断标记,如果发生是设置了syn请求标记,则进入新建连接流程,在tcp_conn_request中将会新建连接请求控制块,用于跟踪完成三次握手;

    1 /**
    2  *tcp_v4_rcv
    3  * |-->tcp_v4_do_rcv
    4  *      |-->tcp_rcv_state_process /* 这里如果是syn请求,则调用下面的conn_request函数 */
    5  *             |-->tcp_v4_conn_request
    6  *                  |-->tcp_conn_request /* 这里新建请求控制块 */
    7  *                      |-->inet_csk_reqsk_queue_hash_add
    8  *                           |-->reqsk_queue_hash_req
    9  */

    启动定时器:

    reqsk_queue_hash_req函数进行连接请求定时器的设定,将req->rsk_timer的超时回调设置为reqsk_timer_handler,reqsk_timer_handler调用inet_rtx_syn_ack进行syn+ack的重传;

    其中超时时间初始化为timeout=TCP_TIMEOUT_INIT,为1HZ;timeout会随着重传的次数不断变化timeo = min(TCP_TIMEOUT_INIT << req->num_timeout, TCP_RTO_MAX);

     1 static void reqsk_queue_hash_req(struct request_sock *req,
     2                  unsigned long timeout)
     3 {
     4     req->num_retrans = 0;
     5     req->num_timeout = 0;
     6     req->sk = NULL;
     7 
     8     /* 添加定时器 */
     9     setup_pinned_timer(&req->rsk_timer, reqsk_timer_handler,
    10                 (unsigned long)req);
    11     mod_timer(&req->rsk_timer, jiffies + timeout);
    12 
    13     inet_ehash_insert(req_to_sk(req), NULL);
    14     /* before letting lookups find us, make sure all req fields
    15      * are committed to memory and refcnt initialized.
    16      */
    17     smp_wmb();
    18     atomic_set(&req->rsk_refcnt, 2 + 1);
    19 }

    定时器回调函数:

    判断是否超时次数超过阈值,是否需要重传syn+ack,如果超时或者未收到ack情况下重传失败,则取消连接;函数中还包含了对refer_accept选项的处理;

     1 /* 新建连接定时器超时处理 */
     2 static void reqsk_timer_handler(unsigned long data)
     3 {
     4     struct request_sock *req = (struct request_sock *)data;
     5     struct sock *sk_listener = req->rsk_listener;
     6     struct net *net = sock_net(sk_listener);
     7     struct inet_connection_sock *icsk = inet_csk(sk_listener);
     8     struct request_sock_queue *queue = &icsk->icsk_accept_queue;
     9     int qlen, expire = 0, resend = 0;
    10     int max_retries, thresh;
    11     u8 defer_accept;
    12 
    13     /* 不是LISTEN状态 */
    14     if (sk_state_load(sk_listener) != TCP_LISTEN)
    15         goto drop;
    16 
    17     /* 阈值设置为允许的最大重传数 */
    18     max_retries = icsk->icsk_syn_retries ? : net->ipv4.sysctl_tcp_synack_retries;
    19     thresh = max_retries;
    20     /* Normally all the openreqs are young and become mature
    21      * (i.e. converted to established socket) for first timeout.
    22      * If synack was not acknowledged for 1 second, it means
    23      * one of the following things: synack was lost, ack was lost,
    24      * rtt is high or nobody planned to ack (i.e. synflood).
    25      * When server is a bit loaded, queue is populated with old
    26      * open requests, reducing effective size of queue.
    27      * When server is well loaded, queue size reduces to zero
    28      * after several minutes of work. It is not synflood,
    29      * it is normal operation. The solution is pruning
    30      * too old entries overriding normal timeout, when
    31      * situation becomes dangerous.
    32      *
    33      * Essentially, we reserve half of room for young
    34      * embrions; and abort old ones without pity, if old
    35      * ones are about to clog our table.
    36      */
    37 
    38     /* 获取accept队列长度 */
    39     qlen = reqsk_queue_len(queue);
    40 
    41     /* 请求accept队列数> 最大未完成连接数的一半 */
    42     if ((qlen << 1) > max(8U, sk_listener->sk_max_ack_backlog)) {
    43 
    44         /* 没有重传ack的请求控制块数 */
    45         int young = reqsk_queue_len_young(queue) << 1;
    46 
    47         /* 可以看出年轻的数量越多,则允许重传次数越多 */
    48         while (thresh > 2) {
    49 
    50             // 没重传的数量> 队列长度的(1/2, 1/4,1/8...)
    51             if (qlen < young)
    52                 break;
    53 
    54             /* 阈值减1 */
    55             thresh--;
    56 
    57             /* 扩大young */
    58             young <<= 1;
    59         }
    60     }
    61     /* 设置了TCP_DEFER_ACCEPT,则使用之 */
    62     defer_accept = READ_ONCE(queue->rskq_defer_accept);
    63     if (defer_accept)
    64         max_retries = defer_accept;
    65 
    66     /* 超时和是否重传判断 */
    67     syn_ack_recalc(req, thresh, max_retries, defer_accept,
    68                &expire, &resend);
    69     /* 统计超时次数 */
    70     req->rsk_ops->syn_ack_timeout(req);
    71     if (!expire && /* 未超时 */
    72         (!resend || /* 不需要重传 */
    73          !inet_rtx_syn_ack(sk_listener, req) || /* 重传成功 */
    74          inet_rsk(req)->acked)) { /* 收到ack了,但是未建立连接(defer_accept,其他情况?) */
    75         unsigned long timeo;
    76 
    77         /* 该请求尚未重传过,则将未重传块-1 */
    78         /* 超时次数+ 1 */
    79         if (req->num_timeout++ == 0)
    80             atomic_dec(&queue->young);
    81 
    82         /* 重新设定超时时间为上次时间* 2,与pto_max取最小值 */
    83         timeo = min(TCP_TIMEOUT_INIT << req->num_timeout, TCP_RTO_MAX);
    84         mod_timer(&req->rsk_timer, jiffies + timeo);
    85         return;
    86     }
    87     
    88     /* 取消连接,从链表移除请求控制块 */
    89 drop:
    90     inet_csk_reqsk_queue_drop_and_put(sk_listener, req);
    91 }

    判断是否已经超时,或者是否需要重传syn+ack;

     1 /* Decide when to expire the request and when to resend SYN-ACK */
     2 /* 判断是否超时,是否重传syn+ack */
     3 static inline void syn_ack_recalc(struct request_sock *req, const int thresh,
     4                   const int max_retries,
     5                   const u8 rskq_defer_accept,
     6                   int *expire, int *resend)
     7 {
     8     /* 无defer_accept选项 */
     9     if (!rskq_defer_accept) {
    10         /* 超时次数> 重传阈值则超时 */
    11         *expire = req->num_timeout >= thresh;
    12 
    13         /* 需要重传 */
    14         *resend = 1;
    15         return;
    16     }
    17 
    18     /* 以下设置了defer_accept */
    19 
    20     /* 超时次数> 重传阈值&&  (未收到ack || 超时次数>= 最大重传数),则超时 */
    21     /* 已经收到ack的情况下,超时次数达到defer_accept限制 */
    22     *expire = req->num_timeout >= thresh &&
    23           (!inet_rsk(req)->acked || req->num_timeout >= max_retries);
    24     /*
    25      * Do not resend while waiting for data after ACK,
    26      * start to resend on end of deferring period to give
    27      * last chance for data or ACK to create established socket.
    28      */
    29 
    30     /* 未收到ack || 收到ack,但是超时次数达到了defer_accept限制-1,需要重传*/
    31     /* 给了设置defer_accept选项情况下一次机会 */
    32     *resend = !inet_rsk(req)->acked ||
    33           req->num_timeout >= rskq_defer_accept - 1;
    34 }
  • 相关阅读:
    CSS印象不深的小地方
    gulp常用插件的使用
    移动端手势库Hammer.js—增强touch事件或手势
    HTML5拖放与文件操作api,实现拖拽上传文件功能
    Less相关
    gulp使用(一)
    将博客搬至CSDN
    jquery Ajax 通过jsonp的方式跨域提交表单
    解决“The superclass "javax.servlet.http.HttpServlet" was not found on the Java Build Path”问题
    使用eclipse4.5创建maven项目
  • 原文地址:https://www.cnblogs.com/wanpengcoder/p/11749431.html
Copyright © 2011-2022 走看看