【翻译】TCP backlog在Linux中的工作原理
原文How TCP backlog works in Linux
水平有限,難免有錯,歡迎指出!
以下為翻譯:
當應用程序通過系統調用listen將一個套接字(socket)置為LISTEN狀態時,需要為該套接字指定一個backlog參數,該參數通常被描述為用來限制進來的連接隊列長度(queue of incoming connections)。
由于TCP協議的三次握手機制,一個進來的套接字連接在進入ESTABLISHED狀態并且可以被accept調用返回給應用程序之前,會經歷中間狀態SYN RECEIVED(見上圖)。這意味著TCP協議棧可以有兩種方案來實現backlog隊列:
歷史上,BCD派生的TCP實現采用第一種方案,這意味著當隊列大小達到backlog最大值時,系統將不再發送用以響應SYN數據包的SYN/ACK數據包。通常,TCP實現將簡單地丟棄收到的SYN數據包(而不是發送RST數據包)以便客戶端重試。這也是W. Richard Stevens的經典教科書《TCP/IP詳解 卷三》14.5節listen Backlog Queue中所描述的方案。
需要注意的是,W. Richard Stevens解釋說BSD的實現實際上確實是使用兩個單獨的隊列,但是它們表現為一個單個隊列,其最大長度固定且由(但不是必須完全等于)backlog參數確定,即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參數的行為隨Linux 2.2而改變。 現在它指等待被接受(accept)的、完全建立的套接字的隊列長度,而不是不完整的連接請求數。 不完整套接字隊列的最大長度可以通過/proc/sys/net/ipv4/tcp_max_syn_backlog設置
這意味著當前Linux版本采用的是具有兩個不同隊列的方案二:一個SYN隊列,大小由系統范圍的設置指定;一個accept隊列,大小由應用程序指定。 方案2一個有趣的問題是,如果當前accept隊列已滿,而一個連接需要從SYN隊列中移到accept隊列中,這個時候TCP實現將如何處理?這種情況由net/ipv4/tcp_minisocks.c中的tcp_check_req函數處理。 相關代碼如下:
child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL); if (child == NULL) goto listen_overflow;對于IPV4,第一行代碼實際會調用net/ipv4/tcp_ipv4.c中的tcp_v4_syn_recv_sock,其中包含如下代碼:
if (sk_acceptq_is_full(sk))goto exit_overflow;上面的代碼中可以看到對accept隊列的檢查。exit_overflow標簽之后的代碼將執行一些清理工作,更新/proc/net/netstat中的ListenOverflows和ListenDrops統計信息,然后返回NULL,這將觸發執行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設置為1(這種情況下將如代碼所示發送RST數據包),否則TCP實現基本上不做任何事情!
總而言之,如果Linux中的(服務端)TCP實現接收到(客戶端)三次握手的ACK數據包,并且accept隊列已滿,則(服務端)基本上將忽略該數據包。這種處理方式剛聽起來可能有點奇怪,但是請記住,SYN RECEIVED狀態有一個關聯定時器:如果服務端沒有收到ACK(或者像這里所說的被忽略),則TCP實現將重新發送 SYN/ACK數據包(重試次數由/proc/sys /net/ipv4/tcp_synack_retries指定,并使用指數退避算法)。
上述現象可以在以下數據包跟蹤中看到,客戶端嘗試連接(并發送數據)到已達到其最大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實現由于收到多個SYN/ACK數據包,會假定其發送的ACK數據包丟失,從而重新發送ACK(參見上述跟蹤中的TCP Dup ACK行)。
如果服務器端的應用程序在達到SYN/ACK最大重試次數之前減少了backlog(即從accept隊列中消耗了一個條目),則TCP實現最終將會處理一個客戶端重復發送的ACK,將連接狀態從SYN RECEIVED轉換到ESTABLISHED,并將連接添加到accept隊列。否則的話,客戶端最終會收到一個RST數據包(如上圖所示)。
數據包跟蹤同時也展示了上述行為另一個有趣的一面。從客戶端的角度來說,TCP連接將在收到第一個SYN/ACK數據包之后變為ESTABLISHED狀態。如果客戶端向服務端發送數據(不等待來自服務端的數據),則該數據也會被重傳。幸運的是,TCP的慢啟動可以限制重傳階段發送的數據段個數。
另一方面,如果客戶端一直在等待來自服務端的數據,而服務端的backlog一直沒有降低,則最終的結果是客戶端的連接狀態是ESTABLISHED,而服務端的連接狀態則是SYN_RCVD(注:原文說的是CLOSED狀態,應該是不對的),也就是處于一個半連接的狀態!
還有另一個方面我們目前沒有討論。listen的man手冊引用表明,除非SYN隊列已滿,否則每個SYN數據包都將導致TCP連接被添加到SYN隊列中,這種說法和實際情況有所出入,原因在于net/ipv4/tcp_ipv4.c中的tcp_v4_conn_request函數存在以下一段代碼:
/* 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隊列已滿,內核會對接收SYN數據包的速率施加限制。如果收到太多SYN數據包其中一些將會被丟棄,這將導致客戶端重試發送SYN數據包,從而最終得到與BSD派生實現中相同的行為。
最后看看為什么Linux的設計選擇會優于傳統的BSD實現。 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。這樣做的問題在于它假定如果應用程序希望調整backlog,不僅要考慮如何處理新建立的傳入連接,還有考慮諸如往返時間等流量特性。Linux中的實現有效地分離了這兩個問題:應用程序只負責調整backlog,使其可以足夠快地接收(accept)連接,避免填滿accept隊列; 系統管理員則可以根據流量特性調整/proc/sys/net/ipv4/tcp_max_syn_backlog
轉載于:https://www.cnblogs.com/sduzh/p/6654225.html
總結
以上是生活随笔為你收集整理的【翻译】TCP backlog在Linux中的工作原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux 命令学习笔记
- 下一篇: Optional变量初学者指南