1. server端调用listen后不accept,client端调用connect发起连接
server在tcp port=54321监听:
通过抓包并使用netstat工具查看,在这种情况下,TCP连接可以正常完成三次握手,建立连接,两端都进入ESTABLISHED状态:
client端:
server端:
然后关闭client进程,抓包可以看到client向server发出了一个FIN,并收到了ACK:
通过netstat查看两端的状态:
client端:
server端:
对于上面出现的各种状态会在后面继续进行分析,下面进一步先来看一下在这种情况下,client连接成功后开始向server端发送数据,又会是什么样的情形?
2. server端调用listen后不accept,client端调用connect连接成功后向server端发送数据
通过抓包并使用netstat工具分析,可以看到这两种情况的流程基本相同,只是在server端稍有区别:
可以看到,在没有进行accept的情况下,server端的Recv-Q竟然收到了数据;
3. accept做了什么?
man手册中描述如下:
The accept() system call is used with connection-based socket types (SOCK_STREAM, SOCK_SEQPACKET). It extracts the first connection request on the queue of pending connections for the listen‐ing socket, sockfd, creates a new connected socket, and returns a new file descriptor referring to that socket. The newly created socket is not in the listening state. The original socket sockfd is unaffected by this call.
可以看到,accept函数的作用是从"pending connections"队列中取出第一个连接,并生成一个新的连接套接字返回给应用程序,用于进行读写操作,并且不会影响原有的监听套接字;
所以,对于两端的内核协议栈而言,在应用程序调用accept函数之前TCP的三次握手过程已经完成,它看到的是一条正常的处于ESTABLISHED状态的连接,只是由于server端没有调用accept,无法获取操作该连接的socket描述符,因而也就无法读取client发来的数据,这些数据会一直存放在协议栈的Recv-Q中;
那么,问题来了,这样的连接一共可以建立多少条呢?继续往下分析;
4. "connection queue"
我们回过头看看listen函数,其原型如下:
int listen(int sockfd, int backlog);
man手册中对backlog参数的解释如下:
DESCRIPTION
The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow. If a connection request arrives when the queue is full, the client may receive an error with an indication of ECONNREFUSED or, if the underlying protocol supports retransmission, the request may be ignored so that a later reattempt at connection succeeds.
NOTES
The behavior of the backlog argument on TCP sockets changed with Linux 2.2. Now it specifies the queue length for completely established sockets waiting to be accepted, instead of the number of incomplete connection requests. The maximum length of the queue for incomplete sockets can be set using /proc/sys/net/ipv4/tcp_max_syn_backlog. When syncookies are enabled there is no logical maximum length and this setting is ignored. See tcp(7) for more information.
If the backlog argument is greater than the value in /proc/sys/net/core/somaxconn, then it is silently truncated to that value; the default value in this file is 128. In kernels before 2.4.25, this limit was a hard coded value, SOMAXCONN, with the value 128.
具体来说,在协议栈的实现中,根据连接的状态划分出了两种类型:
incomplete connection (半开连接,处于SYN_RECV状态,还没有收到最后一个ACK)
completely established socket (已完成连接,处于ESTABLISHED状态)
而listen函数的backlog参数指定的是已完成连接队列的最大长度,在协议栈中定义为:
* @sk_ack_backlog: current listen backlog * @sk_max_ack_backlog: listen backlog set in listen()
经过测试表明,当指定listen函数的backlog参数为5时,服务器端最多可以建立6条ESTABLISHED状态的连接,这是因为内核在判断队列是否已满的时候的实现如下:
static inline bool sk_acceptq_is_full(const struct sock *sk) { return sk->sk_ack_backlog > sk->sk_max_ack_backlog; }
其中,sk_ack_backlog初始化为0;
5. 连接满了怎么办?
当连接数到达sk_max_ack_backlog之后,客户端继续发起连接请求,
可以看到,之后的连接都停留在SYN_RECV状态,抓包可以看到,server端收到了client的最后一个ACK,但仍然会重传SYN/ACK,通过查看协议栈代码可知,此时的行为与sysctl_tcp_abort_on_overflow(/proc/sys/net/ipv4/tcp_abort_on_overflow)的值有关:
当sysctl_tcp_abort_on_overflow为0时(default):compeletly established queue满了之后服务器会丢掉第三个ACK;
否则,直接发RST;
通过上面的分析可知,这些半开连接队列的最大长度由tcp_max_syn_backlog描述,默认为2048;
6. client端的FIN_WAIT2与server端的CLOSE_WAIT
从1.中可以看到,当client端退出后,client和server端的连接分别进入FIN_WAIT2和CLOST_WAIT状态,这是由于client发出了FIN并收到了ACK;
由于server端并没有accept,也没有应用会去关闭这个连接,所以CLOST_WAIT状态的连接会一直挂在那里,直到服务进程退出;
而client端的FIN_WAIT2连接则会在一段时间后超时退出,超时时间可以通过/proc/sys/net/ipv4/tcp_fin_timeout查看;
接下来还可以延伸到TCP的keepalive机制等,后面慢慢探讨。。。