關於FIN_WAIT2

FIN_WAIT2問題

讓我們熱熱身,通過一張舊圖來回憶一下 TCP 關閉連接時的情況:

TCP Close

TCP Close

按照正常的狀態遷移路徑,當 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 監聽通訊過程:

tcpdump -t -nn -i any port 1234

tcpdump -t -nn -i any port 1234

如圖可見:在三次握手之後,客戶端關閉了連接,服務端確認後沒有發出 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 存在的時間大約是一分鐘左右:

FIN_WAIT2 存在的時間

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 同樣是一個合理的選擇。

發佈了29 篇原創文章 · 獲贊 97 · 訪問量 49萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章