1. 程式人生 > >《網路協議》圖解 TCP 連線建立與釋放

《網路協議》圖解 TCP 連線建立與釋放

注:TCP 連線的建立和釋放在網路協議中是比較重要的,由於本人理解也不是很透徹,歡迎各位批評指正。

前言

        TCP 是面向連線的、可靠的位元組流協議。因此,在傳輸資料之前通訊雙方必須建立一個 TCP 連線,建立 TCP 連線需要在伺服器和客戶端之間進行三次握手。通訊雙方資料傳輸完畢之後進行連線釋放,釋放連線需要在通訊雙方之間進行四次揮手。

TCP 狀態機

        TCP 所謂的“連線”,只是通訊雙方維護一個“連線狀態”,讓它看上去好像有連線一樣,其實 TCP 連線是虛擬的連線,不是電路連線。首先看下 TCP 的狀態機,狀態機是 TCP 連線與釋放的全過程。如下圖所示:



        下面針對 TCP 狀態機所出現的各個狀態進行簡要的分析:

  • CLOSED:表示初始狀態。對服務端和客戶端雙方都一樣。
  • LISTEN:表示監聽狀態。服務端呼叫了 listen 函式使其處於監聽狀態,此時可以開始 accept (接收)客戶端的連線。
  • SYN_SENT:表示客戶端已經發送了 SYN 報文段,則會處於該狀態。當客戶端呼叫 connect 函式發起連線請求時,首先發 SYN 給服務端,然後自己進入 SYN_SENT 狀態,並等待服務端傳送 ACK+SYN 作為請求應答。
  • SYN_RCVD:表示服務端收到客戶端傳送 SYN 報文段。服務端收到這個報文段後,進入 SYN_RCVD 狀態,然後傳送 ACK+SYN 給客戶端。
  • ESTABLISHED:表示 TCP 連線已經成功建立,通訊雙方可以開始傳輸資料。服務端傳送完 ACK+SYN 並收到來自客戶端的 ACK 後進入該狀態,客戶端收到來自伺服器的 SYN+ACK 併發送 ACK 後也進入該狀態。
  • FIN_WAIT_1:表示主動關閉連線。無論哪方呼叫 close 函式傳送 FIN 報文都會進入這個這個狀態。
  • FIN_WAIT_2:表示被動關閉方同意關閉連線。主動關閉連線方收到被動關閉方返回的 ACK 後,會進入該狀態。
  • TIME_WAIT表示收到對方的 FIN 報文併發送了 ACK 報文,就等 2MSL 後即可回到 CLOSED 狀態了。如果 FIN_WAIT_1 狀態下,收到對方同時帶 FIN 標誌和 ACK 標誌的報文時,可以直接進入 TIME_WAIT 狀態,而無須經過 FIN_WAIT_2 狀態。
  • CLOSING:表示雙方同時關閉連線。如果雙方几乎同時呼叫 close 函式,那麼會出現雙方同時傳送 FIN 報文的情況,就會出現 CLOSING 狀態,表示雙方都在關閉連線。
  • CLOSE_WAIT:表示被動關閉方等待關閉。當收到對方呼叫 close 函式傳送的 FIN 報文時,迴應對方 ACK 報文,此時進入 CLOSE_WAIT 狀態。
  • LAST_ACK:表示被動關閉方傳送 FIN 報文後,等待對方的 ACK 報文狀態,當收到 ACK 後進入CLOSED狀態。

TCP 連線的建立

           TCP 連線的正常建立過程通訊雙方需要三次握手,其過程如下圖所示:


         TCP 協議提供可靠的連線服務,採用有保障的三次握手方式來建立一個 TCP 連線。三次握手的具體過程如下:
  1. 客戶端程序向服務端發出連線請求,請求報文的報文段首部中的控制位標誌 SYN=1(有關 TCP 控制位資訊參考《TCP 協議》),由於是首次請求建立連線,因此,控制位標誌 ACK=0,該報文段包含計算機隨機生成的初始序號 seq=x。傳送請求連線的 TCP 報文段,此時客戶端程序進入 SYN_SENT 狀態,這是 TCP 連線的第一次握手。
  2. 服務端收到客戶端發來的請求報文後,若同意建立連線,則向客戶端傳送確認。確認報文中的控制位 SYN=1,ACK=1,確認應答號 ack=x+1(即在接收到序列號值基礎上加 1 ),並且傳送自己的一個初始序列號 seq=y(即請求與客戶端連線)。此時,服務端進入SYN_RCVD狀態,這是TCP連線的第二次握手。
  3. 客戶端程序收到服務端程序的確認報文後,還要向服務端發出確認資訊。確認報文段的控制位 ACK=1,確認應答號 ack=y+1(即在接收到序列號值基礎上加 1 ),此時,客戶端進入 ESTABLISHED 狀態。伺服器收到來自客戶端的確認應答資訊也進入  ESTABLISHED 狀態。這是TCP連線的第三次握手。此時,TCP 連線成功建立。

同時開啟連線請求

        正常情況下,通訊一方請求建立連線,另一方響應該請求,但是如果出現,通訊雙方同時請求建立連線時,則連線建立過程並不是三次握手過程,而且這種情況的連線也只有一條,並不會建立兩條連線。同時開啟連線時,兩邊幾乎同時傳送 SYN,並進入 SYN_SENT 狀態,當每一端收到 SYN 時,狀態變為 SYN_RCVD,同時雙方都再發 SYN 和 ACK 作為對收到的 SYN 進行確認應答。當雙方都收到 SYN 及相應的 ACK 時,狀態變為 ESTABLISHED。其過程入下:


TCP 連線釋放

        由於 TCP 連線是全雙工的,因此每個方向都必須單獨進行關閉。原則是主動關閉的一方傳送一個 FIN 報文來表示終止這個方向的連線,收到一個 FIN 意味著這個方向不再有資料流動,但另一個方向仍能繼續傳送資料,直到另一個方向也傳送 FIN 報文。TCP 連線釋放的過程如下圖所示:


       以下是釋放連線的四次揮手過程:

  1. 客戶端程序主動向服務端發出連線釋放請求報文段,並停止傳送資料,主動關閉 TCP 連線。釋放連線報文段中控制位 FIN=1,序列號為 seq=i,傳送該報文段之後客戶端進入FIN_WAIT_1(終止等待1)狀態,等待伺服器的確認。這是 TCP 連線釋放的第一次揮手。
  2. 伺服器收到連線釋放請求報文段後即發出確認釋放連線的報文段,該報文段中控制位 ACK=1,確認應答號為 ack=i+1,然後伺服器進入CLOSE_WAIT(關閉等待)狀態。此時 TCP 處於半關閉狀態,即客戶端已經不向伺服器傳送資料,但伺服器仍可向客戶端傳送資料。這是TCP連線釋放的第二次揮手。
  3. 客戶端收到伺服器的確認資訊後,就進入了FIN_WAIT_2(終止等待2)狀態,等待伺服器發出連線釋放請求報文段,若沒有資料需要傳輸,伺服器被動向客戶端發出連結釋放請求報文段中,報文段中控制位 FIN=1,序列號 seq=j,此時伺服器進入LAST_ACK(最後確認)狀態,等待客戶端的確認應答。這是 TCP 連線釋放的第三次揮手。
  4. 客戶端收到伺服器的連線釋放請求後,必須對此發出確認。確認報文段中控制位 ACK=1,確認應答號 ack=j+1,客戶端發出確認應答資訊之後後進入TIME_WAIT(時間等待)狀態。在這段時間內 TCP連線並沒有釋放,必須等待 2MSL 時間後,客戶端才進入 CLOSED 狀態。伺服器收到了客戶端的確認應答後,就進入了 CLOSED 狀態。直到客戶端和伺服器都進入 CLOSED 狀態後,連線就完全釋放了,這是TCP連線釋放的第四次揮手。

同時關閉連線

       正常情況下,通訊一方請求連線關閉,另一方響應連線關閉請求,並且被動關閉連線。但是若出現同時關閉連線請求時,通訊雙方均從 ESTABLISHED 狀態轉換為 FIN_WAIT_1 狀態。任意一方收到對方發來的 FIN 報文段後,其狀態均由 FIN_WAIT_1轉變到 CLOSING 狀態,併發送最後的 ACK 資料段。當收到最後的 ACK 資料段後,狀態轉變化 TIME_WAIT,在等待 2MSL 時間後進入到 CLOSED 狀態,最終釋放整個 TCP 傳輸連線。其過程入下:


TCP 相關疑問


為什麼在 TCP 協議裡,建立連線是三次握手,而關閉連線卻是四次握手?

        因為當處於 LISTEN 狀態的伺服器端收到來自客戶端的 SYN 報文(客戶端希望新建一個TCP連線)時,它可以把 ACK (確認應答)和 SYN (同步序號)放在同一個報文裡來發送給客戶端。但在關閉 TCP 連線時,當收到對方的 FIN 報文時,對方僅僅表示對方已經沒有資料傳送給你了,但是你自己可能還有資料需要傳送給對方,則等你傳送完剩餘的資料給對方之後,再發送 FIN 報文給對方來表示你資料已經發送完畢,並請求關閉連線,所以通常情況下,這裡的 ACK 報文和 FIN 報文都是分開發送的。

為什麼一定要進行三次握手?
        當客戶端向伺服器端傳送一個連線請求時,由於某種原因長時間駐留在網路節點中,無法達到伺服器端,由於 TCP 的超時重傳機制,當客戶端在特定的時間內沒有收到伺服器端的確認應答資訊,則會重新向伺服器端傳送連線請求,且該連結請求得到伺服器端的響應並正常建立連線,進而傳輸資料,當資料傳輸完畢,並釋放了此次 TCP 連線。若此時第一次傳送的連線請求報文段延遲了一段時間後,到達了伺服器端,本來這是一個早已失效的報文段,但是伺服器端收到該連結請求後誤以為客戶端又發出了一次新的連線請求,於是伺服器端向客戶端發出確認應答報文段,並同意建立連線。如果沒有采用三次握手建立連線,由於伺服器端傳送了確認應答資訊,則表示新的連線已成功建立,但是客戶端此時並沒有向伺服器端發出任何連線請求,因此客戶端忽略伺服器端的確認應答報文,更不會向伺服器端傳輸資料。而伺服器端卻認為新的連線已經建立了,並在一直等待客戶端傳送資料,這樣伺服器端一直處於等待接收資料,直到超出計數器的設定值,則認為客戶端出現異常,並且關閉這個連線。在這個等待的過程中,浪費伺服器的資源。如果採用三次握手,客戶端就不會向服務端發出確認應答資訊,伺服器端由於沒有收到客戶端的確認應答資訊,從而判定客戶端並沒有請求建立連線,從而不建立該連線。

為什麼需要在 TIME_WAIT 狀態必須等待 2MSL 時間,而不直接給進入 CLOSED 狀態?

主要有兩個原因:

  1. TIME_WAIT 確保有足夠的時間讓對端收到了ACK,如果被動關閉的那方沒有收到 ACK,就會觸發被動端重發 FIN。因為最後一次確認應答 ACK 報文段很有可能丟失,因而使被動關閉方處於在LAST_ACK 狀態的,此時被動關閉方會重發這個 FIN+ACK 報文段,在這等待的 2MSL 時間內主動關閉方重新收到這個被動關閉方重發的 FIN+ACK 報文段,因此,主動關閉方會重新發送確認應答資訊,從而重新啟動 2MSL 計時器,直到通訊雙方都進入 CLOSED 狀態。如果主動關閉方在 TIME_WAIT 狀態不等待一段時間就直接釋放連線並進入 CLOSED 狀態,那麼主動關閉方無法收到來自被動關閉方重發的 FIN+ACK 報文段,也就不會再發送一次確認 ACK 報文段,因此被動關閉方就無法正常進入CLOSED 狀態。
  2. 有足夠的時間讓這個連線不會跟後面的連線混在一起。防止已失效的請求連接出現在本連線中。在連線處於 2MSL 等待時,任何遲到的報文段將被丟棄,因為處於 2MSL等待的、由該插口(插口是IP和埠對的意思,socket)定義的連線在這段時間內將不能被再用,這樣就可以使下一個新的連線中不會出現這種舊的連線之前延遲的報文段。

參考資料:

《TCP/IP 詳解》