TCP
是面向連線的、可靠的、基於位元組流的傳輸層通訊協議。
TCP
是面向連線的協議,所以使用 TCP
前必須先建立連線,而建立連線是透過三次握手來進行的。
TCP包頭結構
在講解三次握手的過程之前,我們先來看一下 TCP
包的結構:
TCP
包頭大小在大多數情況下是固定的,它通常是 20
位元組(不包括任何選項),但如果啟用了選項,則最多可以達到 60
位元組。下面是 TCP
包頭的一般結構:
- 源埠號(16位):表示傳送方的埠號。
- 目標埠號(16位):表示接收方的埠號。
- 序列號(32位):用於對資料流中的位元組進行編號,以便對方能夠按順序重新組裝資料。
- 確認號(32位):表示期望接收到的下一個序列號。
- 資料偏移(4位):指示
TCP
包頭的長度,以4位元組
為單位。因此,資料偏移的值乘以4
就是TCP
包頭的總長度。這個欄位也被稱為頭部長度
。 - 保留位(6位):保留供將來使用,目前全部為 0。
- 標誌位(6位):用於控制
TCP
連線的狀態,包括SYN
、ACK
、FIN
、RST
、PSH
、URG
等。 - 視窗大小(16位):表示傳送方的接收視窗大小,用於流量控制。
- 校驗和(16位):用於驗證
TCP
報文的完整性。 - 緊急指標(16位):當
URG
標誌被設定時,緊急指標表示緊急資料的末尾位置。 - 選項(可選):可以包含各種選項,如最大報文段大小(
MSS
)、視窗縮放因子等,每個選項的大小不定。
在三次握手過程中,我們主要關注序列號、確認號以及標誌位中的SYN和ACK
三次握手過程
通常來說,伺服器會開放監聽埠,而客戶端則主動連線這個埠,建立連線的時候,會進行三次握手,過程如下圖所示:
- 客戶端傳送
SYN
包到伺服器,附上一個隨機生成的序列號(ISN
)。此時客戶端處於SYN_SEND
狀態。 - 伺服器返回
SYN+ACK
包到客戶端,附上一個隨機生成的序列號,確認號則是客戶端上傳的序列號+1。此時服務端處於SYN_RECV
狀態。 - 客戶端返回
ACK
到伺服器,確認號是伺服器下發的序列號+1。此時客戶端處於ESTABLISHED
狀態,連線已建立,這個包可以順帶發一些資料。 - 服務端收到
ACK
後,也進入ESTABLISHED
狀態,可以收發資料。
三次握手的一個重要功能是客戶端和服務端交換 ISN(Initial Sequence Number)
,以便讓對方知道接下來接收資料的時候如何按序列號組裝資料。同時也確保了服務端和客戶端的收發都能正常進行。
使用 wireshark
抓包工具,我們可以看到三次握手的資料:
為什麼是三次握手?不是兩次、四次?
1. TCP 連線使用三次握手的首要原因,是為了防止舊的重複連線初始化造成混亂。
想像一個場景,客戶端發了SYN
之後當機了,重啟後又發了新的SYN
。如果只有兩次握手的話,當伺服器收到舊的SYN
之後,傳送ACK
給客戶端,就直接進入ESTABLISHED
狀態,這時候就可以發資料了。
但是客戶端期待的是新的SYN
的序列號,發現服務端發的確認號不對應,會關閉這個連線,而伺服器此時已經發了資料過來,這就造成了混亂。
而三次握手,客戶端可以收到ACK
之後,判斷確認號,正確則返回ACK
,錯誤則返回RST
告訴伺服器關閉這個連線。
使用三次握手和RST
控制訊息,將是否建立連線的最終控制權交給了客戶端,因為只有客戶端有足夠的上下文來判斷當前連線是否是錯誤的或者過期的,這也是TCP
使用三次握手建立連線的最主要原因。
2. 三次握手的第二個原因,是為了互動雙方的序列號。
TCP
協議的通訊雙方,都必須維護一個序列號,用來保證資料包的有序,以及丟包時能夠重發,所以這個初始化的序列號是很重要的。當客戶端發SYN
給伺服器時,伺服器需要返回ACK
確認,而伺服器發SYN
給客戶端時,客戶端也需要發ACK
確認,才能確保兩邊都有正確的序列號。伺服器在發SYN
和ACK
時,可以合併成一條訊息傳送,所以是不需要四次握手的。
參考資料
- 4.1 TCP 三次握手與四次揮手面試題 - 小林coding