TFO(TCP Fast Open)是一种能够在TCP连接建立阶段传输数据的机制。使用这种机制可以将数据交互提前,降低应用层事务的延迟。其基本步骤如下:
1、客户端发送一个SYN包到服务器,这个包中携带了Fast Open Cookie请求的TCP选项;
2、服务器生成一个cookie,这个cookie是通过使用密钥加密客户端的IP地址生成的。服务器给客户端发送SYN|ACK响应,在响应包的选项中包含了这个cookie;
3、客户端存储这个cookie以便将来再次与这个服务器的IP建立TFO连接时使用;
也就是说,第一次TCP连接只是交换cookie信息,无法在SYN包中携带数据。在完成上述步骤后,接下来的TCP连接就可以在SYN中携带数据了。流程如下:
1、客户端发送一个携带应用数据和以TCP选项方式存储的Fast Open cookie的SYN包;
2、服务器验证这个cookie,如果合法,服务器发送一个SYN|ACK确认SYN和数据,然后数据被传递到应用进程;如果不合法,服务器丢弃数据,发送一个SYN|ACK只确认SYN,接下来走三次握手的普通流程;
3、如果接收了SYN包中的数据,服务器在接收到客户端的第一个ACK前可以发送其它响应数据;
4、客户端发送ACK确认了服务器的SYN;如果客户端的数据没有被确认,数据会在ACK包中重传;
5、下面的流程与普通的TCP交互流程无异。
客户端使用TFO的方法:内核功能选项sysctl -w net.ipv4.tcp_fastopen=1;客户端代码:
int sockfd, n;
char recvline[4096], sendline[4096];
struct sockaddr_in servaddr;
char buf[20] = {"aaabbbccc"};
int ret = 0;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf ("create socket error: %s(errno: %d)\n", strerror (errno),
errno);
exit (0);
}
memset (&servaddr, 0, sizeof (servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons (6666);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
ret = sendto(sockfd, buf, strlen(buf), MSG_FASTOPEN,
(struct sockaddr *)&servaddr, sizeof(servaddr));
if (ret < 0) {
printf ("send msg error: %s(errno: %d)\n", strerror (errno), errno);
}
close (sockfd);
即客户端在发送数据时,生成socket后直接使用sendto发送数据,不用connect系统调用。第一次交互时只是向服务器申请一个TFO cookie,数据并不在连接建立过程中送达;TFO cookie交互完成后,以后客户端每次用同样方式发送数据时都会在SYN包中携带数据。
服务器端开启TFO功能的方法:内核功能选项sysctl -w net.ipv4.tcp_fastopen=2;服务器端代码:
int listenfd, connfd, fd;
struct sockaddr_in servaddr;
char buff[MAXLINE];
int n;
if ((listenfd = socket (AF_INET, SOCK_STREAM, 0)) == -1) {
printf ("create socket error: %s(errno: %d)\n", strerror (errno), errno);
exit (0);
}
memset (&servaddr, 0, sizeof (servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servaddr.sin_port = htons (6666);
if (bind (listenfd, (struct sockaddr *) &servaddr, sizeof (servaddr)) == -1) {
printf ("bind socket error: %s(errno: %d)\n", strerror (errno), errno);
exit (0);
}
if (listen (listenfd, 10) == -1) {
printf ("listen socket error: %s(errno: %d)\n", strerror (errno), errno);
exit (0);
}
int qlen = 5;
ret = setsockopt(listenfd, 6, TCP_FASTOPEN, &qlen, sizeof(qlen));
if (ret < 0) {
printf ("setsockopt error: %s(errno: %d)\n", strerror (errno), errno);
}
while (1)
{
printf("Before accpet!\n");
if ((connfd = accept (listenfd, (struct sockaddr *) NULL, NULL)) == -1)
...
如果想要同时开启客户端和服务端的TFO功能,可以用“
sysctl -w net.ipv4.tcp_fastopen=3”。
TFO功能在Linux 2.6.34内核中开始集成。
下面通过分析内核代码来了解TFO的运行机制。开启TFO功能后,server端进程在调用listen系统调用时会初始化TFO队列:
195 int inet_listen(struct socket *sock, int backlog)
196 {
197 struct sock *sk = sock->sk;
198 unsigned char old_state;
199 int err;
...
214 if (old_state != TCP_LISTEN) {
...
222 if ((sysctl_tcp_fastopen & TFO_SERVER_ENABLE) != 0 &&
223 inet_csk(sk)->icsk_accept_queue.fastopenq == NULL) {
224 if ((sysctl_tcp_fastopen & TFO_SERVER_WO_SOCKOPT1) != 0)
225 err = fastopen_init_queue(sk, backlog);
226 else if ((sysctl_tcp_fastopen &
227 TFO_SERVER_WO_SOCKOPT2) != 0)
228 err = fastopen_init_queue(sk,
229 ((uint)sysctl_tcp_fastopen) >> 16);
230 else
231 err = 0;
232 if (err)
233 goto out;
234 }
235 err = inet_csk_listen_start(sk, backlog);
...
fastopen_init_queue函数:
373 static inline int fastopen_init_queue(struct sock *sk, int backlog)
374 {
375 struct request_sock_queue *queue =
376 &inet_csk(sk)->icsk_accept_queue;
377
378 if (queue->fastopenq == NULL) {
379 queue->fastopenq = kzalloc(
380 sizeof(struct fa