tcp选项TCP_DEFER_ACCEPT
http://blog.chinaunix.net/uid-23207633-id-274317.html
之前在项目测试的时候,如果第三次握手发完裸ack(没有数据)之后不发送数据的时候,连接状态一直为SYN_RCV,而且服务端重传synack,当时很不解,后来看了下源码,才发现些端倪。当时测试的内核是2.6.18-194(centos5.5)。
- static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)
- {
- ......
- struct request_sock *req = inet_csk_search_req(sk, &prev, th->source,
- iph->saddr, iph->daddr);//查找半连接队列,返回req
- if (req)
- return tcp_check_req(sk, skb, req, prev);//ack的处理
- ......
- }
我们看函数tcp_check_req
- struct sock *tcp_check_req(struct sock *sk,struct sk_buff *skb,
- struct request_sock *req,
- struct request_sock **prev)
- {
- ......
- /* If TCP_DEFER_ACCEPT is set, drop bare ACK. */
- if (inet_csk(sk)->icsk_accept_queue.rskq_defer_accept &&
- TCP_SKB_CB(skb)->end_seq == tcp_rsk(req)->rcv_isn + 1) {//如果选项设置了,并且是裸
- ack,丢弃该ack;选项值得默
- 认为1
- inet_rsk(req)->acked = 1;
- return NULL;
- }
- child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb,
- req, NULL);//如果非裸ack或没设置选项则建立连接(req从半连接
- 队列到连接队列及tcp状态变为ESTABLISHED)
- ......
- }
我们在用户层写socket程序时,可以通过setsockopt来设置TCP_DEFER_ACCEPT选项:
- val = 5;
- setsockopt(srv_socket->fd, SOL_TCP, TCP_DEFER_ACCEPT, &val, sizeof(val)) ;
- 里面 val 的单位是秒,注意如果打开这个功能,kernel 在 val 秒之内还没有收到数据,不会继续唤醒进程,而是直接丢弃连接。
在内核空间会调用:
- static int do_tcp_setsockopt(struct sock *sk, int level,
- int optname, char __user *optval, int optlen)
- {
- struct tcp_sock *tp = tcp_sk(sk);
- struct inet_connection_sock *icsk = inet_csk(sk);
- int val;
......
if (get_user(val, (int __user *)optval))//拷贝用户空间数据
- return -EFAULT;
- ......
- case TCP_DEFER_ACCEPT:
- icsk->icsk_accept_queue.rskq_defer_accept = 0;
- if (val > 0) {//如果setsockopt中设置val为0,则不开始TCP_DEFER_ACCEPT选项
- /* Translate value in seconds to number of
- * retransmits */
- while (icsk->icsk_accept_queue.rskq_defer_accept < 32 &&
- val > ((TCP_TIMEOUT_INIT / HZ) <<
- icsk->icsk_accept_queue.rskq_defer_accept))//根据设置的val决定重传次数,譬
- 如val=10,重传次数为3;后面我们可以看到,只有
- /proc/sys/net/ipv4/tcp_synack_retries的
- 值小于等于通过val算出的重传次数时,这个val才
- 起作用
- icsk->icsk_accept_queue.rskq_defer_accept++;
- icsk->icsk_accept_queue.rskq_defer_accept++;
- }
- break;
- ......
- }
内核是通过函数inet_csk_reqsk_queue_prune进行重传synack:
- void inet_csk_reqsk_queue_prune(struct sock *parent,
- const unsigned long interval,
- const unsigned long timeout,
- const unsigned long max_rto)
- {
- struct inet_connection_sock *icsk = inet_csk(parent);
- struct request_sock_queue *queue = &icsk->icsk_accept_queue;
- struct listen_sock *lopt = queue->listen_opt;
- int max_retries = icsk->icsk_syn_retries ? : sysctl_tcp_synack_retries;//默认synack
- 重传次数为5
- int thresh = max_retries;
- unsigned long now = jiffies;
- struct request_sock **reqp, *req;
- int i, budget;
- ......
- if (queue->rskq_defer_accept)
- max_retries = queue->rskq_defer_accept;//设定支持选项时候的重传次数
- budget = 2 * (lopt->nr_table_entries / (timeout / interval));
- i = lopt->clock_hand;
- do {
- reqp=&lopt->syn_table[i];
- while ((req = *reqp) != NULL) {
- if (time_after_eq(now, req->expires)) {
- if ((req->retrans < thresh ||
- (inet_rsk(req)->acked && req->retrans < max_retries))
- && !req->rsk_ops->rtx_syn_ack(parent, req, NULL)) {//如果重传次数小于设定
- 的重传次数,就重传synack;这里可以看出两个并列的判断条件:req->retrans < thres
- h和(inet_rsk(req)->acked && req->retrans < max_retries),第一个是当前req
- 的重传次数小于设定的最大重传次数,这里是5;第二个则是TCP_DEFER_ACCEPT;inet_rs
- k(req)->acked则是在函数tcp_check_req中设定的,上面讨论过了,而max_retries则
- 为通过val计算的值,默认为1。这个重传次数决定了synack包的重传次数及最长超时时间,
- 显然两者中较大者起到决定性的作用。譬如,默认重传为2,通过val计算出的max_retries
- 值为3,则将发送3次重传的synack及超时时间为12秒后,关闭连接
- unsigned long timeo;
- if (req->retrans++ == 0)
- lopt->qlen_young--;
- timeo = min((timeout << req->retrans), max_rto);
- req->expires = now + timeo;//每重传一次,超时值就按初始值
- timeout(TCP_TIMEOUT_INIT)比值为2的等比
- 数列增加,如3 6 12 24 48 96
- reqp = &req->dl_next;
- continue;//继续循环
- }
- /* Drop this request */
- 如果超时,如超过例子中的96秒,就将req从半连接队列里删除,丢弃连接
- inet_csk_reqsk_queue_unlink(parent, req, reqp);
- reqsk_queue_removed(queue, req);
- reqsk_free(req);
- continue;
- }
- reqp = &req->dl_next;
- }
- i = (i + 1) & (lopt->nr_table_entries - 1);
- } while (--budget > 0);
- lopt->clock_hand = i;
- if (lopt->qlen)
- inet_csk_reset_keepalive_timer(parent, interval);
- }
那么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,是它在定时器中起作用的
- static void tcp_synack_timer(struct sock *sk)
- {
- inet_csk_reqsk_queue_prune(sk, TCP_SYNQ_INTERVAL,
- TCP_TIMEOUT_INIT, TCP_RTO_MAX);
- }
关于定时器,在后续的分析中。
tcp选项TCP_DEFER_ACCEPT的更多相关文章
- TCP连接建立系列 — TCP选项解析
本文主要分析:在收到客户端的SYN包时,服务器端是如何解析它所携带的TCP选项,并结合本端情况决定是否予以支持. 内核版本:3.6 Author:zhangskd @ csdn blog 概述 收到客 ...
- 常用的TCP选项
MSS选项:通知最大可接收量.发送SYN的TCP一端使用本选项通告对端它的最大分节大小(maximum segment size)即MSS,也就是它在本连接的每个TCP分节中愿意接受的最大数据量.发送 ...
- TCP 选项RST
1.RST介绍 RST表示reset复位,用于异常情况下关闭连接. 发送RST包关闭连接时,不必等缓冲区的包都发出去,直接就丢弃缓冲区中的包. 而接收端收到RST包后,也不必发送ACK包来确认. 2. ...
- TCP选项之SO_LINGER
SO_LINGER这个选项在我以前带队改造haproxy的时候引出过一个reset(RST)客户端连接的bug. SO_LINGER作用设置函数close()关闭TCP连接时的行为.缺省close() ...
- TCP选项之SO_RCVBUF和SO_SNDBUF
每个TCP socket在内核中都有一个发送缓冲区和一个接收缓冲区,TCP的全双工的工作模式以及TCP的滑动窗口便是依赖于这两个独立的buffer以及此buffer的填充状态.接收缓冲区把数据缓存入内 ...
- TCP/IP详解--TCP首部选项中时间戳选项
一.简介 TCP时间戳选项会在TCP包头增加12个字节,以一种比重发超时更精确的方法来启用对RTT 的计算. 二.作用 ) TCP时间戳位于TCP选项中,kind=:lenth=:data由tim ...
- TCP KeepAlive的几个附加选项
TCP_KEEPALIVE选项只是一个开关,Linux中默认的Keepalive的选项如下: $sudo sysctl -a | grep keepalive net.ipv4.tcp_keepali ...
- TCP系列08—连接管理—7、TCP 常见选项(option)
一.TCP选项概述 在前面介绍TCP头的时候,我们说过tcp基本头下面可以带有tcp选项,其中有些选项只能在连接过程中随着SYN包发送,有些可以延后.下表汇总了一些tcp选项 其中我标记为红色的部分是 ...
- TCP连接建立系列 — 服务端接收ACK段(一)
http://blog.csdn.net/zhangskd/article/details/17923917 分类: Linux TCP/IP Linux Kernel 2014-01-07 09 ...
随机推荐
- textview滑动效果
网上很多在xml中改的我经过试验没用,可能是版本不兼容的原因,但在java代码中改有用head_tv1.setEllipsize(TextUtils.TruncateAt.MARQUEE);head_ ...
- 【FFmpeg】Windows下FFmpeg编译
由于FFmpeg是基于Linux开发的开源项目,源代码和Windows下最常见的Visual Studio提供的C/C++编译器不兼容,因此它不能使用MSVC++编译,需要在Windows下配置一个类 ...
- 更新整理本人所有博文中提供的代码与工具(C++,2013.08)
为了更方便地管理博文中涉及的各种代码与工具资源,现在把这些资源迁移到 Google Code 中,有兴趣者可前往下载. C++ 1.<通用高性能 Windows Socket 组件 HP-Soc ...
- MyBatis入门(一)
一.MyBaris简介 1)MyBaris发展过程 MyBatis的前身叫iBatis,本是apache的一个开源项目, 2010年这个项目由apache software foundation 迁移 ...
- 关于HTML面试题汇总之H5
一.H5有哪些新特性,移除了哪些元素?如何处理h5新标签的浏览器兼容性问题,如何区分html和html5 1. html5不在是SGL(通用标记语言)的一个子集,而包含了:图像.位置.存储.多任务等功 ...
- 【初探Underscore】再说模版引擎
前言 Underscore 是一个JavaScript实用库,提供了类似Prototype.js (或 Ruby)的一些功能,但是没有扩展任何JavaScript内置对象. 它弥补了部分jQuery没 ...
- 错误 24 无法嵌入互操作类型“ESRI.ArcGIS.Geometry.PointClass”。请改用适用的接口。 E:\MyGIS\MyGIS\Form1.cs 78 37 MyGIS
解决办法:选中那个引用,在属性页,将“嵌入互操作”设置为false
- Maven下SiteMesh的使用
先白扯两句,为什么用SiteMesh,当然是减少重复代码,让程序员更加转自己的那一小块. 优点呢:结构化,重用 缺点:呵呵呵,性能,尤其是GC 至于3.0是不是有很大提升,请大神来分析一下. 性能你还 ...
- iOS界面传值的方式(7种)
iOS传值的方式 属性传值 方法传值 代理传值(delegate) block传值 单例模式方式 通知notification方式 UserDefault或者文件方式 1.属性传值 情况:A页面跳转到 ...
- Android自定义控件2--优酷菜单界面初始化
本文开始将逐步去实现下面优酷菜单的效果: 本文地址:http://www.cnblogs.com/wuyudong/p/5912538.html,转载请注明源地址. 本文首先来实现优酷菜单界面初始化工 ...