Linux TCP_DEFER_ACCEPT的作用

来源:互联网 发布:淘宝手机助手好用吗 编辑:程序博客网 时间:2024/06/05 03:37

1. TCP服务端

        int serverSocket = socket(AF_INET, SOCK_STREAM, 0);        ......        bind(serverSocket, (struct sockaddr *)&server_addr, sizeof(server_addr));listen(serverSocket, 5);while(1){int client = accept(serverSocket, (struct sockaddr*)&clientAddr, (socklen_t*)&addr_len);                while(not_finished){iDataNum = recv(client, buffer, 1024, 0);//process the data received...}}

2. 连接的建立


client                                   server

      --------------SYNC--------------->>

      <<----------SYNC/ACK---------------

      ---------------ACK--------------->>  accept() returns, but blocks on recv() immediately;


3. 对于HTTP

其实1中的TCP服务端的例子和HTTP服务端很像:建立连接后,立即读数据(读取HTTP请求)。在这种情况下,client发出ACK之后,server被唤醒(accept返回),并立即试图读数据,由于client还没有发数据,server又再度阻塞。这对于调度是一种浪费。另外,client发的那个ACK也没有实际作用,是不必要的。

假如server端内核忽略client发的ACK,而直接等待数据,数据收到之后再唤醒serve(accept返回),server醒来后就可以直接得到数据并处理。这就是TCP_DEFER_ACCEPT的作用。

server端的socket fd是处于listen状态的,设置TCP_DEFER_ACCEPT之后,内核就会忽略ACK,接收到数据之后再唤醒server(accept返回)。

另外,试想,既然server端忽略ACK,那么客户端可不可以不发送ACK,而直接发送数据呢,这样就节省了一次传输?答案是可以的:TCP_DEFER_ACCEPT设置在client端的socket fd上,就能达到这个效果。

问题:客户端如何知道服务端设置了TCP_DEFER_ACCEPT呢?假如服务端没有设置,而自己设置了,会不会出错?我猜应该不会出错,client本该使用序列号X发送ACK,现在使用序列号X直接发送数据,也符合TCP协议,验证一下。首先,写个简单的TCP服务端客户端(可能不够严谨,不过作为我们这个验证是够了)


服务端:

#include <unistd.h>#include <string.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <netinet/tcp.h>int main(){  int serverSock;  struct sockaddr_in addr;  int clientSock;  struct sockaddr_in clientAddr;  int addrLen;  char buf[1024];  int read;  serverSock = socket(AF_INET, SOCK_STREAM, 0);  if(serverSock == -1)  {    write(STDERR_FILENO, "failed!\n",8);    return 1;  }#ifdef DEFER_ACCEPT  int soValue = 1;  if(setsockopt(serverSock, IPPROTO_TCP, TCP_DEFER_ACCEPT, &soValue, sizeof(soValue))<0)  {    write(STDERR_FILENO, "failed!\n",8);    return 10;  }#endif  memset(&addr, 0, sizeof(addr));  addr.sin_family = AF_INET;  addr.sin_port = htons(7890);  addr.sin_addr.s_addr=inet_addr("127.0.0.1");  if(bind(serverSock, (struct sockaddr*)&addr, sizeof(addr))<0)  {    write(STDERR_FILENO, "failed!\n",8);    return 2;  }  if(listen(serverSock,511)<0)  {    write(STDERR_FILENO, "failed!\n",8);    return 3;  }  while(1)  {    addrLen = sizeof(clientAddr);    clientSock = accept(serverSock, (struct sockaddr*)&clientAddr, &addrLen);    if(clientSock<0)    {      write(STDERR_FILENO, "failed!\n",8);      return 4;    }    read = recv(clientSock, buf, 1024, 0);    write(STDOUT_FILENO, buf, read);    close(clientSock);  }  return 0;}


客户端:

#include <unistd.h>#include <string.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <netinet/tcp.h>int main(){  int clientSock;  struct sockaddr_in addr;  clientSock = socket(AF_INET, SOCK_STREAM, 0);  if(clientSock == -1)  {    write(STDERR_FILENO, "failed!\n",8);    return 1;  }#ifdef DEFER_ACCEPT  int soValue = 1;  if(setsockopt(clientSock, IPPROTO_TCP, TCP_DEFER_ACCEPT, &soValue, sizeof(soValue))<0)  {    write(STDERR_FILENO, "failed!\n",8);    return 10;  }#endif  memset(&addr, 0, sizeof(addr));  addr.sin_family = AF_INET;  addr.sin_port = htons(7890);  addr.sin_addr.s_addr=inet_addr("127.0.0.1");  if(connect(clientSock, (struct sockaddr*)&addr, sizeof(addr))<0)  {    write(STDERR_FILENO, "failed!\n",8);    return 2;  }  if(send(clientSock, "Hello\n", 6, 0)<0)  {    write(STDERR_FILENO, "failed!\n",8);    return 3;  }  close(clientSock);  return 0;}

3.1  client和server都不设置TCP_DEFER_ACCEPT

# gcc server.c -o server# gcc client.c -o client

运行server和client,并通过

# tcpdump -i 8 -x tcp and host 127.0.0.1 and port 7890

来抓包。我们分析6个标志位:URG, ACK,PSH, RST, SYN, FIN。

其中,对于我们日常的分析有用的就是其中五个,它们的含义是:

ACK表示响应;

PSH表示有 DATA数据传输;

RST表示连接重置;

SYN表示建立连接;

FIN表示关闭连接;


前四个包是:

# tcpdump -i 8 -x tcp and host 127.0.0.1 and port 7890tcpdump: verbose output suppressed, use -v or -vv for full protocol decodelistening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes19:45:03.166902 IP localhost.35151 > localhost.7890: Flags [S], seq 587644530, win 43690, options [mss 65495,sackOK,TS val 7427280 ecr 0,nop,wscale 7], length 00x0000:  4500 003c eaa2 4000 4006 5217 7f00 00010x0010:  7f00 0001 894f 1ed2 2306 be72 0000 00000x0020:  a002 aaaa fe30 0000 0204 ffd7 0402 080a  <-- a002最后6bit是000010,SYNC位被设置0x0030:  0071 54d0 0000 0000 0103 030713:05:28.132948 IP localhost.7890 > localhost.35151: Flags [S.], seq 3213519726, ack 587644531, win 43690, options [mss 65495,sackOK,TS val 7427280 ecr 7427280,nop,wscale 7], length 00x0000:  4500 003c 0000 4000 4006 3cba 7f00 00010x0010:  7f00 0001 1ed2 894f bf8a 6b6e 2306 be730x0020:  a012 aaaa fe30 0000 0204 ffd7 0402 080a  <-- a012最后6bit是010010,ACK和SYNC都被设置0x0030:  0071 54d0 0071 54d0 0103 030719:45:03.166941 IP localhost.35151 > localhost.7890: Flags [.], ack 1, win 342, options [nop,nop,TS val 7427280 ecr 7427280], length 00x0000:  4500 0034 eaa3 4000 4006 521e 7f00 00010x0010:  7f00 0001 894f 1ed2 2306 be73 bf8a 6b6f0x0020:  8010 0156 fe28 0000 0101 080a 0071 54d0  <-- 8010最后6bit是010000,ACK被设置0x0030:  0071 54d019:45:03.167723 IP localhost.35151 > localhost.7890: Flags [P.], seq 1:7, ack 1, win 342, options [nop,nop,TS val 7427280 ecr 7427280], length 60x0000:  4500 003a eaa4 4000 4006 5217 7f00 00010x0010:  7f00 0001 894f 1ed2 2306 be73 bf8a 6b6f0x0020:  8018 0156 fe2e 0000 0101 080a 0071 54d0  <-- 8018最后6bit是011000,ACK和PSH被设置,PSH表示包含负载数据0x0030:  0071 54d0 4865 6c6c 6f0a                 <-- Hello\n


3.2  server设置TCP_DEFER_ACCEPT而client不设置

# gcc -DDEFER_ACCEPT server.c -o server# gcc client.c -o client 
前四个包是:

19:57:33.551706 IP localhost.35153 > localhost.7890: Flags [S], seq 899612856, win 43690, options [mss 65495,sackOK,TS val 8177664 ecr 0,nop,wscale 7], length 00x0000:  4500 003c c8b8 4000 4006 7401 7f00 00010x0010:  7f00 0001 8951 1ed2 359f 00b8 0000 00000x0020:  a002 aaaa fe30 0000 0204 ffd7 0402 080a   <-- a002最后6bit是000010,SYNC位被设置0x0030:  007c c800 0000 0000 0103 030720:20:03.622935 IP localhost.7890 > localhost.35153: Flags [S.], seq 2767718812, ack 899612857, win 43690, options [mss 65495,sackOK,TS val 8177664 ecr 8177664,nop,wscale 7], length 00x0000:  4500 003c 0000 4000 4006 3cba 7f00 00010x0010:  7f00 0001 1ed2 8951 a4f8 099c 359f 00b90x0020:  a012 aaaa fe30 0000 0204 ffd7 0402 080a   <-- a012最后6bit是010010,ACK和SYNC都被设置0x0030:  007c c800 007c c800 0103 030719:57:33.551798 IP localhost.35153 > localhost.7890: Flags [.], ack 1, win 342, options [nop,nop,TS val 8177665 ecr 8177664], length 00x0000:  4500 0034 c8b9 4000 4006 7408 7f00 00010x0010:  7f00 0001 8951 1ed2 359f 00b9 a4f8 099d0x0020:  8010 0156 fe28 0000 0101 080a 007c c801   <-- 8010最后6bit是010000,ACK被设置0x0030:  007c c80019:57:33.552000 IP localhost.35153 > localhost.7890: Flags [P.], seq 1:7, ack 1, win 342, options [nop,nop,TS val 8177665 ecr 8177664], length 60x0000:  4500 003a c8ba 4000 4006 7401 7f00 00010x0010:  7f00 0001 8951 1ed2 359f 00b9 a4f8 099d0x0020:  8018 0156 fe2e 0000 0101 080a 007c c801   <-- 8018最后6bit是011000,ACK和PSH被设置0x0030:  007c c800 4865 6c6c 6f0a                  <-- Hello\n

从抓包上,并看不出什么不同;但是,accept在接到第三个包时并不返回,而直到接到第四个包才返回。返回后,recv立即就能接收到数据,不需要再度阻塞。

3.3  client和server都设置TCP_DEFER_ACCEPT

# gcc -DDEFER_ACCEPT server.c -o server# gcc -DDEFER_ACCEPT client.c -o client

前三个包是:

20:05:33.235173 IP localhost.35154 > localhost.7890: Flags [S], seq 3639246407, win 43690, options [mss 65495,sackOK,TS val 8657348 ecr 0,nop,wscale 7], length 00x0000:  4500 003c 6783 4000 4006 d536 7f00 00010x0010:  7f00 0001 8952 1ed2 d8ea 7e47 0000 00000x0020:  a002 aaaa fe30 0000 0204 ffd7 0402 080a  <-- a002最后6bit是000010,SYNC被设置0x0030:  0084 19c4 0000 0000 0103 030716:37:06.900176 IP localhost.7890 > localhost.35154: Flags [S.], seq 4073929758, ack 3639246408, win 43690, options [mss 65495,sackOK,TS val 8657348 ecr 8657348,nop,wscale 7], length 00x0000:  4500 003c 0000 4000 4006 3cba 7f00 00010x0010:  7f00 0001 1ed2 8952 f2d3 3c1e d8ea 7e480x0020:  a012 aaaa fe30 0000 0204 ffd7 0402 080a  <-- a012最后6bit是010010,ACK和SYNC都被设置0x0030:  0084 19c4 0084 19c4 0103 030720:05:33.235891 IP localhost.35154 > localhost.7890: Flags [P.], seq 1:7, ack 1, win 342, options [nop,nop,TS val 8657349 ecr 8657348], length 60x0000:  4500 003a 6784 4000 4006 d537 7f00 00010x0010:  7f00 0001 8952 1ed2 d8ea 7e48 f2d3 3c1f0x0020:  8018 0156 fe2e 0000 0101 080a 0084 19c5  <-- 8018最后6bit是011000,ACK和PSH被设置0x0030:  0084 19c4 4865 6c6c 6f0a                 <--Hello\n

可见,节省了一个包。


3.4  client设置TCP_DEFER_ACCEPT而server不设置

# gcc server.c -o server# gcc -DDEFER_ACCEPT client.c -o client
前三个包是:

20:13:21.659260 IP localhost.35155 > localhost.7890: Flags [S], seq 1415645579, win 43690, options [mss 65495,sackOK,TS val 9125772 ecr 0,nop,wscale 7], length 00x0000:  4500 003c 3607 4000 4006 06b3 7f00 00010x0010:  7f00 0001 8953 1ed2 5461 098b 0000 00000x0020:  a002 aaaa fe30 0000 0204 ffd7 0402 080a   <-- a002最后6bit是000010,SYNC被设置0x0030:  008b 3f8c 0000 0000 0103 030723:28:13.129345 IP localhost.7890 > localhost.35155: Flags [S.], seq 1092521209, ack 1415645580, win 43690, options [mss 65495,sackOK,TS val 9125772 ecr 9125772,nop,wscale 7], length 00x0000:  4500 003c 0000 4000 4006 3cba 7f00 00010x0010:  7f00 0001 1ed2 8953 411e 8cf9 5461 098c0x0020:  a012 aaaa fe30 0000 0204 ffd7 0402 080a   <-- a012最后6bit是010010,ACK和SYNC都被设置0x0030:  008b 3f8c 008b 3f8c 0103 030720:13:21.659471 IP localhost.35155 > localhost.7890: Flags [P.], seq 1:7, ack 1, win 342, options [nop,nop,TS val 9125772 ecr 9125772], length 60x0000:  4500 003a 3608 4000 4006 06b4 7f00 00010x0010:  7f00 0001 8953 1ed2 5461 098c 411e 8cfa0x0020:  8018 0156 fe2e 0000 0101 080a 008b 3f8c   <-- 8018最后6bit是011000,ACK和PSH都被设置0x0030:  008b 3f8c 4865 6c6c 6f0a                  <-- Hello\n
可见,即使server端不设置TCP_DEFER_ACCEPT,客户端省掉一个ACK也不出错。其实,这个ACK没有被省掉,而是和数据包合在一起了。所以,只要连接建立后,第一个包由客户端发出,客户端就可以设置这个标记,从而省掉一次传输。HTTP满足这个特性,但FTP不满足。见第4节。

4. 对于FTP

这个优化不能适用于FTP,原因是:连接建立之后,服务端应该立即发送数据给客户端,而不是立即等待接收数据。服务端发的数据就是提示符prompt,客户端收到提示符之后,才能上传下载文件。


别的系统也可能有类似的东西,但名字可能不同,例如FreeBSD上叫SO_ACCEPTFILTER.

0 0