FIN_WAIT2問題
讓我們熱熱身,通過一張舊圖來回憶一下 TCP 關閉連接時的情況:
按照正常的狀態遷移路徑,當 FIN_WAIT2 收到 FIN 包後會遷移到 TIME_WAIT 狀態。如果沒有收到 FIN 包,那麼連接狀態會如何遷移,我們不妨測試一下:
#!/usr/bin/env python
import socket
import time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 1234))
s.listen(1)
c, _ = s.accept()
time.sleep(1000)
c.close()
如上是用 Python 實現的一個簡單的 server 演示代碼,需要注意的是我在 close 前設置了一個巨大的延遲時間,從而達到拖延服務端發出 FIN 包的目的。與之相對應的我們再實現一個簡單的 client 演示代碼,它沒什麼可說的,就是連上後直接關閉:
#!/usr/bin/env python
import socket
import time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 1234))
s.close()
測試的時候先啓動服務端監聽 1234 端口,再啓動客戶端連接 1234 端口,爲了確認整個過程是否符合我們的預期,可以使用 tcpdump 監聽通訊過程:
如圖可見:在三次握手之後,客戶端關閉了連接,服務端確認後沒有發出 FIN 包,所以整個過程符合我們的預期,同時爲了判斷 FIN_WAIT2 存在了多久,寫了如下代碼:
shell> while sleep 1; do netstat -ant | grep FIN_WAIT2 | while read content; do echo -n $(date +"%T") "" echo $content done done
監控發現,在本例中 FIN_WAIT2 存在的時間大約是一分鐘左右:
實際上此時間是「net.ipv4.tcp_fin_timeout」控制的,不過在測試中發現,FIN_WAIT2 存在的時間並不是精確的等於 tcp_fin_timeout 的設置,存在一定的偏差。此外,需要說明的是在 tcp_fin_timeout 後,FIN_WAIT2 並沒有遷移到 TIME_WAIT,而是直接關閉了。
關於 tcp_fin_timeout 的介紹,可以參考內核的說明:
The length of time an orphaned (no longer referenced by any application) connection will remain in the FIN_WAIT_2 state before it is aborted at the local end. While a perfectly valid “receive only” state for an un-orphaned connection, an orphaned connection in FIN_WAIT_2 state could otherwise wait forever for the remote to close its end of the connection.
Cf. tcp_max_orphans
Default: 60 seconds
介紹中提到了 orphan 的概念,指的是當一個 socket 不再被任何應用引用時,它便成爲了一個孤兒,而 tcp_fin_timeout 限定了成爲孤兒的 FIN_WAIT2 所能存活的時間。
注:orphan 太多的話會:The “Out of socket memory” error。
或許有人會問:如果 FIN_WAIT2 沒有成爲孤兒,那麼會如何:
#!/usr/bin/env python
import socket
import time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 1234))
s.shutdown(socket.SHUT_WR)
time.sleep(1000)
s.close()
在這一版的 client 代碼中,我們沒有直接 close 連接,而是通過 shutdown 來關閉,此時客戶端同樣會發送 FIN 包,但是並沒有釋放連接,所以本例中的 FIN_WAIT2 和上例中的 FIN_WAIT2 不同,其並不會成爲孤兒。通過測試發現,此時 tcp_fin_timeout 將失效,而 本例中的 FIN_WAIT2 會一直存在,直到客戶端 close 連接或者退出。
實際上,同其它 TCP 狀態相比,通常 FIN_WAIT2 並不會給你添什麼麻煩。如果你統計服務器上的 TCP 狀態,你會發現它們多數時候都很少,如果不是,那麼一定是應用層面上出了問題。至於 tcp_fin_timeout,我並不建議大家把它設置得太小,因爲如上所說,正常情況下,TCP 連接並不會在 FIN_WAIT2 狀態上停留太久,假設真的出現 FIN 包丟失之類的情況,那麼給 FIN_WAIT2 狀態一個合理的存在週期是必要的,畢竟丟失的 FIN 包可能會重傳,在這一點上和 TIME_WAIT 爲什麼要存在 2MSL 是同樣的原因。既然 TIME_WAIT 缺省存在 60 秒,那麼 tcp_fin_timeout 缺省設置爲 60 同樣是一個合理的選擇。