TCP連接的關閉方式
建立TCP連接需要三次握手,而關閉連接則需要四次握手,並且分為主動關閉和被動關閉。這是由於TCP連接是全雙工的,我關了你的連接,並不等於你關了我 的連接,因此雙方都必須單獨進行關閉。當一方完成它的數據發送任務後可以發送FIN包來終止這個方向的連接,表明自己不再有數據需要發送;收到FIN包的 那一方雖然不能再讀取數據,但仍能發送數據。以Client主動關閉連接為例:
Client向Server發送FIN包,表示Client主動關閉連接,然後進入FIN_WAIT_1狀態,等待Server返回ACK包。此後Client不能再向Server發送數據,但能讀取數據。
Server收到FIN包後向Client發送ACK包,然後進入CLOSE_WAIT狀態,此後Server不能再讀取數據,但可以繼續向Client 發送數據。Client收到Server返回的ACK包後進入FIN_WAIT_2狀態,等待Server發送FIN包。
Server完成數據的發送後,將FIN包發送給Client,然後進入LAST_ACK狀態,等待Client返回ACK包,此後Server既不能讀取數據,也不能發送數據。
Client收到FIN包後向Server發送ACK包,然後進入TIME_WAIT狀態,接著等待足夠長的時間(2MSL)以確保Server接收到 ACK包,最後回到CLOSED狀態,釋放網絡資源。Server收到Client返回的ACK包後便回到CLOSED狀態,釋放網絡資源。
TCP連接的建立到關閉,需要經歷以下狀態遷移(假定Client發起連接,並主動關閉連接):
Client CLOSED -> SYN_SENT -> ESTABLISHED -> FIN_WAIT_1 -> FIN_WAIT_2 -> TIME_WAIT -> CLOSED
Server CLODES -> LISTEN -> SYN_RECEIVED -> ESTABLISHED -> CLOSE_WAIT -> LAST_ACK -> CLOSED
3. 對Server與Client的影響
在詳細了解TCP連接的狀態和關閉方式後,我們會發現TIME_WAIT狀態是一個坑爹的存在!主動關閉連接的一方在發送最後一個ACK包後,無論對方是 否收到都會進入TIME_WAIT狀態,等待2MSL的時間,然後才能釋放網絡資源。MSL就是Maximum Segment Lifetime(數據包的最大生命周期),是一個數據包能在互聯網上生存的最長時間,若超過這個時間則該數據包將會消失在網絡中。操作系統通常會將2MSL設為4分鐘,最低不少於30秒,因而TIME_WAIT狀態一般維持在30秒至4分鐘。這個是TCP/IP協議必不可少的,是TCP/IP設計者設計的,也就是無法解決的。TIME_WAIT狀態的存在主要有兩個原因:
可靠地實現TCP全雙工連接的終止。在關TCP閉連接時,最後的ACK包是由主動關閉方發出的,如果這個ACK包丟失,則被動關閉方將重發FIN包,因此 主動方必須維護狀態信息,以允許它重發這個ACK包。如果不維持這個狀態信息,那麼主動方將回到CLOSED狀態,並對被動方重發的FIN包響應RST 包,而被動關閉方將此包解釋成一個錯誤(在Java中會拋出connection reset的SocketException)。因而,要實現TCP全雙工連接的正常終止,必須能夠處理四次握手協議中任意一個包丟失的情況,主動關閉方必須維持狀態信息進入TIME_WAIT狀態。
確保迷路重復數據包在網絡中消失,防止上一次連接中的包迷路後重新出現,影響新連接。TCP數據包可能由於路由器 異常而迷路,在迷路期間,數據包發送方可能因超時而重發這個包,迷路的數據包在路由器恢復後也會被送到目的地,這個迷路的數據包就稱為Lost Duplicate.在關閉一個TCP連接後,如果馬上使用相同的IP地址和端口建立新的TCP連接,那麼有可能出現前一個連接的迷路重復數據包在前一個 連接關閉後再次出現,影響新建立的連接。為了避免這一情況,TCP協議不允許使用處於TIME_WAIT狀態的連接的IP和端口啟動一個新連接,只有經過2MSL的時間,確保上一次連接中所有的迷路重復數據包都已消失在網絡中,才能安全地建立新連接。