1. server端调用listen后不accept,client端调用connect发起链接服务器
server在tcp port=54321监听:cookie
经过抓包并使用netstat工具查看,在这种状况下,TCP链接能够正常完成三次握手,创建链接,两端都进入ESTABLISHED状态:socket
client端:tcp
server端:函数
而后关闭client进程,抓包能够看到client向server发出了一个FIN,并收到了ACK:工具
经过netstat查看两端的状态:测试
client端:this
server端:code
对于上面出现的各类状态会在后面继续进行分析,下面进一步先来看一下在这种状况下,client链接成功后开始向server端发送数据,又会是什么样的情形?orm
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机制等,后面慢慢探讨。。。