由于标題長度有限制,我把想要描述的問題再次描述下:
核心通常會為每一個
LISTEN
狀态的
Socket
維護兩個隊列:
1
accept
隊列:
listen()
函數第二個參數
BACKLOG
指定,表示已完成連接配接的隊列,等待被
accept
函數取走。
2
SYN
隊列:由
/proc/sys/net/ipv4/tcp_max_syn_backlog
指定,表示處于
SYN_RECV
狀态的隊列。
如果沒有概念,參考:
http://blog.csdn.net/yangbodong22011/article/details/60399728
現在的問題是:
如果
accept
隊列已經滿了,并且伺服器沒有使用
accept
函數取走任何連接配接,那麼當一個連接配接需要從
SYN隊列
移動到
accept
隊列時會發生什麼?
正确的解釋
答案來源:
http://veithen.github.io/2014/01/01/how-tcp-backlog-works-in-linux.html
下面的部分我翻譯自作者原文:
This case is handled by thefunction in
tcp_check_req
, The relevant code reads:
net/ipv4/tcp_minisocks.c
這種情況由
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;
For IPv4, the first line of code will actually call tcp_v4_syn_recv_sock in net/ipv4/tcp_ipv4.c, which contains the following code:
對于IPv4,第一行代碼實際上将調用
net/ipv4/tcp_ipv4.c
中的
tcp_v4_syn_recv_sock
,其中包含以下代碼:
if (sk_acceptq_is_full(sk))
goto exit_overflow;
We see here the check for the accept queue. The code after thelabel will perform some cleanup, update the
exit_overflow
and
ListenOverflows
statistics in
ListenDrops
and then return
/proc/net/netstat
. This will trigger the execution of the
NULL
code in
listen_overflow
:
tcp_check_req
我們在這裡看到了對于
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 = ;
return NULL;
}
This means that unless /proc/sys/net/ipv4/tcp_abort_on_overflow is set to 1 , the implementation basically does… nothing!
這意味着隻要
/proc/sys/net/ipv4/tcp_abort_on_overflow
被設定為
1
,在上面的
if (!sysctl_tcp_abort_on_overflow)
判斷中為0,就不會執行下面代碼,也就是Linux的政策是 : 什麼都不幹!!!!
備注:
這裡的什麼都不幹對應的是:
回應RST包
,用戶端出現
connection reset by peer
。
To summarize, if the TCP implementation in Linux receives the ACK packet of the 3-way handshake and the accept queue is full, it will basically ignore that packet. At first, this sounds strange, but remember that there is a timer associated with the SYN RECEIVED state: if the ACK packet is not received (or if it is ignored, as in the case considered here), then the TCP implementation will resend the SYN/ACK packet (with a certain number of retries specified by /proc/sys/net/ipv4/tcp_synack_retries and using an exponential backoff algorithm).
總而言之,如果
Linux
中的
TCP
接收到3次握手的
ACK資料包
,并且接受隊列已滿,它将基本上忽略該資料包。 這聽起來很奇怪,但是記住有一個與SYN RECEIVED狀态相關聯的定時器:如果沒有收到
ACK
分組(在這裡我們考慮的情況是ACK被忽略,就和沒有收到是一樣的),則
TCP
将重新發送
SYN/ACK
資料包(由
/proc/sys/net/ipv4/tcp_synack_retries
指定次數)。
備注:我的電腦是5
Since the TCP implementation on the client side gets multiple SYN/ACK packets, it will assume that the ACK packet was lost and resend it (see the lines with TCP Dup ACK in the above trace). If the application on the server side reduces the backlog (i.e. consumes an entry from the accept queue) before the maximum number of SYN/ACK retries has been reached, then the TCP implementation will eventually process one of the duplicate ACKs, transition the state of the connection from SYN RECEIVED to ESTABLISHED and add it to the accept queue. Otherwise, the client will eventually get a RST packet (as in the sample shown above).
由于用戶端收到多個
SYN/ACK
分組,它将假定
ACK
分組丢失并重新發送它。 如果在達到最大
SYN/ACK
重試次數之前,伺服器側的應用程式減少了
Accept隊列大小
(即
調用accept
來取接受隊列的條目),則
TCP
将最終處理
重複ACK
中的一個,轉變狀态從
SYN RECEIVED
到
ESTABLISHED
的連接配接,并将其添加到
accept
隊列。 否則,用戶端将最終收到
RST
分組。
實驗驗證
驗證環境:
RedHat 7
Linux version 3.10.0-514.el7.x86_64
驗證思路:
- 模拟一個”忙”的伺服器,保證它的
。accept隊列被占滿
- 再用用戶端連接配接。
使用
wireshark
抓包情況如下所示(在新的标簽頁打開圖檔):
- 前三個包是三次握手,143是用戶端,155是伺服器。
- 但是155伺服器此時
是滿的,雖然143和它完成了三次握手,但是它仍然不能從accept隊列
狀态轉換到SYN RECEIVED
狀态。ESTABLISHED
- 此時由于
狀态的定時器機制,伺服器155沒有收到SYN RECEIVED
分組的時候(實際上是直接扔掉了),會繼續給用戶端143發送ACK
資料包,143以為伺服器沒有收到SYN/ACK
,然後回複ACK,這也就是”黑色”的包為什麼有10個的原因了,伺服器發送的ACK
有5個,用戶端回複的SYN/ACK
有5個,就是ACK
定義的。/proc/sys/net/ipv4/tcp_synack_retries
我的疑問:
按道理,用戶端最後是會收到伺服器端發送的
RST
資料包的。但是我不知道多長時間之後會發,于是我等了很久(2小時)。此時伺服器端通過
netstat
指令檢視已經沒有任何用戶端的連接配接資訊了,但是用戶端檢視和伺服器的連接配接卻是
ESTABLISHED
狀态。很奇怪,這樣的話用戶端什麼時候退出呢? 難道它一直卡在那裡?
ps : 重新測了以下,伺服器丢掉
SYN RECEIVED
狀态的用戶端時間大概是1分鐘,也就是用戶端連接配接過來,伺服器
Accept隊列滿
的話,大概一分鐘之後伺服器就會丢失這個連接配接。但是此時用戶端維持着一條
ESTABLISHED
狀态的連接配接~~,它到底什麼時候釋放呢?