关于TCP 半连接队列和全连接队列

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于TCP 半连接队列和全连接队列相关的知识,希望对你有一定的参考价值。

环境centos7内核版本3.10.0-327.el7.x86_64、nginx1.10.3

一、先来回顾下三次握手里面涉及到的问题:

Linux内核协议栈为一个tcp连接管理使用两个队列,一个是半链接队列(用来保存处于SYN_SENT和SYN_RECV状态的请求),一个是accpetd队列(用来保存处于established状态,但是应用层没有调用accept取走的请求)。

1.半连接队列 syn squeue roundup_pow_of_two(max_t(u32,min(somaxconn,sysctl_max_syn_backlog,backlog),8) +1) 用来保存 SYN_SENT 以及 SYN_RECV 的信息。

2.全连接队列 accept queue(min(somaxconn, backlog)), 保存 ESTAB 的状态。

技术分享

a. tcp_max_syn_backlog参数位于/proc/sys/net/ipv4/tcp_max_syn_backlog,默认是128,可以通过/etc/sysctl.conf文件调整。

b. somaxconn参数位与/proc/sys/net/core/somaxconn,默认是128, 表示最多有129个established链接等待accept。可以通过/etc/sysctl.conf文件调整。

c. backlog参数这个和具体的应用程序有关,比如nginx默认为511

这个可以通过 ss -lnt 的 Send-Q 确认:
State      Recv-Q Send-Q        Local Address:Port          Peer Address:Port
LISTEN     0      511                       *:80                       *:*    

可以通过适当的增大 nginx 的 backlog 以及 somaxconn 来增大队列:
listen 80 backlog=1638;

Tomcat默认为100,也可通过server.xml中的<Connector acceptCount="300"/>来调整

ss获取到的 Recv-Q/Send-Q 在 LISTEN 状态以及非 LISTEN 状态所表达的含义是不同的。从 / source/net/ipv4/tcp_diag.c源码中可以看到二者的区别:

技术分享

LISTEN 状态: Recv-Q 表示的当前等待服务端调用 accept 完成三次握手的 listen backlog 数值,也就是说,当客户端通过 connect() 去连接正在 listen() 的服务端时,这些连接会一直处于这个 queue 里面直到被服务端 accept();Send-Q 表示的则是最大的 listen backlog 数值,这就就是上面提到的 min(backlog, somaxconn) 的值。

其余状态: 非 LISTEN 状态。Recv-Q 表示 receive queue 中的 bytes 数量;Send-Q 表示 send queue 中的 bytes 数值

当用户进程调用服务器程序的listen()时,内核将创建一个队列来存储积压连接。

listen() -> inet_listen() -> inet_csk_listen_start() -> reqsk_queue_alloc()

二、我们先来看半连接数,内核版本为3.10.0-327.el7.x86_64

  首先定位到tcp_v4_conn_request函数,        (source/net/ipv4/tcp_ipv4.c)

跟进关键函数inet_csk_reqsk_queue_is_full,(source/include/net/inet_connection_sock.h)

跟进关键函数reqsk_queue_is_full,                (/source/include/net/request_sock.h)

查找qlen和max_qlen_log的定义,                  (/source/include/net/request_sock.h)

关键是如何计算max_qlen_log,                         (/source/net/socket.c)

sock->ops->listen其实是inet_listen,               (/source/net/ipv4/af_inet.c)

跟进inet_csk_listen_start,                                (/source/net/ipv4/inet_connection_sock.c)

跟进reqsk_queue_alloc,                                      (/source/net/core/request_sock.c)

然后我们计算一下为何在Server端的SYN_RECV状态数量会是256

nr_table_entries = listen的第二个参数int backlog ,上限是系统的somaxconn
若 somaxconn = 128,max_syn_backlog = 4096, backlog = 511 则nr_table_entries = 128

nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
取两者较小的一个 nr_table_entries = 128

nr_table_entries = max_t(u32, nr_table_entries, 8);
取两者较大的一个 nr_table_entries = 128

nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);   //roundup_pow_of_two - round the given value up to nearest power of two
roundup_pow_of_two(128 + 1) = 256

for (lopt->max_qlen_log = 3;

(1 << lopt->max_qlen_log) < nr_table_entries;

lopt->max_qlen_log++);
max_qlen_log = 8

总结:半连接队列长其实为 half open queue roundup_pow_of_two(max_t(u32,min(somaxconn,sysctl_max_syn_backlog,backlog),8) +1)

判断半连接队列是否满 queue->listen_opt->qlen >> queue->listen_opt->max_qlen_log;
queue->listen_opt->qlen = 256 时reqsk_queue_is_full返回1 , 进入drop
所以queue->listen_opt->qlen 取值 0~255, 因此SYN_RECV状态数量会是 256

三、现在我们再来看看全连接队列

技术分享

查了nginx文档关于ListenBackLog 指令的说明,默认值是511. 可见最终全连接队列(backlog)应该是net.core.somaxconn = 258.

从/source/net/socket.c源码中也可以看出。

技术分享

用慢连接攻击测试观察到虚拟机S的80端口ESTABLISHED=1016。基本等于nginx中的work_connections.

现在增加work_connections到1018,慢连接攻击测试观察到虚拟机S的80端口ESTABLISHED=1278(约等于1018+258).

总结,nginx单线程的情况下worker_processes=1,work_connections 约等于1018,

技术分享

ESTABLISHED基本等于连接数,大于1018的情况下ESTABLISHED的数量要加上backlog;

技术分享

多线程的情况下ESTABLISHED 2倍的work_connections。至于ESTABLISHED会不会加上backlog,

还是看work_connections是否大于1016。

以上是关于关于TCP 半连接队列和全连接队列的主要内容,如果未能解决你的问题,请参考以下文章

TCP 半连接队列和全连接队列

你知道TCP的半连接与全连接队列吗?

TCP 半连接队列和全连接队列

TCP 半连接队列和全连接队列

TCP半连接队列和全连接队列(史上最全)

TCP半连接队列(syns queue)和全连接队列(accept queue)