文章目錄
- 一、什么是 TCP 半連接隊列和全連接隊列
- 二、TCP 全連接隊列
- 1、如何查看進程的 TCP 全連接隊列大小?
- 注意
- 2、TCP 全連接隊列溢出問題
- 注意
- 3、TCP 全連接隊列最大長度
- 三、TCP 半連接隊列
- 1、TCP 半連接隊列溢出問題
- 2、TCP 半連接隊列最大長度
- 3、引申問題
一、什么是 TCP 半連接隊列和全連接隊列
TCP 三次握手期間,Linux 內核會維護兩個隊列,分別是:
- 半連接隊列,也稱 SYN 隊列
- 全連接隊列,也稱 Accept 隊列
不管是半連接隊列還是全連接隊列,都有最大長度限制
二、TCP 全連接隊列
1、如何查看進程的 TCP 全連接隊列大小?
# -l:顯示正在監聽(listening)的 socket
# -n:不解析服務名稱
# -t:只顯示 tcp socket
$ ss -lnt | grep 55535
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 5 192.168.5.28:55535 *:*
注意
ss 命令的 Recv-Q/Send-Q 在「LISTEN 狀態」和「非 LISTEN 狀態」所表達的含義是不同的
/* Linux Kernel 2.6.32 tcp_diag.c */
static void tcp_diag_get_info( ... )
{.../* LISTEN 狀態 */if (sk->sk_state == TCP_LISTEN) {/* 當前全連接隊列大小 */ r->idiag_rqueue = sk->sk_ack_backlog;/* 當前全連接隊列最大長度 */r->idiag_wqueue = sk->sk_max_ack_backlog;} else {/* 已收到但未被進程讀取的字節數 */r->idiag_rqueue = tp->rcv_nxt - tp->copied_seq;/* 已發送但未收到確認的字節數 */r->idiag_wqueue = tp->write_seq - tp->snd_una;}...
}
「LISTEN 狀態」的 Recv-Q/Send-Q 含義如下:
- Recv-Q:當前全連接隊列大小
- Send-Q:當前全連接隊列最大長度
2、TCP 全連接隊列溢出問題
Linux 有個參數(tcp_abort_on_overflow),可以指定在 TCP 全連接隊列滿了的情況下使用什么策略回應客戶端
/proc/sys/net/ipv4/tcp_abort_on_overflow 共有兩個值:
- 0:全連接隊列滿了的情況下,服務端丟棄客戶端發來的 ACK 報文
- 1:全連接隊列滿了的情況下,服務端回給客戶端 RST 報文
注意
通常情況下,應該把參數 tcp_abort_on_overflow 設為 0,這樣有利于應對突發流量
例如,TCP 全連接隊列滿了的情況下,服務端丟棄客戶端發來的 ACK 報文,但此時客戶端的連接狀態卻是 ESTABLISHED,客戶端可以在建立好的 TCP 連接上發出請求,只要服務端沒有對請求回復 ACK 應答,那么請求就會被客戶端重發,如果服務端只是因為短暫的繁忙致使全連接隊列滿了,那么當全連接隊列有空位時,再次接收到的請求報文中由于含有 ACK 標志,仍會觸發服務端建立 TCP 連接,所以,tcp_abort_on_overflow=0 可以提高連接建立的成功率
3、TCP 全連接隊列最大長度
min(somaxconn,backlog),其中,somaxconn 是 Linux 內核參數,可以通過 /proc/sys/net/core/somaxconn 調整,而 backlog 是 listen() 函數參數
/* Linux Kernel 2.6.32 socket.c */
SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{.../* /proc/sys/net/core/somaxconn */somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;/* TCP 全連接隊列最大長度 */if ((unsigned)backlog > somaxconn)backlog = somaxconn;...
}
三、TCP 半連接隊列
1、TCP 半連接隊列溢出問題
- 如果半連接隊列滿了,并且沒有開啟 tcp_syncookies,丟棄
- 如果全連接隊列滿了,并且沒有重傳 SYN+ACK 的連接請求多于 1 個,丟棄
- 如果沒有開啟 tcp_syncookies,并且 tcp_max_syn_backlog - 當前半連接隊列長度 < (tcp_max_syn_backlog >> 2),丟棄
/* Linux Kernel 2.6.32 tcp_ipv4.c* TCP 第一次握手的 Linux 內核代碼 */
int tcp_v4_conn_request( ... )
{.../* 條件 1* 如果半連接隊列滿了*/if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
#ifdef CONFIG_SYN_COOKIESif (sysctl_tcp_syncookies) {want_cookie = 1;} else
#endif/* 如果半連接隊列滿了,并且沒有開啟 tcp_syncookies,丟棄 */goto drop;}/* 條件 2* 如果全連接隊列滿了,并且沒有重傳 SYN+ACK 的連接請求多于 1 個,丟棄*/if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)goto drop;...if (want_cookie) {...} else if (!isn) {...if ( ... ) {...}/* 條件 3* 如果沒有開啟 tcp_syncookies,* 并且 tcp_max_syn_backlog - 當前半連接隊列長度 < (tcp_max_syn_backlog >> 2),* 丟棄*/else if (!sysctl_tcp_syncookies && sysctl_max_syn_backlog - inet_csk_reqsk_queue_len(sk) <(sysctl_max_syn_backlog >> 2)) && ...) {...goto drop_and_release;}...}...
}
2、TCP 半連接隊列最大長度
tcp_max_syn_backlog < min(somaxconn,backlog) ? tcp_max_syn_backlog * 2 : min(somaxconn,backlog) * 2,其中,tcp_max_syn_backlog 是 Linux 內核參數,可以通過 /proc/sys/net/ipv4/tcp_max_syn_backlog 調整
3、引申問題
半連接隊列最大長度并不代表服務端處于 SYN_RCVD 狀態的最大個數
我們在上述內容中總結過 TCP 第一次握手報文被丟棄的三種條件:
- 如果半連接隊列滿了,并且沒有開啟 tcp_syncookies,丟棄
- 如果全連接隊列滿了,并且沒有重傳 SYN+ACK 的連接請求多于 1 個,丟棄
- 如果沒有開啟 tcp_syncookies,并且 tcp_max_syn_backlog - 當前半連接隊列長度 < (tcp_max_syn_backlog >> 2),丟棄
假設條件一不成立,也就是當前半連接隊列長度沒有超,而后的條件二也不成立,一旦滿足條件三,”半連接隊列最大長度并不代表服務端處于 SYN_RCVD 狀態的最大個數“ 結論正確,例如 somaxconn = 128, backlog = 511,tcp_max_syn_backlog = 256,計算出半連接隊列最大長度是 256,但是按照假設,一旦當前半連接隊列長度 > tcp_max_syn_backlog - (tcp_max_syn_backlog >> 2),也就是 256 - 64 = 192,SYN 報文就會被丟棄,那么此時處于 SYN_RCVD 狀態的最大個數是 193,而非 256