原文链接:http://blog.csdn.net/hbhhww/article/details/8237309
我们首先考虑的第1个选项是TCP_DEFER_ACCEPT(这是Linux系统上的叫法,其他一些操作系统上也有同样的选项但使用不同的名字)。为了理解TCP_DEFER_ACCEPT选项的具体思想,我们有必要大致阐述一下典型的HTTP客户/服务器交互过程。请回想下TCP是如何与传输数据的目标建立连接的。在网络上,在分离的单元之间传输的信息称为IP包(或IP 数据报)。一个包总有一个携带服务信息的包头,包头用于内部协议的处理,并且它也可以携带数据负载。服务信息的典型例子就是一套所谓的标志,它把包标记代表TCP/IP协议栈内的特殊含义,例如收到包的成功确认等等。通常,在经过“标记”的包里携带负载是完全可能的,但有时,内部逻辑迫使TCP/IP协议栈发出只有包头的IP包。这些包经常会引发讨厌的网络延迟而且还增加了系统的负载,结果导致网络性能在整体上降低。
现在服务器创建了一个套接字同时等待连接。TCP/IP式的连接过程就是所谓“3次握手”。首先,客户程序发送一个设置SYN标志而且不带数据负载的TCP包(一个SYN包)。服务器则以发出带SYN/ACK标志的数据包(一个SYN/ACK包)作为刚才收到包的确认响应。客户随后发送一个ACK包确认收到了第2个包从而结束连接过程。在收到客户发来的这个ACK包之后,服务器会唤醒一个接收进程等待数据到达。当3次握手完成后,客户程序即开始把“有用的”的数据发送给服务器。通常,一个HTTP请求的量是很小的而且完全可以装到一个包里。但是,在以上的情况下,至少有4个包将用来进行双向传输,这样就增加了可观的延迟时间。此外,你还得注意到,在“有用的”数据被发送之前,接收方已经开始在等待信息了。
为了减轻这些问题所带来的影响,Linux(以及其他的一些操作系统)在其TCP实现中包括了TCP_DEFER_ACCEPT选项。它们设置在侦听套接字的服务器方,该选项命令内核不等待最后的ACK包而且在第1个真正有数据的包到达才初始化侦听进程。在发送SYN/ACK包之后,服务器就会等待客户程序发送含数据的IP包。现在,只需要在网络上传送3个包了,而且还显着降低了连接建立的延迟,对HTTP通信而言尤其如此。这一选项在好些操作系统上都有相应的对等物。例如,在FreeBSD上,同样的行为可以用以下代码实现:
/* 为明晰起见,此处略去无关代码 */
struct accept_filter_arg af = { "dataready", "" };
setsockopt(s, SOL_SOCKET, SO_ACCEPTFILTER, &af, sizeof(af));
这个特征在FreeBSD上叫做“接受过滤器”,而且具有多种用法。不过,在几乎所有的情况下其效果与TCP_DEFER_ACCEPT是一样的:服务器不等待最后的ACK包而仅仅等待携带数据负载的包。要了解该选项及其对高性能Web服务器的重要意义的更多信息请参考Apache文档上的有关内容。
就HTTP客户/服务器交互而言,有可能需要改变客户程序的行为。客户程序为什么要发送这种“无用的”ACK包呢?这是因为,TCP协议栈无法知道ACK包的状态。如果采用FTP而非HTTP,那么客户程序直到接收了FTP服务器提示的数据包之后才发送数据。在这种情况下,延迟的ACK将导致客户/服务器交互出现延迟。为了确定ACK是否必要,客户程序必须知道应用程序协议及其当前状态。这样,修改客户行为就成为必要了。
对Linux客户程序来说,我们还可以采用另一个选项,它也被叫做TCP_DEFER_ACCEPT。我们知道,套接字分成两种类型,侦听套接字和连接套接字,所以它们也各自具有相应的TCP选项集合。因此,经常同时采用的这两类选项却具有同样的名字也是完全可能的。在连接套接字上设置该选项以后,客户在收到一个
SYN/ACK包之后就不再发送ACK包,而是等待用户程序的下一个发送数据请求;因此,服务器发送的包也就相应减少了。
tcp三次握手过程:
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认; 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态; 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
服务器在三次握手后会唤起一个进程等待数据到达,当服务器设置了TCP_DEFER_ACCEPT后,内核不等待最后一个ACK包,而且在第一个真正有数据的包到达后才初始化侦听过程,可以减少一些上下文切换,三次握手后,如果没有数据到来,服务器socket处于SYN_RECV状态,这个时候就依赖于net.ipv4.tcp_synack_retries参数控制了,当达到重传后,过指定的时间关闭socket
看测试程序
服务器环境:Linux version 2.6 + gcc,ip:192.168.30.202
客户端环境:win32 + vs2010,ip:192.168.30.12
没有设置TCP_DEFER_ACCEPT,三次握手的抓包:
[root@localhost ~]# tcpdump tcp port 10000
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
16:30:59.486716 IP 192.168.30.12.4326 > 192.168.30.202.ndmp: S 1992149464:1992149464(0) win 65535
16:30:59.490951 IP 192.168.30.202.ndmp > 192.168.30.12.4326: S 524361898:524361898(0) ack 1992149465 win 5840
16:30:59.491059 IP 192.168.30.12.4326 > 192.168.30.202.ndmp: . ack 1 win 65535
设置TCP_DEFER_ACCEPT,通过命令“sysctl -w net.ipv4.tcp_synack_retries=1”设置重传后,抓包如下:
[root@localhost ~]# tcpdump tcp port 10000
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
16:35:12.764157 IP 192.168.30.12.4548 > 192.168.30.202.ndmp: S 1431219651:1431219651(0) win 65535
16:35:12.767924 IP 192.168.30.202.ndmp > 192.168.30.12.4548: S 795485424:795485424(0) ack 1431219652 win 5840
16:35:12.768044 IP 192.168.30.12.4548 > 192.168.30.202.ndmp: . ack 1 win 65535
16:35:15.726875 IP 192.168.30.202.ndmp > 192.168.30.12.4548: S 795485424:795485424(0) ack 1431219652 win 5840
16:35:15.726980 IP 192.168.30.12.4548 > 192.168.30.202.ndmp: . ack 1 win 65535
设置TCP_DEFER_ACCEPT,通过命令“sysctl -w net.ipv4.tcp_synack_retries=3”设置重传后,抓包如下:
[root@localhost ~]# tcpdump tcp port 10000
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
16:36:57.993105 IP 192.168.30.12.4648 > 192.168.30.202.ndmp: S 242062800:242062800(0) win 65535
16:36:57.996526 IP 192.168.30.202.ndmp > 192.168.30.12.4648: S 917726329:917726329(0) ack 242062801 win 5840
16:36:57.996567 IP 192.168.30.12.4648 > 192.168.30.202.ndmp: . ack 1 win 65535
16:37:02.140832 IP 192.168.30.202.ndmp > 192.168.30.12.4648: S 917726329:917726329(0) ack 242062801 win 5840
16:37:02.141226 IP 192.168.30.12.4648 > 192.168.30.202.ndmp: . ack 1 win 65535
16:37:08.065966 IP 192.168.30.202.ndmp > 192.168.30.12.4648: S 917726329:917726329(0) ack 242062801 win 5840
16:37:08.066190 IP 192.168.30.12.4648 > 192.168.30.202.ndmp: . ack 1 win 65535
16:37:19.915745 IP 192.168.30.202.ndmp > 192.168.30.12.4648: S 917726329:917726329(0) ack 242062801 win 5840
16:37:19.915993 IP 192.168.30.12.4648 > 192.168.30.202.ndmp: . ack 1 win 65535
上面抓包结果可以看出结果,不过还有个问题没弄明白,当达到重传次数后,过指定的时间关闭socket,这个时间没有准确的计算出来
int val = 10;
if (setsockopt(sock_descriptor, IPPROTO_TCP, TCP_DEFER_ACCEPT, &val, sizeof(val))== -1)
{
perror("setsockopt");
exit(1);
}
val以秒为单位,val设置的时间越久,测试出这个等待关闭的时间就越久,具体没弄清楚是多少,或者是由其他什么控制的,杯具啊!!