Linux TCP_DEFER_ACCEPT的作用

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 7890
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes
19: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 0
	0x0000:  4500 003c eaa2 4000 4006 5217 7f00 0001
	0x0010:  7f00 0001 894f 1ed2 2306 be72 0000 0000
	0x0020:  a002 aaaa fe30 0000 0204 ffd7 0402 080a  <-- a002最后6bit是000010,SYNC位被设置
	0x0030:  0071 54d0 0000 0000 0103 0307
13: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 0
	0x0000:  4500 003c 0000 4000 4006 3cba 7f00 0001
	0x0010:  7f00 0001 1ed2 894f bf8a 6b6e 2306 be73
	0x0020:  a012 aaaa fe30 0000 0204 ffd7 0402 080a  <-- a012最后6bit是010010,ACK和SYNC都被设置
	0x0030:  0071 54d0 0071 54d0 0103 0307
19:45:03.166941 IP localhost.35151 > localhost.7890: Flags [.], ack 1, win 342, options [nop,nop,TS val 7427280 ecr 7427280], length 0
	0x0000:  4500 0034 eaa3 4000 4006 521e 7f00 0001
	0x0010:  7f00 0001 894f 1ed2 2306 be73 bf8a 6b6f
	0x0020:  8010 0156 fe28 0000 0101 080a 0071 54d0  <-- 8010最后6bit是010000,ACK被设置
	0x0030:  0071 54d0
19: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 6
	0x0000:  4500 003a eaa4 4000 4006 5217 7f00 0001
	0x0010:  7f00 0001 894f 1ed2 2306 be73 bf8a 6b6f
	0x0020:  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 0
	0x0000:  4500 003c c8b8 4000 4006 7401 7f00 0001
	0x0010:  7f00 0001 8951 1ed2 359f 00b8 0000 0000
	0x0020:  a002 aaaa fe30 0000 0204 ffd7 0402 080a   <-- a002最后6bit是000010,SYNC位被设置
	0x0030:  007c c800 0000 0000 0103 0307
20: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 0
	0x0000:  4500 003c 0000 4000 4006 3cba 7f00 0001
	0x0010:  7f00 0001 1ed2 8951 a4f8 099c 359f 00b9
	0x0020:  a012 aaaa fe30 0000 0204 ffd7 0402 080a   <-- a012最后6bit是010010,ACK和SYNC都被设置
	0x0030:  007c c800 007c c800 0103 0307
19:57:33.551798 IP localhost.35153 > localhost.7890: Flags [.], ack 1, win 342, options [nop,nop,TS val 8177665 ecr 8177664], length 0
	0x0000:  4500 0034 c8b9 4000 4006 7408 7f00 0001
	0x0010:  7f00 0001 8951 1ed2 359f 00b9 a4f8 099d
	0x0020:  8010 0156 fe28 0000 0101 080a 007c c801   <-- 8010最后6bit是010000,ACK被设置
	0x0030:  007c c800
19: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 6
	0x0000:  4500 003a c8ba 4000 4006 7401 7f00 0001
	0x0010:  7f00 0001 8951 1ed2 359f 00b9 a4f8 099d
	0x0020:  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 0
	0x0000:  4500 003c 6783 4000 4006 d536 7f00 0001
	0x0010:  7f00 0001 8952 1ed2 d8ea 7e47 0000 0000
	0x0020:  a002 aaaa fe30 0000 0204 ffd7 0402 080a  <-- a002最后6bit是000010,SYNC被设置
	0x0030:  0084 19c4 0000 0000 0103 0307
16: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 0
	0x0000:  4500 003c 0000 4000 4006 3cba 7f00 0001
	0x0010:  7f00 0001 1ed2 8952 f2d3 3c1e d8ea 7e48
	0x0020:  a012 aaaa fe30 0000 0204 ffd7 0402 080a  <-- a012最后6bit是010010,ACK和SYNC都被设置
	0x0030:  0084 19c4 0084 19c4 0103 0307
20: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 6
	0x0000:  4500 003a 6784 4000 4006 d537 7f00 0001
	0x0010:  7f00 0001 8952 1ed2 d8ea 7e48 f2d3 3c1f
	0x0020:  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 0
	0x0000:  4500 003c 3607 4000 4006 06b3 7f00 0001
	0x0010:  7f00 0001 8953 1ed2 5461 098b 0000 0000
	0x0020:  a002 aaaa fe30 0000 0204 ffd7 0402 080a   <-- a002最后6bit是000010,SYNC被设置
	0x0030:  008b 3f8c 0000 0000 0103 0307
23: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 0
	0x0000:  4500 003c 0000 4000 4006 3cba 7f00 0001
	0x0010:  7f00 0001 1ed2 8953 411e 8cf9 5461 098c
	0x0020:  a012 aaaa fe30 0000 0204 ffd7 0402 080a   <-- a012最后6bit是010010,ACK和SYNC都被设置
	0x0030:  008b 3f8c 008b 3f8c 0103 0307
20: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 6
	0x0000:  4500 003a 3608 4000 4006 06b4 7f00 0001
	0x0010:  7f00 0001 8953 1ed2 5461 098c 411e 8cfa
	0x0020:  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.

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值