首先我們必須明白,處於「listening」狀態的tcp socket,有兩個獨立的佇列:
這兩個術語有時也被稱為「reqsk_queue」,「ack backlog」,「listen backlog」,甚至「tcp backlog」,但是這篇文章中我們使用上面兩個術語以免造成混淆。
syn佇列儲存了收到syn包的連線(對應核心**的結構體:struct inet_request_sock)。它的職責是回覆syn+ack包,並且在沒有收到ack包時重傳,直到超時。在linux下,重傳的次數為:
1文件中對tcp_synack_retries的描述如下:$ sysctl net.ipv4.tcp_synack_retries
2 net.ipv4.tcp_synack_retries = 5
1 tcp_synack_retries -int整型23傳送完syn+ack之後,syn佇列等待從客戶端發出的ack包(也即三次握手的最後乙個包)。當收到ack包時,首先找到對應的syn佇列,再在對應的syn佇列中檢查相關的資料看是否匹配,如果匹配,核心將該連線相關的資料從syn佇列中移除,建立乙個完整的連線(對應核心**的結構體:struct inet_sock),並將這個連線加入accept佇列。對於乙個被動tcp連線,重傳synacks的次數。該值不能超過255。
4預設值為5,如果初始rto是1秒,那麼對應的最後一次重傳是31秒。
5 對應的最後一次超時是63秒之後。
accept佇列中存放的是已建立好的連線,也即等待被上層應用程式取走的連線。當程序呼叫accept(),這個socket從佇列中取出,傳遞給上層應用程式。
這就是linux處理syn包的乙個簡單描述。順便一提,當socket開啟了tcp_defer_accept
和tcp_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假如程式呼叫accept()不夠快?還可以通過我們的systemtap指令碼來觀察這個資料:resq.stp2119
3 $ ss -n state syn-recv sport = :443 | wc -l
478
如果程式呼叫accept()不夠快會發生什麼呢?
發生這種情況時,我們只能寄希望於程式的處理效能稍後能恢復正常,客戶端重新傳送被服務端丟棄的包。
核心的這種表現對於大部分服務來說是可接受的。順便一提,可以通過調整net.ipv4.tcp_abort_on_overflow
這個全域性引數來修改這種表現,但是最好還是不要改這個引數。
可以通過檢視nstat的計數來觀察accept佇列溢位的狀態:
1 $ nstat -az tcpextlistendrops但是這是乙個全域性的計數。觀察起來不夠直觀,比如有時我們觀察到它在增長,但是所有的服務程式看起來都是正常的。此時我們可以使用ss命令來觀察單個監聽埠的accept佇列大小:2 tcpextlistendrops 49199
0.0
1 $ ss -plnt sport = :6443|cat2 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通過上面的操作,可以觀察到哪些syn包被listendrops影響了。從而我們也就可以知道哪些程式在丟失連線。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
...
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...