【翻译】TCP backlog在Linux中的工作原理
原文How TCP backlog works in Linux
水平有限,難免有錯,歡迎指出!
以下為翻譯:
當應(yīng)用程序通過系統(tǒng)調(diào)用listen將一個套接字(socket)置為LISTEN狀態(tài)時,需要為該套接字指定一個backlog參數(shù),該參數(shù)通常被描述為用來限制進來的連接隊列長度(queue of incoming connections)。
由于TCP協(xié)議的三次握手機制,一個進來的套接字連接在進入ESTABLISHED狀態(tài)并且可以被accept調(diào)用返回給應(yīng)用程序之前,會經(jīng)歷中間狀態(tài)SYN RECEIVED(見上圖)。這意味著TCP協(xié)議棧可以有兩種方案來實現(xiàn)backlog隊列:
歷史上,BCD派生的TCP實現(xiàn)采用第一種方案,這意味著當隊列大小達到backlog最大值時,系統(tǒng)將不再發(fā)送用以響應(yīng)SYN數(shù)據(jù)包的SYN/ACK數(shù)據(jù)包。通常,TCP實現(xiàn)將簡單地丟棄收到的SYN數(shù)據(jù)包(而不是發(fā)送RST數(shù)據(jù)包)以便客戶端重試。這也是W. Richard Stevens的經(jīng)典教科書《TCP/IP詳解 卷三》14.5節(jié)listen Backlog Queue中所描述的方案。
需要注意的是,W. Richard Stevens解釋說BSD的實現(xiàn)實際上確實是使用兩個單獨的隊列,但是它們表現(xiàn)為一個單個隊列,其最大長度固定且由(但不是必須完全等于)backlog參數(shù)確定,即BSD邏輯上如方案1所述。
Linux上的情況有些不同,listen的man手冊寫到:
The behavior of the backlog argument on TCP sockets changed with Linux 2.2. Now it specifies the queue length forcompletely established sockets waiting to be accepted, instead of the number of incomplete connection requests. The maximum length of the queue for incomplete sockets can be set using /proc/sys/net/ipv4/tcp_max_syn_backlog
TCP套接字上的backlog參數(shù)的行為隨Linux 2.2而改變。 現(xiàn)在它指等待被接受(accept)的、完全建立的套接字的隊列長度,而不是不完整的連接請求數(shù)。 不完整套接字隊列的最大長度可以通過/proc/sys/net/ipv4/tcp_max_syn_backlog設(shè)置
這意味著當前Linux版本采用的是具有兩個不同隊列的方案二:一個SYN隊列,大小由系統(tǒng)范圍的設(shè)置指定;一個accept隊列,大小由應(yīng)用程序指定。 方案2一個有趣的問題是,如果當前accept隊列已滿,而一個連接需要從SYN隊列中移到accept隊列中,這個時候TCP實現(xiàn)將如何處理?這種情況由net/ipv4/tcp_minisocks.c中的tcp_check_req函數(shù)處理。 相關(guān)代碼如下:
child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL); if (child == NULL) goto listen_overflow;對于IPV4,第一行代碼實際會調(diào)用net/ipv4/tcp_ipv4.c中的tcp_v4_syn_recv_sock,其中包含如下代碼:
if (sk_acceptq_is_full(sk))goto exit_overflow;上面的代碼中可以看到對accept隊列的檢查。exit_overflow標簽之后的代碼將執(zhí)行一些清理工作,更新/proc/net/netstat中的ListenOverflows和ListenDrops統(tǒng)計信息,然后返回NULL,這將觸發(fā)執(zhí)行tcp_check_req中的listen_overflow代碼:
listen_overflow:if (!sysctl_tcp_abort_on_overflow) {inet_rsk(req)->acked = 1;return NULL;}這意味著除非/proc/sys/net/ipv4/tcp_abort_on_overflow設(shè)置為1(這種情況下將如代碼所示發(fā)送RST數(shù)據(jù)包),否則TCP實現(xiàn)基本上不做任何事情!
總而言之,如果Linux中的(服務(wù)端)TCP實現(xiàn)接收到(客戶端)三次握手的ACK數(shù)據(jù)包,并且accept隊列已滿,則(服務(wù)端)基本上將忽略該數(shù)據(jù)包。這種處理方式剛聽起來可能有點奇怪,但是請記住,SYN RECEIVED狀態(tài)有一個關(guān)聯(lián)定時器:如果服務(wù)端沒有收到ACK(或者像這里所說的被忽略),則TCP實現(xiàn)將重新發(fā)送 SYN/ACK數(shù)據(jù)包(重試次數(shù)由/proc/sys /net/ipv4/tcp_synack_retries指定,并使用指數(shù)退避算法)。
上述現(xiàn)象可以在以下數(shù)據(jù)包跟蹤中看到,客戶端嘗試連接(并發(fā)送數(shù)據(jù))到已達到其最大backlog的套接字:
0.000 127.0.0.1 -> 127.0.0.1 TCP 74 53302 > 9999 [SYN] Seq=0 Len=0
0.000 127.0.0.1 -> 127.0.0.1 TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
0.000 127.0.0.1 -> 127.0.0.1 TCP 66 53302 > 9999 [ACK] Seq=1 Ack=1 Len=0
0.000 127.0.0.1 -> 127.0.0.1 TCP 71 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
0.207 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
0.623 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
1.199 127.0.0.1 -> 127.0.0.1 TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
1.199 127.0.0.1 -> 127.0.0.1 TCP 66 [TCP Dup ACK 6#1] 53302 > 9999 [ACK] Seq=6 Ack=1 Len=0
1.455 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
3.123 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
3.399 127.0.0.1 -> 127.0.0.1 TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
3.399 127.0.0.1 -> 127.0.0.1 TCP 66 [TCP Dup ACK 10#1] 53302 > 9999 [ACK] Seq=6 Ack=1 Len=0
6.459 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
7.599 127.0.0.1 -> 127.0.0.1 TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
7.599 127.0.0.1 -> 127.0.0.1 TCP 66 [TCP Dup ACK 13#1] 53302 > 9999 [ACK] Seq=6 Ack=1 Len=0
13.131 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
15.599 127.0.0.1 -> 127.0.0.1 TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
15.599 127.0.0.1 -> 127.0.0.1 TCP 66 [TCP Dup ACK 16#1] 53302 > 9999 [ACK] Seq=6 Ack=1 Len=0
26.491 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
31.599 127.0.0.1 -> 127.0.0.1 TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
31.599 127.0.0.1 -> 127.0.0.1 TCP 66 [TCP Dup ACK 19#1] 53302 > 9999 [ACK] Seq=6 Ack=1 Len=0
53.179 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
106.491 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
106.491 127.0.0.1 -> 127.0.0.1 TCP 54 9999 > 53302 [RST] Seq=1 Len=0
客戶端TCP實現(xiàn)由于收到多個SYN/ACK數(shù)據(jù)包,會假定其發(fā)送的ACK數(shù)據(jù)包丟失,從而重新發(fā)送ACK(參見上述跟蹤中的TCP Dup ACK行)。
如果服務(wù)器端的應(yīng)用程序在達到SYN/ACK最大重試次數(shù)之前減少了backlog(即從accept隊列中消耗了一個條目),則TCP實現(xiàn)最終將會處理一個客戶端重復(fù)發(fā)送的ACK,將連接狀態(tài)從SYN RECEIVED轉(zhuǎn)換到ESTABLISHED,并將連接添加到accept隊列。否則的話,客戶端最終會收到一個RST數(shù)據(jù)包(如上圖所示)。
數(shù)據(jù)包跟蹤同時也展示了上述行為另一個有趣的一面。從客戶端的角度來說,TCP連接將在收到第一個SYN/ACK數(shù)據(jù)包之后變?yōu)镋STABLISHED狀態(tài)。如果客戶端向服務(wù)端發(fā)送數(shù)據(jù)(不等待來自服務(wù)端的數(shù)據(jù)),則該數(shù)據(jù)也會被重傳。幸運的是,TCP的慢啟動可以限制重傳階段發(fā)送的數(shù)據(jù)段個數(shù)。
另一方面,如果客戶端一直在等待來自服務(wù)端的數(shù)據(jù),而服務(wù)端的backlog一直沒有降低,則最終的結(jié)果是客戶端的連接狀態(tài)是ESTABLISHED,而服務(wù)端的連接狀態(tài)則是SYN_RCVD(注:原文說的是CLOSED狀態(tài),應(yīng)該是不對的),也就是處于一個半連接的狀態(tài)!
還有另一個方面我們目前沒有討論。listen的man手冊引用表明,除非SYN隊列已滿,否則每個SYN數(shù)據(jù)包都將導(dǎo)致TCP連接被添加到SYN隊列中,這種說法和實際情況有所出入,原因在于net/ipv4/tcp_ipv4.c中的tcp_v4_conn_request函數(shù)存在以下一段代碼:
/* Accept backlog is full. If we have already queued enough * of warm entries in syn queue, drop request. It is better than * clogging syn queue with openreqs with exponentially increasing * timeout. */ if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) { NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS); goto drop; }上面的代碼意味著,如果當前accept隊列已滿,內(nèi)核會對接收SYN數(shù)據(jù)包的速率施加限制。如果收到太多SYN數(shù)據(jù)包其中一些將會被丟棄,這將導(dǎo)致客戶端重試發(fā)送SYN數(shù)據(jù)包,從而最終得到與BSD派生實現(xiàn)中相同的行為。
最后看看為什么Linux的設(shè)計選擇會優(yōu)于傳統(tǒng)的BSD實現(xiàn)。 Stevens提出了以下有趣的觀點:
The backlog can be reached if the completed connection queue fills (i.e., the server process or the server host is so busy that the process cannot call accept fast enough to take the completed entries off the queue) or if the incomplete connection queue fills. The latter is the problem that HTTP servers face, when the round-trip time between the client and server is long, compared to the arrival rate of new connection requests, because a new SYN occupies an entry on this queue for one round-trip time. […]
The completed connection queue is almost always empty because when an entry is placed on this queue, the server’s call to accept returns, and the server takes the completed connection off the queue.
Stevens提出的解決方案只是增加backlog。這樣做的問題在于它假定如果應(yīng)用程序希望調(diào)整backlog,不僅要考慮如何處理新建立的傳入連接,還有考慮諸如往返時間等流量特性。Linux中的實現(xiàn)有效地分離了這兩個問題:應(yīng)用程序只負責(zé)調(diào)整backlog,使其可以足夠快地接收(accept)連接,避免填滿accept隊列; 系統(tǒng)管理員則可以根據(jù)流量特性調(diào)整/proc/sys/net/ipv4/tcp_max_syn_backlog
轉(zhuǎn)載于:https://www.cnblogs.com/sduzh/p/6654225.html
總結(jié)
以上是生活随笔為你收集整理的【翻译】TCP backlog在Linux中的工作原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux 命令学习笔记
- 下一篇: Optional变量初学者指南