最近讀到一篇分析文章TCP SOCKET中backlog參數的用途是什麼?,其中有這樣一段描述:php
在linux 2.2之前,backlog大小包括了半鏈接狀態和全鏈接狀態兩種隊列大小。linux 2.2之後,分離爲兩個backlog來分別限制半鏈接SYN_RCVD狀態的未完成鏈接隊列大小跟全鏈接ESTABLISHED狀態的已完成鏈接隊列大小。互聯網上常見的TCP SYN FLOOD惡意DOS攻擊方式就是用/proc/sys/net/ipv4/tcp_max_syn_backlog來控制的,可參見《TCP洪水攻擊(SYN Flood)的診斷和處理》。html
在使用listen函數時,內核會根據傳入參數的backlog跟系統配置參數/proc/sys/net/core/somaxconn中,兩者取最小值,做爲「ESTABLISHED狀態以後,完成TCP鏈接,等待服務程序ACCEPT」的隊列大小。在kernel 2.4.25以前,是寫死在代碼常量SOMAXCONN,默認值是128。在kernel 2.4.25以後,在配置文件/proc/sys/net/core/somaxconn (即 /etc/sysctl.conf 之類 )中能夠修改。我稍微整理了流程圖,以下:linux
回過頭來看以前本身的一篇文章調查SocketServer,發現有一段描述有誤:git
也就是說,backlog形容的是server在與客戶端創建tcp鏈接的過程當中,SYN隊列的大小,在socket的listen接口中,通常第二個參數就是backlog的大小。github
在How TCP backlog works in linux一文中,做者給出了比較詳細的分析:windows
第一種實現方式在底層維護一個由backlog指定大小的隊列。服務端收到SYN後,返回一個SYN/ACK,並把鏈接放入隊列中,此時這個鏈接的狀態是SYN_RECEIVED。當客戶端返回ACK後,此鏈接的狀態變爲ESTABLISHED。隊列中只有ESTABLISHED狀態的鏈接可以交由應用處理。第一種實現方式能夠簡單歸納爲:一個隊列,兩種狀態。socket
第二種實現方式在底層維護一個SYN_RECEIVED隊列和一個ESTABLISHED隊列,當SYN_RECEIVED隊列中的鏈接返回ACK後,將被移動到ESTABLISHED隊列中。backlog指的是ESTABLISHED隊列的大小。tcp
傳統的基於BSD的tcp實現第一種方式,在linux2.2以前,內核也實現第一種方式。當隊列滿了之後,服務端再收到SYN時,將不會返回SYN/ACK。比較優雅的處理方法就是不處理這條鏈接,不返回RST,讓客戶端重試。函數
在linux2.2後,選擇第二種方式實現,SYN_RECEIVED隊列的大小由proc/sys/net/ipv4/tcp_max_syn_backlog系統參數指定,ESTABLISHED隊列由backlog和/proc/sys/net/core/somaxconn中較小的指定。spa
可是在windows server中,底層選擇winsock API實現,backlog的定義是represents the maximum length of the queue of pending connections for the listener
(這是一個比較模糊的定義……來源於BSD),當隊列滿了後,將會返回RST。
考慮這樣一種狀況,當ESTABLISHED隊列滿了,此時收到一個鏈接的ACK,須要將此鏈接從SYN隊列移到ESTABLISHED隊列中,會發生什麼?
linux底層的關鍵代碼是:
1
2
3
4
5
|
listen_overflow:
if (!sysctl_tcp_abort_on_overflow) {
inet_rsk(req)->acked =
1;
return NULL;
}
|
除非系統的tcp_abort_on_overflow指定爲1(將返回RST),不然底層將不會作任何事情……這是一種委婉的退讓策略,在服務端處理不過來時,讓客戶端誤覺得ACK丟失,繼續從新發送ACK。這樣,當服務端的處理能力恢復時,這條鏈接又能夠從新被移動到ESTABLISHED隊列中去。