TCP 連線斷開
在當今數字化時代,網際網路已經成為了人們生活中不可或缺的一部分。而在網際網路的基礎之上,TCP協議扮演著關鍵的角色,它負責著資料在網路中的可靠傳輸。在TCP連線的建立過程中,我們已經瞭解了三次握手的過程和原理。然而,連線的建立只是TCP協議的一部分,同樣重要的是連線的斷開過程。本文將重點探討TCP連線的斷開過程,包括四次揮手的過程和狀態變遷,以及為什麼揮手需要四次和為什麼需要TIME_WAIT狀態。透過深入理解TCP連線斷開的過程,我們可以更好地理解網路通訊的原理
TCP 四次揮手過程和狀態變遷
TCP斷開連線需要透過四次揮手的方式。雙方都有能力主動斷開連線,一旦斷開連線,主機中的各種「資源」將被釋放。那麼我們將詳細講解下TCP四次揮手的原理及過程!
- 當客戶端打算關閉連線時,它會傳送一個TCP首部中FIN標誌位被置為1的報文,即FIN報文。隨後,客戶端進入FIN_WAIT_1狀態。
- 當服務端收到該報文後,會向客戶端傳送一個ACK應答報文,並進入CLOSED_WAIT狀態。
- 客戶端接收到服務端的ACK應答報文後,進入FIN_WAIT_2狀態。
- 服務端等待處理完資料後,也會向客戶端傳送一個FIN報文,然後進入LAST_ACK狀態。
- 客戶端收到服務端的FIN報文後,會回覆一個ACK應答報文,並進入TIME_WAIT狀態。
- 一旦服務端收到了ACK應答報文,就進入CLOSE狀態,這樣服務端就完成了連線的關閉。
- 客戶端經過2MSL一段時間後,自動進入CLOSE狀態,這樣客戶端也完成了連線的關閉。
在TCP連線的斷開過程中,我們可以觀察到每個方向都需要傳送一個FIN報文和接收一個ACK報文,因此通常將這個過程稱為四次揮手。
需要注意的一點是,只有主動發起關閉連線的一方,才會進入TIME_WAIT狀態。這是因為在關閉連線後,客戶端需要等待一段時間(通常為兩倍的最大報文段生存時間,也即2MSL)來確保服務端收到了自己的ACK應答報文。這樣做的目的是為了防止已經關閉的連線上出現延遲的報文段,確保連線的可靠關閉。而服務端則不需要等待這段時間,因此沒有TIME_WAIT狀態。
為什麼揮手需要四次?
為了更好地理解為什麼揮手需要四次,讓我們再來回顧一下雙方發出FIN包的過程。這樣我們就能理解為什麼需要四次揮手了。
在關閉連線時,當客戶端向服務端傳送FIN時,這僅僅表示客戶端不再傳送資料了,但是它仍然可以接收資料。
當服務端收到客戶端的FIN報文時,它首先會回覆一個ACK應答報文。然而,服務端可能還有資料需要處理和傳送,所以它會等待直到它不再傳送資料時,才會傳送FIN報文給客戶端,表示同意現在關閉連線。
透過上述過程,我們可以看出,服務端通常需要等待完成資料的傳送和處理,所以服務端的ACK和FIN通常會分開傳送,這就導致了比三次握手多了一次揮手的過程。
為什麼 TIME_WAIT 等待的時間是 2MSL?
MSL是Maximum Segment Lifetime,即報文的最大生存時間,它表示報文在網路中存在的最長時間。超過此時間,報文將被丟棄。因為TCP協議是基於IP協議的,IP頭部有一個TTL欄位,它表示資料包可以經過的最大路由數。每經過一個路由器,TTL值就減1。當TTL值為0時,資料包將被丟棄,並且傳送ICMP報文通知源主機。
MSL和TTL的區別在於單位。MSL的單位是時間,而TTL是經過的路由跳數。因此,為了確保報文已經自然消亡,MSL應該大於或等於TTL消耗為0的時間。
TIME_WAIT等待2倍MSL的合理解釋是:網路中可能存在來自傳送方的資料包。當這些資料包被接收方處理後,它會向對方傳送響應,因此往返需要等待2倍的時間。就是確保最後一個ACK被服務端接收到了,如果沒有接收到也要給足時間讓伺服器端的第三次揮手的FIN重新傳過來。
舉個例子,如果被動關閉方沒有收到斷開連線的最後一個ACK報文,就會觸發超時重發FIN報文。另一方收到FIN報文後,會重發ACK給被動關閉方,這樣來回就需要2個MSL的時間。
2MSL時間是從客戶端接收到FIN後傳送ACK開始計時的。如果在TIME_WAIT時間內,因為客戶端的ACK沒有傳輸到服務端,客戶端又接收到了服務端重發的FIN報文,那麼2MSL時間將重新計時。
在Linux系統中,預設的2MSL時間是60秒,即一個MSL為30秒。Linux系統停留在TIME_WAIT狀態的時間是固定的60秒。
在Linux核心程式碼中,它的定義名為TCP_TIMEWAIT_LEN:
#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT
state, about 60 seconds */
如果要修改TIME_WAIT的時間長度,只能修改Linux核心程式碼中TCP_TIMEWAIT_LEN的值,並重新編譯Linux核心。
為什麼需要 TIME_WAIT 狀態?
TIME_WAIT 狀態的存在是為了確保網路連線的可靠關閉。只有主動發起關閉連線的一方(即主動關閉方)才會有 TIME_WAIT 狀態。
TIME_WAIT 狀態的需求主要有兩個原因:
- 防止具有相同「四元組」的「舊」資料包被收到:在網路通訊中,每個 TCP 連線都由源 IP 地址、源埠號、目標 IP 地址和目標埠號這四個元素唯一標識,稱為「四元組」。當一方主動關閉連線後,進入 TIME_WAIT 狀態,它仍然可以接收到一段時間內來自對方的延遲資料包。這是因為網路中可能存在被延遲傳輸的資料包,如果沒有 TIME_WAIT 狀態的存在,這些延遲資料包可能會被錯誤地傳遞給新的連線,導致資料混亂。透過保持 TIME_WAIT 狀態,可以防止舊的資料包乾擾新的連線。
- 保證「被動關閉連線」的一方能被正確關閉:當連線的被動關閉方接收到主動關閉方的 FIN 報文(表示關閉連線),它需要傳送一個確認 ACK 報文給主動關閉方,以完成連線的關閉。然而,網路是不可靠的,ACK 報文可能會在傳輸過程中丟失。如果主動關閉方在收到 ACK 報文之前就關閉連線,被動關閉方將無法正常完成連線的關閉。TIME_WAIT 狀態的存在確保了被動關閉方能夠接收到最後的 ACK 報文,從而幫助其正常關閉連線。
防止舊連線的資料包
假設TIME-WAIT狀態沒有適當的等待時間或時間過短,延遲的資料包抵達後可能會引發嚴重的問題。
例如,服務端在關閉連線之前傳送的SEQ = 301報文被網路延遲了。然後,同一埠的TCP連線被複用,並且延遲的SEQ = 301到達了客戶端。在這種情況下,客戶端有可能正常地接收到這個過期的報文,從而導致資料錯亂等嚴重問題的發生。
為了解決這個問題,TCP設計了一個機制,即經過2MSL的時間,足夠讓連線中的兩個方向上的資料包都被丟棄。這樣,原來連線的資料包在網路中自然消失,再出現的資料包一定是由新建立的連線產生的,從而避免了資料錯亂等問題的發生。
保證連線正確關閉
TIME-WAIT狀態的作用是等待足夠的時間,以確保最後的ACK報文能夠被被動關閉方接收,並幫助其正常關閉。
假設TIME-WAIT沒有適當的等待時間或時間過短,斷開連線可能會導致以下問題:
例如,如果在四次揮手的過程中,客戶端傳送的最後一個ACK報文在網路中丟失,並且客戶端的TIME-WAIT狀態過短或沒有設定,則客戶端會直接進入CLOSE狀態,而服務端則會一直處於LAST-ACK狀態。這種情況下,連線無法正常關閉。
另外,當客戶端發起建立連線的SYN請求後,如果服務端傳送的RST報文給客戶端,連線建立的過程將會被終止。
如果TIME-WAIT等待的時間足夠長,會發生以下兩種情況:
- 服務端正常接收到四次揮手的最後一個ACK報文,從而正常關閉連線。
- 服務端沒有收到四次揮手的最後一個ACK報文時,會重發FIN關閉連線報文並等待新的ACK報文。
因此,客戶端在TIME-WAIT狀態等待2MSL時間後,可以確保雙方的連線都能夠正常關閉。
這裡再科普一下有關知識,大多數三次握手和四次揮手都沒有提到。為什麼第三次揮手的時候會傳送ack呢?不是正常就是傳送fin就可以了嗎?
在TCP協議中,除了初始連線的第一個SYN包,其中ACK欄位被設定為0,而其他所有的TCP包都會將ACK欄位設定為1。這個ACK欄位的作用是用來確認接收方已經成功接收到資料。如果有資料需要傳送,TCP協議會在傳送資料的同時附帶ACK來確認對方的資料。如果資料在傳輸過程中丟失,TCP會進行資料重傳。ACK欄位是TCP頭部必備的,這32個位空著也是空著,那麼幹脆讓除了初始報文段之外的所有報文段的ACK欄位都有效。
總結
TCP連線的斷開需要透過四次揮手的過程來完成。雙方都有能力主動斷開連線,並且在斷開連線後,各種資源將被釋放。四次揮手的過程涉及到雙方傳送FIN和ACK報文的互動,確保資料的可靠傳輸和連線的正確關閉。其中,主動關閉方會進入TIME_WAIT狀態,等待一段時間來確保對方已經接收到最後的ACK報文。TIME_WAIT狀態的存在是為了防止舊連線的資料包乾擾新連線,並確保被動關閉方能夠正常關閉連線。揮手需要四次的原因是為了確保資料的完整傳輸和連線的可靠關閉。TIME_WAIT狀態等待2倍MSL的時間是為了確保網路中的資料包都已經消失。