TCP的SYN佇列和Accept佇列

2022-03-16 03:57:48 字數 3207 閱讀 6927

首先我們必須明白,處於「listening」狀態的tcp socket,有兩個獨立的佇列:

這兩個術語有時也被稱為「reqsk_queue」,「ack backlog」,「listen backlog」,甚至「tcp backlog」,但是這篇文章中我們使用上面兩個術語以免造成混淆。

syn佇列儲存了收到syn包的連線(對應核心**的結構體:struct inet_request_sock)。它的職責是回覆syn+ack包,並且在沒有收到ack包時重傳,直到超時。在linux下,重傳的次數為:

1

$ sysctl net.ipv4.tcp_synack_retries

2 net.ipv4.tcp_synack_retries = 5

文件中對tcp_synack_retries的描述如下:

1 tcp_synack_retries -int整型23

對於乙個被動tcp連線,重傳synacks的次數。該值不能超過255。

4預設值為5,如果初始rto是1秒,那麼對應的最後一次重傳是31秒。

5 對應的最後一次超時是63秒之後。

傳送完syn+ack之後,syn佇列等待從客戶端發出的ack包(也即三次握手的最後乙個包)。當收到ack包時,首先找到對應的syn佇列,再在對應的syn佇列中檢查相關的資料看是否匹配,如果匹配,核心將該連線相關的資料從syn佇列中移除,建立乙個完整的連線(對應核心**的結構體:struct inet_sock),並將這個連線加入accept佇列。

accept佇列中存放的是已建立好的連線,也即等待被上層應用程式取走的連線。當程序呼叫accept(),這個socket從佇列中取出,傳遞給上層應用程式。

這就是linux處理syn包的乙個簡單描述。順便一提,當socket開啟了tcp_defer_accepttcp_fastopen時,工作方式將會有細微不同,本文不做介紹。

應用程式通過呼叫系統呼叫listen(2),傳入backlog引數,來設定syn佇列和accept佇列的最大大小。比如下面這樣,將syn佇列和accept佇列的最大大小同時設定為1024:

1 listen(sfd, 1024)
注意,在4.3版本之前的核心,syn佇列的大小是用另一種方式計算。

syn佇列的最大大小以前是用net.ipv4.tcp_max_syn_backlog來配置,但是現在已經不再使用了。現在用net.core.somaxconn來同時表示syn佇列和accept佇列的最大大小。在我們的伺服器上,我們將它設定為16k:

1

$ sysctl net.core.somaxconn

2 net.core.somaxconn = 16384

知道了上面這些資訊後,你可能會問,佇列設定為多大合適?佇列設定為多大合適

答案是:看情況。對於大多數的tcp服務來說,這並不太重要。比如,go語言1.11版本之前,並沒有提供設定佇列大小的方法。

儘管如此,也存在一些合理的原因,需要增大佇列的大小:

但是,將backlog設定的過大也會帶來不好的影響:

linux下,如果想檢視syn佇列的當前狀態,我們可以使用ss命令來查詢syn-recv狀態的socket。比如如下執行結果,表示80埠的syn佇列中當前有119個元素,443埠則為78。

1 $ ss -n state syn-recv sport = :80 | wc -l

2119

3 $ ss -n state syn-recv sport = :443 | wc -l

478

假如程式呼叫accept()不夠快?還可以通過我們的systemtap指令碼來觀察這個資料:resq.stp

如果程式呼叫accept()不夠快會發生什麼呢?

發生這種情況時,我們只能寄希望於程式的處理效能稍後能恢復正常,客戶端重新傳送被服務端丟棄的包。

核心的這種表現對於大部分服務來說是可接受的。順便一提,可以通過調整net.ipv4.tcp_abort_on_overflow這個全域性引數來修改這種表現,但是最好還是不要改這個引數。

可以通過檢視nstat的計數來觀察accept佇列溢位的狀態:

1 $ nstat -az tcpextlistendrops

2 tcpextlistendrops 49199

0.0

但是這是乙個全域性的計數。觀察起來不夠直觀,比如有時我們觀察到它在增長,但是所有的服務程式看起來都是正常的。此時我們可以使用ss命令來觀察單個監聽埠的accept佇列大小:

1 $ ss -plnt sport = :6443|cat

2 state recv-q send-q local address:port peer address:port

3 listen 0

1024 *:6443 *:*

recv-q這一列顯示的是處於accept佇列中的socket數量,send-q顯示的是佇列的最大大小。在上面的例子中,我們發現並沒有未被程式accept()的socket,但是我們依然發現listendrops計數在增長。

這是因為我們的程式只是週期性的短暫卡住不處理新的連線,而非永久性的不處理,過段時間程式又恢復了正常。這種情況下,用ss命令比較難觀察這種現象,因此我們寫了乙個systemtap指令碼,它會hook進核心,把被丟棄的syn包列印出來:

$ sudo stap -v acceptq.stp

time

(us) acceptq qmax local addr remote_addr

1495634198449075

1025

1024

0.0.0.0:6443

10.0.1.92:28585

1495634198449253

1025

1024

0.0.0.0:6443

10.0.1.92:50500

1495634198450062

1025

1024

0.0.0.0:6443

10.0.1.92:65434

...

通過上面的操作,可以觀察到哪些syn包被listendrops影響了。從而我們也就可以知道哪些程式在丟失連線。

TCP的SYN報文可以攜帶payload嗎?

對於敲門服務,是不是厭倦了複雜繁瑣的服務端配置?send tcp syn packet with payload?眾所周知,tcp的syn報文是不能攜帶payload的,因為 等等,等等 煩透了!在過程中,我非常討厭去討論標準,討厭有人在耳旁叨叨類似 rfc規定 但沒有強制 intel手冊 但是 正...

TCP半連線佇列和全連線佇列

半連線佇列 syn queue 全連線佇列 accept queue ss lnt recv q send q local address port peer address port 0 100 8080 當連線處於時listen狀態,send q表示accept queue的最大值,recv q...

迴圈佇列的入佇列和出佇列

如果希望迴圈佇列中的元素都能得到利用,則需要設定乙個標誌域 tag,並以 tag 的值為0或1來區分,尾指標和頭指標相同時的佇列狀態是 空 還是 滿 試編寫與此結構相應的入佇列和出佇列的演算法。include include define maxsize 10 typedef struct queu...