在启动测试程序之前,在客户端使用tcpdump抓包,并将输出结果通过-w选项存储在192.cap文件中,便于后续使用wireshark来分析。
测试发现,在客户端建立300个连接后,客户端建立连接的速度明显慢了很多,而且最终建立完1000个连接花了20分钟左右。使用wireshark打开192.cap文件,来看抓包的情况,发现在300个连接之后有大量的ack包重传,如下图所示:
在wireshark的过滤器中选择本地端口为49274的连接来具体分析,该连接抓包情况如下所示:
上面的图中可以看到,SYN包重传了一次;在正常的三次握手之后,服务器又发送了SYN+ACK包给客户端,导致客户段再次发送ACK,而且这个过程重复了5次。在wireshark中过滤其他连接,发现情况也是如此。
问题来了,为什么要重传SYN包?为什么在三次握手之后,服务器端还要重复发送SYN+ACK包?为什么重复了5次之后就不再发了呢?要解答这些问题,我们需要深入到内核代码中看三次握手过程中内核是如何处理的,以及在连接队列满之后是怎么处理。内核中处理客户端发送的SYN包是在tcp_v4_conn_request()函数中,关键代码如下所示:
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
......
if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
#ifdef CONFIG_SYN_COOKIES
if (sysctl_tcp_syncookies) {
want_cookie = 1;
} else
#endif
goto drop;
}
/* Accept backlog is full. If we have already queued enough
* of warm entries in syn queue, drop request. It is better than
* clogging syn queue with openreqs with exponentially increasing
* timeout.
*/
if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)
goto drop;
req = inet_reqsk_alloc(&tcp_request_sock_ops);
if (!req)
goto drop; ......
if (__tcp_v4_send_synack(sk, req, dst) || want_cookie)
goto drop_and_free;
inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);
return 0;
drop_and_release:
dst_release(dst);
drop_and_free:
reqsk_free(req);
drop:
return 0;
}