關於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 半連接隊列和全連接隊列