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. }

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

tcp选项TCP_DEFER_ACCEPT的更多相关文章

  1. TCP连接建立系列 — TCP选项解析

    本文主要分析:在收到客户端的SYN包时,服务器端是如何解析它所携带的TCP选项,并结合本端情况决定是否予以支持. 内核版本:3.6 Author:zhangskd @ csdn blog 概述 收到客 ...

  2. 常用的TCP选项

    MSS选项:通知最大可接收量.发送SYN的TCP一端使用本选项通告对端它的最大分节大小(maximum segment size)即MSS,也就是它在本连接的每个TCP分节中愿意接受的最大数据量.发送 ...

  3. TCP 选项RST

    1.RST介绍 RST表示reset复位,用于异常情况下关闭连接. 发送RST包关闭连接时,不必等缓冲区的包都发出去,直接就丢弃缓冲区中的包. 而接收端收到RST包后,也不必发送ACK包来确认. 2. ...

  4. TCP选项之SO_LINGER

    SO_LINGER这个选项在我以前带队改造haproxy的时候引出过一个reset(RST)客户端连接的bug. SO_LINGER作用设置函数close()关闭TCP连接时的行为.缺省close() ...

  5. TCP选项之SO_RCVBUF和SO_SNDBUF

    每个TCP socket在内核中都有一个发送缓冲区和一个接收缓冲区,TCP的全双工的工作模式以及TCP的滑动窗口便是依赖于这两个独立的buffer以及此buffer的填充状态.接收缓冲区把数据缓存入内 ...

  6. TCP/IP详解--TCP首部选项中时间戳选项

    一.简介 TCP时间戳选项会在TCP包头增加12个字节,以一种比重发超时更精确的方法来启用对RTT 的计算.   二.作用 ) TCP时间戳位于TCP选项中,kind=:lenth=:data由tim ...

  7. TCP KeepAlive的几个附加选项

    TCP_KEEPALIVE选项只是一个开关,Linux中默认的Keepalive的选项如下: $sudo sysctl -a | grep keepalive net.ipv4.tcp_keepali ...

  8. TCP系列08—连接管理—7、TCP 常见选项(option)

    一.TCP选项概述 在前面介绍TCP头的时候,我们说过tcp基本头下面可以带有tcp选项,其中有些选项只能在连接过程中随着SYN包发送,有些可以延后.下表汇总了一些tcp选项 其中我标记为红色的部分是 ...

  9. TCP连接建立系列 — 服务端接收ACK段(一)

      http://blog.csdn.net/zhangskd/article/details/17923917 分类: Linux TCP/IP Linux Kernel 2014-01-07 09 ...

随机推荐

  1. phpcms 移植【添加相关文章】功能

    添加相关文章功能相当有用,移植一个过来基本上可以实现比较复杂的页面内包含分类功能,做二次开发时可以省下不少力气. 用例:如果一个产品,属于一个厂家,而这个厂家是动态添加的,既不是一个分类,而是一个厂家 ...

  2. log4j2配置详解

    1.    log4j2需要两个jar   log4j-api-2.x.x.jar    log4j-core-2.x.x.jar  .log4j和log4j2有很大的区别,jar包不要应错. 2. ...

  3. [asp.net mvc 奇淫巧技] 01 - 封装上下文 - 在View中获取自定义的上下文

    我们在asp.net 开发中已经封装了最强大的HttpContext,我们可以在HttpContext中可以获取到几乎任何想获取的东西,也可以在HttpContext写入需要返回客户端的信息.但是这些 ...

  4. ENVI数据显示操作【Tools菜单操作1】

    ---恢复内容开始--- 一.Tools菜单命令及其功能 主图像窗口中Tool菜单多对应的下拉菜单共17项命令. 二.窗口链接/覆盖显示 窗口链接和叠加显示(Link和Overlay)是对多幅图像某一 ...

  5. 手机端Swiper 触屏滑动

    在线实例 默认 响应式 垂直 空间间隔 滚动 自动滚动 中心化 中心化自动 免费模式 多个滚动 水平滚动 grab-cursor 使用方法 <div class="swiper-con ...

  6. Hello.js – Web 服务授权的 JavaScript SDK

    Hello.js 是一个客户端的 Javascript SDK,用于实现 OAuth2 认证(或者基于 OAuth 代理实现的 OAuth1)的 Web 服务和查询 REST API. HelloJS ...

  7. CSS3里的display

    默认值:inline 适用于:所有元素 继承性:无 动画性:否 none: 隐藏对象.与visibility属性的hidden值不同,其不为被隐藏的对象保留其物理空间 inline: 指定对象为内联元 ...

  8. JavaScript实战-菜单特效

    以下是我自己用原生JS写的各种菜单特效,虽然网上一搜一大堆,但我还是喜欢自己来写一写! 这是上一篇:JavaScript实战(带收放动画效果的导航菜单) 下面是经过优化后的完整代码,优化了CSS样式. ...

  9. Request.MapPath和ServerMapPath

    一.路径 / 念 反斜杠,/ 是超文本协议的路径分隔符号,所有的网站在浏览器中显示的路径分隔都是以"/"表示.它一般代表虚拟路径. \ 念 斜杠,在普通程序代码中则以"\ ...

  10. Sharepoint 2010 无法上传文件的问题

    现象: 用户拥有某文档库的参与讨论权限,但是点击“上传文件”时,系统提示当前用户没有权限 (Access Denied) . 某用户拥有某文档库的参与讨论权限,“上传单个文件”按键是可以用的,但是“上 ...