您的位置:首页 > 理论基础 > 计算机网络

Linux TCP_DEFER_ACCEPT的作用

2017-01-07 17:21 471 查看

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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: