? 本文已收錄於 CS-Wiki(Gitee 官方推薦專案,現已 0.9k star)
0. 前言
越簡單常見的問題越不可小覷,萬丈高樓平地起,把簡單的問題深入化,才更能拉開與競爭者的距離。掌握了本文講的全部知識點,關於 TCP 三次握手和四次揮手基本就 OK 了 ?
1. TCP 和 UDP
講解 TCP 三次握手和四次握手之前,我們先了解一下 TCP 和 UDP 這兩個重量級的傳輸層協議。
? 使用者資料包協議 UDP(User Datagram Protocol):
-
UDP 在傳送資料之前不需要先建立連線,遠端主機在收到 UDP 報文後,不需要給出任何確認。
-
雖然 UDP 不提供可靠交付,但在某些情況下 UDP 確是一種最有效的工作方式(一般用於即時通訊),比如: QQ 語音、 QQ 視訊 、直播等等
? 傳輸控制協議 TCP(Transmission Control Protocol):
-
TCP 提供面向連線的服務。在傳送資料之前必須先建立連線,資料傳送結束後要釋放連線。
-
TCP 不提供廣播或多播服務。由於 TCP 要提供可靠的,面向連線的傳輸服務(TCP的可靠體現在TCP在傳遞資料之前,會有三次握手來建立連線,而且在資料傳遞時,有確認、視窗、重傳、流量控制、擁塞控制機制,在資料傳完後,還會四次揮手斷開連線用來節約系統資源),這不僅使協議資料單元的首部增大很多,還要佔用許多處理機資源。
-
TCP 一般用於檔案傳輸、傳送和接收郵件、遠端登入等場景。
2. TCP 報文段首部格式
TCP 報文段的具體格式大家可以不必都記住,但是其中的幾個控制位與我們接下來要講的三次握手和四次揮手息息相關,大家一定要牢記。
首部固定部分各欄位意義如下:
-
1 - 源埠和目的埠:各佔 2 個位元組,分別寫入源埠和目的埠。IP 地址 + 埠號就可以確定一個程式地址
-
2 - 序號/序列號(Sequense Number,SN):在一個 TCP 連線中傳送的位元組流中的每一個位元組都按順序編號。該欄位表示本報文段所傳送的資料的第一個位元組的序號。初始序號稱為 Init Sequense Number, ISN(序號/序列號這個欄位很重要,大家留個印象,下文會詳細講解)
例如,一報文段的序號是 101,共有 100 位元組的資料。這就表明:本報文段的資料的第一個位元組的序號是 101,最後一個位元組的序號是 200。顯然,下一個報文段的資料序號應當從 201 開始,即下一個報文段的序號欄位值應為 201。
-
3 - 確認號 ack:期望收到對方下一個報文段的第一個資料位元組的序號。若確認號為 N,則表明:到序號 N-1 為止的所有資料都已正確收到。
-
4 - 資料偏移(首部長度):它指出 TCP 報文段的資料起始處距離 TCP 報文段的起始處有多遠。這個欄位實際上是指出TCP報文段的首部長度。
-
5 - 保留:佔 6 位,應置為 0,保留為今後使用。
⭐ 大家看上圖,保留位的右邊還有 6 個控制位(重要),這是TCP 用來說明該報文段性質的:
-
緊急位 URG:當 URG = 1 時,表明此報文段中有緊急資料,是高優先順序的資料,應儘快傳送,不用在快取中排隊。該控制位需配合緊急指標使用(緊急指標指出本報文段中緊急資料的位元組數)
舉個例子:我們需要取消一個已經傳送了很長程式的執行,因此使用者從鍵盤發出中斷命令。如果不使用緊急資料,那麼這個指令將儲存在接收 TCP 的快取末尾,只有在所有的資料被處理完畢後這兩個字元才被交付接收方的應用程式,這樣做就無法實現立即中斷。
-
確認 ACK:僅當 ACK = 1 時確認號欄位才有效,當 ACK = 0 時確認號無效。TCP 規定,在連線建立後所有傳送的報文段都必須把 ACK 置為 1。
-
推送 PSH:當兩個應用程式進行互動式的通訊時,有時在一端的應用程式希望在鍵入一個命令後立即就能收到對方的響應。在這種情況下,TCP 就可以使用推送(push)操作。這時,傳送方 TCP 把 PSH 置為 1,並立即建立一個報文段傳送出去。接收方 TCP 收到 PSH = 1 的報文段,就儘快地交付接收應用程式。而不用等到整個快取都填滿了後再向上交付。
-
復位 RST:當 RST = 1 時,表明 TCP 連線中出現了嚴重錯誤(如由於主機崩潰或其他原因),必須釋放連線,然後再重新建立傳輸連線。
-
同步 SYN:SYN = 1 表示這是一個連線請求或連線接受報文。
當 SYN = 1 而 ACK = 0 時,表明這是一個連線請求報文段。對方若同意建立連線,則應在響應的報文段中使 SYN = 1 且 ACK = 1。
-
終止 FIN:用來釋放一個連線。當 FIN = 1時,表明此報文段的傳送發的資料已傳送完畢,並要求釋放運輸連線。
3. TCP 三次握手建立連線
① 三次握手過程詳解
三次握手的原文是 three-way handshake
,整個名詞的可以翻譯為:需要三個步驟才能建立握手/連線的機制。當然,三次握手也可以叫 three-message handshake
,通過三條訊息來建立的握手/連線。
進行三次握手的主要作用就是為了確認雙方的接收能力和傳送能力是否正常、指定自己的 初始化序列號(Init Sequense Number, ISN
) 為後面的可靠性傳輸做準備。
三次握手過程如下圖:
回顧一下圖中字元的含義:
-
SYN
:連線請求/接收 報文段 -
seq
:傳送的第一個位元組的序號 -
ACK
:確認報文段 -
ack
:確認號。希望收到的下一個資料的第一個位元組的序號
剛開始客戶端處於 Closed
的狀態,而服務端處於 Listen
狀態:
CLOSED
:沒有任何連線狀態
LISTEN
:偵聽來自遠方 TCP 埠的連線請求
1)第一次握手:客戶端向服務端傳送一個 SYN 報文(SYN = 1),並指明客戶端的初始化序列號 ISN(x),即圖中的 seq = x,表示本報文段所傳送的資料的第一個位元組的序號。此時客戶端處於 SYN_Send
狀態。
SYN-SENT
:在傳送連線請求後等待匹配的連線請求
2)第二次握手:伺服器收到客戶端的 SYN 報文之後,會傳送 SYN 報文作為應答(SYN = 1),並且指定自己的初始化序列號 ISN(y),即圖中的 seq = y。同時會把客戶端的 ISN + 1 作為確認號 ack 的值,表示已經收到了客戶端發來的的 SYN 報文,希望收到的下一個資料的第一個位元組的序號是 x + 1,此時伺服器處於 SYN_REVD
的狀態。
SYN-RECEIVED
:在收到和傳送一個連線請求後等待對連線請求的確認
3)第三次握手:客戶端收到伺服器端響應的 SYN 報文之後,會傳送一個 ACK 報文,也是一樣把伺服器的 ISN + 1 作為 ack 的值,表示已經收到了服務端發來的的 SYN 報文,希望收到的下一個資料的第一個位元組的序號是 y + 1,並指明此時客戶端的序列號 seq = x + 1(初始為 seq = x,所以第二個報文段要 +1),此時客戶端處於 Establised
狀態。
伺服器收到 ACK 報文之後,也處於 Establised 狀態
,至此,雙方建立起了 TCP 連線。
ESTABLISHED
:代表一個開啟的連線,資料可以傳送給使用者
② 為什麼要三次握手
三次握手的目的是建立可靠的通訊通道,說到通訊,簡單來說就是資料的傳送與接收,而三次握手最主要的目的就是雙方確認自己與對方的傳送與接收是正常的。
只有經過三次握手才能確認雙發的收發功能都正常,缺一不可:
-
第一次握手(客戶端傳送 SYN 報文給伺服器,伺服器接收該報文):客戶端什麼都不能確認;伺服器確認了對方傳送正常,自己接收正常
-
第二次握手(伺服器響應 SYN 報文給客戶端,客戶端接收該報文):
客戶端確認了:自己傳送、接收正常,對方傳送、接收正常;
伺服器確認了:對方傳送正常,自己接收正常
-
第三次握手(客戶端傳送 ACK 報文給伺服器):
客戶端確認了:自己傳送、接收正常,對方傳送、接收正常;
伺服器確認了:自己傳送、接收正常,對方傳送、接收正常
③ ISN (Initial Sequence Number) 是固定的嗎
三次握手的其中一個重要功能是客戶端和服務端交換 ISN(Initial Sequence Number),以便讓對方知道接下來接收資料的時候如何按序列號組裝資料。
當一端為建立連線而傳送它的 SYN 時,它會為連線選擇一個初始序號。ISN 隨時間而變化,因此每個連線都將具有不同的 ISN。如果 ISN 是固定的,攻擊者很容易猜出後續的確認號,因此 ISN 是動態生成的。
④ 三次握手過程中可以攜帶資料嗎
第三次握手的時候,是可以攜帶資料的。但是,第一次、第二次握手絕對不可以攜帶資料
假如第一次握手可以攜帶資料的話,如果有人要惡意攻擊伺服器,那他每次都在第一次握手中的 SYN 報文中放入大量的資料,然後瘋狂重複發 SYN 報文的話(因為攻擊者根本就不用管伺服器的接收、傳送能力是否正常,它就是要攻擊你),這會讓伺服器花費很多時間、記憶體空間來接收這些報文。
⭐ 簡單的記憶就是,請求連線/接收 即 SYN = 1
的時候不能攜帶資料
而對於第三次的話,此時客戶端已經處於 ESTABLISHED
狀態。對於客戶端來說,他已經建立起連線了,並且也已經知道伺服器的接收、傳送能力是正常的了,所以當然能正常傳送/攜帶資料了。
⑤ 半連線佇列
伺服器第一次收到客戶端的 SYN 之後,就會處於 SYN_RCVD
狀態,此時雙方還沒有完全建立其連線,伺服器會把這種狀態下的請求連線放在一個佇列裡,我們把這種佇列稱之為半連線佇列。
當然還有一個全連線佇列,完成三次握手後建立起的連線就會放在全連線佇列中。如果佇列滿了就有可能會出現丟包現象。
⑥ SYN 洪泛攻擊
SYN 攻擊就是 Client 在短時間內偽造大量不存在的 IP 地址,並向 Server 不斷地傳送 SYN 包,Server 則回覆確認包,並等待 Client 確認,由於源地址不存在,因此 Server 需要不斷重發直至超時,這些偽造的 SYN 包將長時間佔用半連線佇列,導致正常的 SYN 請求因為佇列滿而被丟棄,從而引起網路擁塞甚至系統癱瘓。
⑦ 如果第三次握手丟失了,客戶端服務端會如何處理
伺服器傳送完 SYN-ACK 包,如果未收到客戶端響應的確認包,也即第三次握手丟失。那麼伺服器就會進行首次重傳,若等待一段時間仍未收到客戶確認包,就進行第二次重傳。如果重傳次數超過系統規定的最大重傳次數,則系統將該連線資訊從半連線佇列中刪除。
注意,每次重傳等待的時間不一定相同,一般會是指數增長,例如間隔時間為 1s,2s,4s,8s…
4. TCP 四次揮手釋放連線
① 四次揮手過程詳解
建立一個 TCP 連線需要三次握手,而終止一個 TCP 連線要經過四次揮手(也有將四次揮手叫做四次握手的)。這是由於 TCP 的半關閉(half-close)特性造成的,TCP 提供了連線的一端在結束它的傳送後還能接收來自另一端資料的能力。
TCP 連線的釋放需要傳送四個包(執行四個步驟),因此稱為四次揮手(Four-way handshake
),客戶端或服務端均可主動發起揮手動作。
回顧一下上圖中符號的意思:
-
FIN
:連線終止位 -
seq
:傳送的第一個位元組的序號 -
ACK
:確認報文段 -
ack
:確認號。希望收到的下一個資料的第一個位元組的序號
剛開始雙方都處於ESTABLISHED
狀態,假設是客戶端先發起關閉請求。四次揮手的過程如下:
1)第一次揮手:客戶端傳送一個 FIN 報文(請求連線終止:FIN = 1),報文中會指定一個序列號 seq = u。並停止再傳送資料,主動關閉 TCP 連線。此時客戶端處於 FIN_WAIT1
狀態,等待服務端的確認。
FIN-WAIT-1
- 等待遠端TCP的連線中斷請求,或先前的連線中斷請求的確認;
2)第二次揮手:服務端收到 FIN 之後,會傳送 ACK 報文,且把客戶端的序號值 +1 作為 ACK 報文的序列號值,表明已經收到客戶端的報文了,此時服務端處於 CLOSE_WAIT
狀態。
CLOSE-WAIT
- 等待從本地使用者發來的連線中斷請求;
此時的 TCP 處於半關閉狀態,客戶端到服務端的連線釋放。客戶端收到服務端的確認後,進入FIN_WAIT2
(終止等待 2)狀態,等待服務端發出的連線釋放報文段。
FIN-WAIT-2
- 從遠端TCP等待連線中斷請求;
3)第三次揮手:如果服務端也想斷開連線了(沒有要向客戶端發出的資料),和客戶端的第一次揮手一樣,傳送 FIN 報文,且指定一個序列號。此時服務端處於 LAST_ACK
的狀態,等待客戶端的確認。
LAST-ACK
- 等待原來發向遠端TCP的連線中斷請求的確認;
4)第四次揮手:客戶端收到 FIN 之後,一樣傳送一個 ACK 報文作為應答(ack = w+1),且把服務端的序列值 +1 作為自己 ACK 報文的序號值(seq=u+1),此時客戶端處於 TIME_WAIT
(時間等待)狀態。
TIME-WAIT
- 等待足夠的時間以確保遠端TCP接收到連線中斷請求的確認;
? 注意 !!!這個時候由服務端到客戶端的 TCP 連線並未釋放掉,需要經過時間等待計時器設定的時間 2MSL(一個報文的來回時間) 後才會進入 CLOSED
狀態(這樣做的目的是確保服務端收到自己的 ACK 報文。如果服務端在規定時間內沒有收到客戶端發來的 ACK 報文的話,服務端會重新傳送 FIN 報文給客戶端,客戶端再次收到 FIN 報文之後,就知道之前的 ACK 報文丟失了,然後再次傳送 ACK 報文給服務端)。服務端收到 ACK 報文之後,就關閉連線了,處於 CLOSED
狀態。
② 為什麼要四次揮手
由於 TCP 的半關閉(half-close)特性,TCP 提供了連線的一端在結束它的傳送後還能接收來自另一端資料的能力。
任何一方都可以在資料傳送結束後發出連線釋放的通知,待對方確認後進入半關閉狀態。當另一方也沒有資料再傳送的時候,則發出連線釋放通知,對方確認後就完全關閉了TCP連線。
通俗的來說,兩次握手就可以釋放一端到另一端的 TCP 連線,完全釋放連線一共需要四次握手。
? 關注公眾號 | 飛天小牛肉,即時獲取更新
-
博主東南大學研究生在讀,利用課餘時間運營一個公眾號『 飛天小牛肉 』,2020/12/29 日開通,專注分享計算機基礎(資料結構 + 演算法 + 計算機網路 + 資料庫 + 作業系統 + Linux)、Java 基礎和麵試指南的相關原創技術好文。本公眾號的目的就是讓大家可以快速掌握重點知識,有的放矢。希望大家多多支援哦,和小牛肉一起成長 ?
-
並推薦個人維護的開源教程類專案: CS-Wiki(Gitee 推薦專案,現已 0.9k star), 致力打造完善的後端知識體系,在技術的路上少走彎路,歡迎各位小夥伴前來交流學習 ~ ?