Linux-服务器的全连接队列(Accept队列)
分析了服务器Listen的底层细节,其中也分析了Listen系统调用的backlog参数,其决定了服务器Listen过程中全连接队列(Accept队列)的最大长度。本文将更进一步分析全连接队列(Accept队列)以及backlog参数是如何影响中全连接队列(Accept队列)的,并通过小实验直观了解backlog参数对全连接队列(Accept队列)的影响。
全连接队列存储3次握手成功并已建立的连接,将其称为全连接队列,也可称为接收队列(Accept队列),本文中的描述将称为Accept队列或全连接队列。如下红框中所示,全连已成功建立三次握手,当前的TCP状态为ESTABLISHED,但是服务端还未Accept的队列。
那么这全连接队列 (Accept队列) 在Linux内核中用什么数据结构进行表示?
在介绍Accept队列前先看一下连接请求块:存储相关连接请求的队列的结构体
连接请求块的存储队列是对SYN同步队列(半连接)队列(服务端收到客户端SYN请求并回复SYN+ACK的队列)、接收(全连接)队列的描述。在Linux内核中使用 request_sock_queue 进行表示,如下结构体所示:
由连接请求块-存储队列的结构体可以看到全连接队列-Accept队列由struct request_sock结构体进行表示,如下所示,服务器端收到SYN请求之后,内核会建立连接请求块(req)
结构体成员变量request_sock *dl_next指向队列中下一个Accept队列节点,Accept队列与存储队列直接的关系如下图所示:
从上面的分析也可以看出来答案,内核中backlog变量的最终取值是Listen系统调用传入的backlog与系统默认值两者之间的最小值, 所以在Listen时backlog的需求超过系统默认值128时,需要修改系统默认值以满足更大的需求。
syn_recv_sock对应的回调函数首先是对Accept队列进行判断:当前的Accept队列是否满,未满的情况下才会去创建子套接口
服务器Accept获取Accept队列的请求套接口,并删除该请求套接口时
并且当服务执行accept后,accept将返回已建立的连接,此时需要删除该请求套接口,删除过程如下:
函数reqsk_queue_remove为简单的链表移除单个元素的操作,rskq_accept_head为链表的头,注意ACCEPT队列总是从头部开始移除队列中的子套接口元素,即用户层的accept操作总是取走队列中的第一个子套接口,如下图所示,绿色的线即头部重新指向被移除的next元素。
3、编写客户端程序:要求向服务端发起多次连接(大于6次),使用Go语言编写的客户端程序如下,并发10个去连接服务端
Recv-Q:当前全连接队列的大小,也就是当前已完成三次握手并等待服务端 accept 的 TCP 连接;
Send-Q:当前全连接最大队列长度(从0开始计数),上面服务器的最大全连接长度为6(0~5);
可以看到服务端 127.0.0.1:5200的Send-Q为5(0~5),即最大全连接长度为6,Recv-Q是当前的Accept队列的长度为6。10个并行连接只有6个成功完成3次握手,剩下4个都未完成三次握手。说明TCP 全连接队列过小,就容易溢出,当发生 TCP 全连接队溢出的时候,后续的请求就会被丢弃。