前言
在面試中,計算機網絡的 TCP 三次握手和四次揮手是很常見的問題,但是在實際面試中,面試官會更願意聽到怎樣的回答呢?詳細程度是怎樣的?
越簡單常見的問題越不可小覷,萬丈高樓平地起,把簡單的問題深入化,才更能拉開與競爭者的距離。掌握了本文講的全部知識點,關於 TCP 三次握手和四次揮手基本就OK 了😊
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 一般用於文件傳輸、發送和接收郵件、遠程登錄等場景。
TCP 報文段首部格式
TCP 報文段的具體格式大家可以不必都記住,但是其中的幾個控制位與我們接下來要講的三次握手和四次揮手息息相關,大家一定要牢記。
首部固定部分各字段意義如下:
- 源端口和目的端口: 各佔 2 個字節,分別寫入源端口和目的端口。
IP 地址
+端口號
就可以確定一個進程地址。 - 序號/序列號(Sequense Number,SN): 在一個TCP 連接中傳送的字節流中的每一個字節都按順序編號。該字段表示本報文段所發送的數據的第一個字節的序號。初始序號稱為Init Sequense Number,ISN(序號/序列號這個字段很重要,大家留個印象,下文會詳細講解)
例如,一報文段的序號是 101,共有 100 字節的數據。這就表明:本報文段的數據的第一個字節的序號是 101,最後一個字節的序號是 200。顯然,下一個報文段的數據序號應當從 201 開始,即下一個報文段的序號字段值應為 201。 - 確認號 ack: 期望收到對方下一個報文段的第一個數據字節的序號。若確認號為 N,則表明:到序號 N-1 為止的所有數據都已正確收到。
- 數據偏移(首部長度): 它指出 TCP 報文段的數據起始處距離 TCP 報文段的起始處有多遠。這個字段實際上是指出 TCP 報文段的首部長度。
- 保留: 佔 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 時,表明此報文段的發送發的數據已發送完畢,並要求釋放運輸連接。
TCP 三次握手建立連接
三次握手過程詳解
三次握手的原文是 three-way handshake
,整個名詞的可以翻譯為:需要三個步驟才能建立握手/連接的機制。當然,三次握手也可以叫 three-message handshake
,通過三條消息來建立的握手/連接。
進行三次握手的主要作用就是為了確認雙方的接收能力和發送能力是否正常、指定自己的 初始化序列號 (Init Sequense Number,ISN
) 為後面的可靠性傳輸做準備。
三次握手過程如下圖:
回顧一下圖中字符的含義:
- SYN: 連接請求/接收報文段
- seq: 發送的第一個字節的序號
- ACK: 確認報文段
- ack: 確認號。希望收到的下一個數據的第一個字節的序號
剛開始客戶端處於 Closed
的狀態,而服務端處於 Listen
狀態:
CLOSED
: 沒有任何連接狀態LISTEN
: 偵聽來自遠方 TCP 端口的連接請求
- 第一次握手: 客戶端向服務端發送一個 SYN 報文(SYN = 1),並指明客戶端的初始化序列號 ISN(x),即圖中的 seq = x,表示本報文段所發送的數據的第一個字節的序號。此時客戶端處於
SYN_Send
狀態。
SYN-SENT: 在發送連接請求後等待匹配的連接請求
- 第二次握手: 服務器收到客戶端的 SYN 報文之後,會發送 SYN 報文作為應答(SYN = 1),並且指定自己的初始化序列號 ISN(y),即圖中的 seq = y。同時會把客戶端的 ISN + 1 作為確認號 ack 的值,表示已經收到了客戶端發來的的 SYN 報文,希望收到的下一個數據的第一個字節的序號是 x + 1,此時服務器處於
SYN_REVD
的狀態。
SYN-RECEIVED: 在收到和發送一個連接請求後等待對連接請求的確認
- 第三次握手: 客戶端收到服務器端響應的 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…
TCP 四次揮手釋放連接
四次揮手過程詳解
建立一個 TCP 連接需要三次握手,而終止一個 TCP 連接要經過四次揮手(也有將四次揮手叫做四次握手的)。這是由於 TCP 的半關閉(half-close)特性造成的,TCP 提供了連接的一端在結束它的發送後還能接收來自另一端數據的能力。
TCP 連接的釋放需要發送四個包(執行四個步驟),因此稱為四次揮手(Four-way handshake
),客戶端或服務端均可主動發起揮手動作。
回顧一下上圖中符號的意思:
- FIN: 連接終止位
- seq: 發送的第一個字節的序號
- ACK: 確認報文段
- ack: 確認號。希望收到的下一個數據的第一個字節的序號
剛開始雙方都處於 ESTABLISHED
狀態,假設是客戶端先發起關閉請求。四次揮手的過程如下:
- 第一次揮手: 客戶端發送一個 FIN 報文(請求連接終止:FIN = 1),報文中會指定一個序列號 seq = u。並停止再發送數據,主動關閉 TCP 連接。此時客戶端處於
FIN_WAIT1
狀態,等待服務端的確認。
FIN-WAIT-1: 等待遠程 TCP 的連接中斷請求,或先前的連接中斷請求的確認
- 第二次揮手: 服務端收到 FIN 之後,會發送 ACK 報文,且把客戶端的序號值 +1 作為 ACK 報文的序列號值,表明已經收到客戶端的報文了,此時服務端處於
CLOSE_WAIT
狀態。
CLOSE-WAIT: 等待從本地用戶發來的連接中斷請求
此時的 TCP 處於半關閉狀態,客戶端到服務端的連接釋放。客戶端收到服務端的確認後,進入 FIN_WAIT2
(終止等待2)狀態,等待服務端發出的連接釋放報文段。
FIN-WAIT-2: 從遠程TCP等待連接中斷請求
- 第三次揮手: 如果服務端也想斷開連接了(沒有要向客戶端發出的數據),和客戶端的第一次揮手一樣,發送 FIN 報文,且指定一個序列號。此時服務端處於
LAST_ACK
的狀態,等待客戶端的確認。
LAST-ACK: 等待原來發向遠程TCP的連接中斷請求的確認
- 第四次揮手: 客戶端收到 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 連接,完全釋放連接一共需要四次握手。
舉個例子: A 和 B 打電話,通話即將結束後,A 說"我沒啥要說的了",B 回答"我知道了",於是 A 向 B 的連接釋放了。但是 B 可能還會有要說的話,於是 B 可能又巴拉巴拉說了一通,最後 B 說"我說完了",A 回答"知道了",於是 B 向 A 的連接釋放了,這樣整個通話就結束了。