tcp的全连接队列和半连接队列满时,客户端再connect发生的情况
首先简单介绍下tcp的全连接队列(accept queue)和半连接队列(syn queue),
1.客户端发起syn请求时,服务端收到该请求,将其放入到syn queue,然后回复ack+syn给客户端。
2.客户端收到ack+syn,再发送ack给服务端。
3. 服务端从syn queue中查找对应记录,完成连接建立,并且将此记录从半连接队列中删除,然后将建 立的连接塞入到accept queue。
上面就是三次握手建立连接的过程,连接建立后,服务端调用accept从accept queue中取出一条连接。
如果服务端发现accept queue满了后,无法塞入,会出现什么情况呢。
为模拟这种情况,可以先将服务端的accept函数注释掉,这样accept queue中的连接无法被消费,从而很快就满了,而且为了快速达到效果,本人在sysctl.conf中,设置net.core.somaxconn=2,即全连接队列的长度为2.
现在100个客户端程序同时执行,如下所示,可以看到服务端的ESTABLISHED的数量是3,不是2。
为何ESTABLISHED的数量是3,不是2,即刚好比全连接队列的长度大1,这里本人给出自己的看法,即在最后一个连接(即第3个)建立后,再往accept队列塞的时候,发现队列已经满了,所以就不塞了,但是连接已经建立好了。所以数目是队列的长度+1。
可以看出,全连接队列满了后,会出现SYN_RECV的状态,此状态的出现其实标志着全连接队列已经满了。
再过一段时间,可以发现SYN_RECV的状态消失,如下所示:
本人的sysctl.conf的内容如下:
net.ipv4.tcp_max_syn_backlog=2 net.ipv4.tcp_abort_on_overflow=0 net.core.somaxconn=2 fs.suid_dumpable=1 vm.swappiness=100 net.ipv4.tcp_synack_retries = 8
其中tcp_max_syn_backlog是半连接队列大小,也是2,但是可以看到上面的SYN_RECV的状态数目不是2,也不是3。这块tcp确实设计的很复杂,硬去理解比较头疼,暂时可以不用去管。
上面SYN_RECV和ESTABLISHED的数量是11,剩余的80多个连接,最终由于connect超时而失败。
下面解释下为何SYN_RECV存在一段时间后,最终都消失了。
首先全连接队列满了后,客户端在发起一次SYN后,客户端将此连接放入syn queue;然后发送ack+syn给客户端,客户端ack给服务端。
此时服务端发现全连接队列已经满了,然后就不再认为客户端的ack有效,其重新发送ack+syn给客户端,并且此次发送的ack+syn的序列号(seq)跟之前一样。
之后客户端收到ack+syn,再发送ack给服务端,如此反复若干次,服务端ack+syn重复的次数跟net.ipv4.tcp_synack_retries有关,该次数默认是5。
服务端这样做的目的在于缓冲,避免一下子上来一堆连接,导致连接队列满,后面再连接直接失败。
如上图所示,10.0.0.60是客户端,64是服务端,注意Time这一列,看上面的时间,6,7,9,13,22,38,70,134。
可以很快算出间隔为1,2,4,8,16,32,64。
即每次客户端ack后,服务端若判断客户端ack无效,则其再次发送ack+syn的间隔时间要在上次的基础上翻倍。
最后一次,服务端发现全连接队列依然是满的,则认为再给客户端发ack+syn失去了意义,所以将此半连接也删掉,从而看不到SYN_RECV状态了。
下面给出客户端和服务端程序的代码,
服务端的代码如下:
#include #include #include #include #include #include #include #define PORT 8888 //侦听端口地址:8888 #define BACKLOG 20 //侦听队列长度:2 extern void process_conn_server(int s); //服务器对客户端的处理:读取数据并发送响应字符 int main(int argc,char *argv[]) { int ss = 0; //ss = server socket = 服务器socket描述符 int cs = 0; //cs = client socket = 客户端socket描述符 struct sockaddr_in server_addr; //服务器地址结构 struct sockaddr_in client_addr; //客户端地址结构 int ret = 0; //返回值 pid_t pid; //进程ID char buffer[1024]; ssize_t size = 0; /** * Step 2 : 建立套接字 */ ss = socket(AF_INET,SOCK_STREAM,0); //创建一个AF_INET族的流类型socket if(ss客户端的代码如下:
#include #include #include #include #include #include #include #include #include #define PORT 8888 //端口地址:8888 #define SERVER_IP "10.0.0.64" extern void process_conn_client(int s); int main(int argc,char *argv[]) { int s = 0; //socket描述符 struct sockaddr_in server_addr; //服务器地址结构 int ret = 0; //返回值 char buf[1024] = {0}; int i = 0; strcpy(buf, "hello world"); /** * Step 2 : 建立套接字 */ s = socket(AF_INET,SOCK_STREAM,0); //创建一个AF_INET族的流类型socket if(s