zoukankan      html  css  js  c++  java
  • tcp选项TCP_DEFER_ACCEPT

    tcp选项TCP_DEFER_ACCEPT

    http://blog.chinaunix.net/uid-23207633-id-274317.html

    之前在项目测试的时候,如果第三次握手发完裸ack(没有数据)之后不发送数据的时候,连接状态一直为SYN_RCV,而且服务端重传synack,当时很不解,后来看了下源码,才发现些端倪。当时测试的内核是2.6.18-194(centos5.5)。

        第三次握手会调用函数tcp_v4_hnd_req:
    1. static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)
    2. {
    3.     ......
    4.     struct request_sock *req = inet_csk_search_req(sk, &prev, th->source,
    5.                          iph->saddr, iph->daddr);//查找半连接队列,返回req
    6.     if (req)
    7.         return tcp_check_req(sk, skb, req, prev);//ack的处理
    8.     ......
    9. }

        我们看函数tcp_check_req

    1. struct sock *tcp_check_req(struct sock *sk,struct sk_buff *skb,
    2.              struct request_sock *req,
    3.              struct request_sock **prev)
    4. {
    5.        ......
    6.         /* If TCP_DEFER_ACCEPT is set, drop bare ACK. */
    7.         if (inet_csk(sk)->icsk_accept_queue.rskq_defer_accept &&
    8.          TCP_SKB_CB(skb)->end_seq == tcp_rsk(req)->rcv_isn + 1) {//如果选项设置了,并且是裸
    9.                                                                    ack,丢弃该ack;选项值得默
    10.                                                                    认为1
    11.             inet_rsk(req)->acked = 1;
    12.             return NULL;
    13.         }
    14.         child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb,
    15.                                  req, NULL);//如果非裸ack或没设置选项则建立连接(req从半连接
    16.                                               队列到连接队列及tcp状态变为ESTABLISHED)
    17.         ......
    18. }

    我们在用户层写socket程序时,可以通过setsockopt来设置TCP_DEFER_ACCEPT选项:

    1. val = 5;
    2. setsockopt(srv_socket->fd, SOL_TCP, TCP_DEFER_ACCEPT, &val, sizeof(val)) ;
    3. 里面 val 的单位是秒,注意如果打开这个功能,kernel 在 val 秒之内还没有收到数据,不会继续唤醒进程,而是直接丢弃连接。

    在内核空间会调用:

      1. static int do_tcp_setsockopt(struct sock *sk, int level,
      2.         int optname, char __user *optval, int optlen)
      3. {
      4.     struct tcp_sock *tp = tcp_sk(sk);
      5.     struct inet_connection_sock *icsk = inet_csk(sk);
      6.     int val;
            ......
      7.      if (get_user(val, (int __user *)optval))//拷贝用户空间数据

                  return -EFAULT;

    1.     ......
    2.     case TCP_DEFER_ACCEPT:
    3.         icsk->icsk_accept_queue.rskq_defer_accept = 0;
    4.         if (val > 0) {//如果setsockopt中设置val为0,则不开始TCP_DEFER_ACCEPT选项
    5.             /* Translate value in seconds to number of
    6.              * retransmits */
    7.             while (icsk->icsk_accept_queue.rskq_defer_accept < 32 &&
    8.              val > ((TCP_TIMEOUT_INIT / HZ) <<
    9.                  icsk->icsk_accept_queue.rskq_defer_accept))//根据设置的val决定重传次数,譬
    10.                                                 如val=10,重传次数为3;后面我们可以看到,只有
    11.                                                 /proc/sys/net/ipv4/tcp_synack_retries的
    12.                                                 值小于等于通过val算出的重传次数时,这个val才
    13.                                                 起作用
    14.                 icsk->icsk_accept_queue.rskq_defer_accept++;
    15.             icsk->icsk_accept_queue.rskq_defer_accept++;
    16.         }
    17.         break;
    18.     ......
    19. }

    内核是通过函数inet_csk_reqsk_queue_prune进行重传synack:

    1. void inet_csk_reqsk_queue_prune(struct sock *parent,
    2.                 const unsigned long interval,
    3.                 const unsigned long timeout,
    4.                 const unsigned long max_rto)
    5. {
    6.     struct inet_connection_sock *icsk = inet_csk(parent);
    7.     struct request_sock_queue *queue = &icsk->icsk_accept_queue;
    8.     struct listen_sock *lopt = queue->listen_opt;
    9.     int max_retries = icsk->icsk_syn_retries ? : sysctl_tcp_synack_retries;//默认synack
    10.                                                                              重传次数为5
    11.     int thresh = max_retries;
    12.     unsigned long now = jiffies;
    13.     struct request_sock **reqp, *req;
    14.     int i, budget;
    15.     ......
    16.     if (queue->rskq_defer_accept)
    17.         max_retries = queue->rskq_defer_accept;//设定支持选项时候的重传次数
    18.     budget = 2 * (lopt->nr_table_entries / (timeout / interval));
    19.     i = lopt->clock_hand;
    20.     do {
    21.         reqp=&lopt->syn_table[i];
    22.         while ((req = *reqp) != NULL) {
    23.             if (time_after_eq(now, req->expires)) {
    24.                 if ((req->retrans < thresh ||
    25.                  (inet_rsk(req)->acked && req->retrans < max_retries))
    26.                  && !req->rsk_ops->rtx_syn_ack(parent, req, NULL)) {//如果重传次数小于设定
    27.                  的重传次数,就重传synack;这里可以看出两个并列的判断条件:req->retrans < thres
    28.                  h和(inet_rsk(req)->acked && req->retrans < max_retries),第一个是当前req
    29.                  的重传次数小于设定的最大重传次数,这里是5;第二个则是TCP_DEFER_ACCEPT;inet_rs
    30.                  k(req)->acked则是在函数tcp_check_req中设定的,上面讨论过了,而max_retries则
    31.                  为通过val计算的值,默认为1。这个重传次数决定了synack包的重传次数及最长超时时间,
    32.                  显然两者中较大者起到决定性的作用。譬如,默认重传为2,通过val计算出的max_retries
    33.                  值为3,则将发送3次重传的synack及超时时间为12秒后,关闭连接
    34.                     unsigned long timeo;
    35.                     if (req->retrans++ == 0)
    36.                         lopt->qlen_young--;
    37.                     timeo = min((timeout << req->retrans), max_rto);
    38.                     req->expires = now + timeo;//每重传一次,超时值就按初始值
    39.                                                  timeout(TCP_TIMEOUT_INIT)比值为2的等比
    40.                                                  数列增加,如3 6 12 24 48 96
    41.                     reqp = &req->dl_next;
    42.                     continue;//继续循环
    43.                 }
    44.                 /* Drop this request */
    45.                 如果超时,如超过例子中的96秒,就将req从半连接队列里删除,丢弃连接
    46.                 inet_csk_reqsk_queue_unlink(parent, req, reqp);
    47.                 reqsk_queue_removed(queue, req);
    48.                 reqsk_free(req);
    49.                 continue;
    50.             }
    51.             reqp = &req->dl_next;
    52.         }
    53.         i = (i + 1) & (lopt->nr_table_entries - 1);
    54.     } while (--budget > 0);
    55.     lopt->clock_hand = i;
    56.     if (lopt->qlen)
    57.         inet_csk_reset_keepalive_timer(parent, interval);
    58. }

    那么TCP_DEFER_ACCEPT选项有什么好处呢,我们知道服务端处于监听时,客户端connect;服务端会收到syn包,并发送 synack;当客户端收到synack并发送裸ack时,服务端accept创建一个新的句柄,这是不支持TCP_DEFER_ACCEPT选项下的流 程。如果支持TCP_DEFER_ACCEPT,收到裸ack时,不会建立连接,操作系统不会Accept,也不会创建IO句柄。操作系统应该在若干秒 后,会释放相关的链接;但没有同时关闭相应的端口,所以客户端会一直以为处于链接状态,如果Connect后面马上有后续的发送数据,那么服务器会调用 Accept接收这个连接。

    函数inet_csk_reqsk_queue_prune是通过tcp_synack_timer,是它在定时器中起作用的

    1. static void tcp_synack_timer(struct sock *sk)
    2. {
    3.     inet_csk_reqsk_queue_prune(sk, TCP_SYNQ_INTERVAL,
    4.                  TCP_TIMEOUT_INIT, TCP_RTO_MAX);
    5. }

    关于定时器,在后续的分析中。

  • 相关阅读:
    所谓的小项目
    PHP开发者常犯的10个MySQL错误
    这是掌握cookie最后的一个障碍
    Lua学习笔记(2)——table
    Corona Enterprise 引入第三方 jar 包
    Lua学习笔记(1)
    SQL分页查询笔记
    三两句记录设计模式(1)——简单工厂模式
    Window + Net + Corona + IOS PUSH
    IoC学习
  • 原文地址:https://www.cnblogs.com/mull/p/4478682.html
Copyright © 2011-2022 走看看