經典面試題:TCP 三次握手、四次揮手詳解
在網絡通信的復雜架構里,“三次握手”與“四次揮手”仿若一座無形的橋梁,它們是連接客戶端與服務器的關鍵紐帶。這座“橋梁”不僅確保了連接的穩固建立,還保障了連接的有序結束,使得網絡世界中的信息能夠順暢、準確地流動。
在面試過程中,TCP 三次握手、四次揮手也經常被問到的問題。本文就來快速、詳細的介紹下 TCP 三次握手、四次揮手的全部過程。
TCP 的三次握手和四次揮手實質就是 TCP 通信的連接和斷開:
- 三次握手:同步雙方的初始序列號(ISN),確認雙方收發能力正常;
- 四次揮手:雙方獨立關閉數據通道,確保數據完整傳輸。
一、TCP頭格式組成
為了使你更好的理解 TCP 三次握手和四次揮手過程,本小節先介紹下 TCP 頭部格式。
(1) 源端口號和目的端口號:代表連接發起方和連接接收方
(2) 序號:在建立連接時,由計算機生產的隨機數作為初始值,通過SYN包傳給接收端主機,每發送一次數據,就累加一次該數據字節數的大小。用來解決網絡包亂序問題。
(3) 確認序號:指下一次期望收到的數據的序號,發送端收到這個確認應答以后,可以認為在這個序號以前的數據,都已經被正常接收。 用來解決網絡丟包的問題
(4) 標志位,如上圖,一共6個
- URG
- ACK:該位為 1 時,「確認應答」的字段變為有效,TCP 規定除了最初建立連接時的 SYN 包之外該位必須設置為 1
- PSH
- RST:該位為 1 時,表示 TCP 連接中出現異常必須強制斷開連接。
- SYN:該位為 1 時,表示希望建立連接,并在其「序列號」的字段進行序列號初始值的設定。
- FIN:該位為 1 時,表示今后不會再有數據發送,希望斷開連接。當通信結束希望斷開連接時,通信雙方的主機之間就可以相互交換 FIN 位為 1 的 TCP 段。
(5) 數據:連接需要發送的內容。
二、建立連接:三次握手
三次握手(Three-way Handshake)是 TCP 協議中用于建立連接的一個重要環節。在這一過程中,客戶端和服務器需要互相發送三個數據包,以確保雙方的接收和發送能力均正常,并為后續的數據傳輸指定初始化序列號,從而確保數據傳輸的可靠性。
TCP 三次握手流程圖如下所示:
圖中字符詳解:
- SYN:代表連接請求或接收的報文段。
- seq:指發送的第一個字節的序號。
- ACK:確認報文段,用于回應 SYN。
- ack:確認號,表示希望收到的下一個數據的第一個字節的序號。
在 TCP 協議中,主動發起連接請求的一方被稱為客戶端,而被動等待連接的一方則被稱為服務端。無論是客戶端還是服務端,一旦 TCP 連接成功建立,雙方均可進行數據的發送與接收。
連接建立之初,服務器和客戶端都處于 CLOSED 狀態。在通信正式開始前,雙方需要分別創建自己的傳輸控制塊(TCB)。服務器完成 TCB 創建后,會進入 LISTEN 狀態,隨時準備接收客戶端發來的連接請求。
1. 第一次握手
客戶端向服務端發送一個 SYN 報文(SYN=1),并指明客戶端的初始化序列號 ISN(x),即圖中的 seq=x,它表示本報文段所發送的數據的第一個字節的序號。在發送 SYN 報文后,客戶端進入 SYN_SENT 狀態,意味著它正在等待服務端的連接確認。
SYN_SENT 狀態解釋:當客戶端發送連接請求后,它進入 SYN_SENT 狀態,等待服務端的響應。在這個狀態下,客戶端準備好了接受服務端的連接確認。
TCP 協議規定:SYN=1 的報文段是用于建立連接的請求,它不攜帶任何數據,但會消耗一個序號。這是 TCP 協議確保連接建立過程中的有序性和可靠性的一種方式。
2. 第二次握手
服務器在接收到客戶端的 SYN 報文后,會以 SYN 報文作為回應(SYN=1),并賦予自己獨特的初始化序列號ISN(y),即圖中的 seq=y。同時,服務器將客戶端的 ISN+1 設置為確認號 ack 的值,以此確認已收到客戶端的 SYN 報文,并期待接收到的下一個數據報的起始序號為 x+1。在此之后,服務器會進入 SYN-RCVD 狀態,等待對連接請求的進一步確認。
SYN-RCVD 狀態解析:當服務器在收到并發送連接請求后,會進入 SYN-RCVD 狀態,此時它正在等待對初始連接請求的確認。在這個狀態下,服務器已經準備好接受來自客戶端的進一步通信。
TCP 協議規定:SYN=1 且 ACK=1 的報文段是用于確認連接的應答,它同樣不攜帶任何數據,但通過確認號的使用,確保了連接建立過程中的有序性和可靠性。
3. 第三次握手
在收到服務器發送的 SYN 報文后,客戶端會回應一個 ACK 報文。這個 ACK 報文將服務器的 ISN+1 作為 ack 的值,表明客戶端已經收到了服務器的 SYN 報文,并期待接收到的下一個數據報的起始序號為 y+1。
同時,客戶端將自己的序列號 seq 設置為 x+1,即初始序列號 seq=x 增加 1。完成這些操作后,客戶端進入 ESTABLISHED 狀態,表示連接已成功建立。服務器在收到這個 ACK 報文后,也會轉入 ESTABLISHED 狀態,此時雙方連接的建立工作全部完成。
ESTABLISHED 狀態解釋:當一個 TCP 連接進入 ESTABLISHED 狀態時,它意味著連接已經打開,數據可以開始在雙方之間傳送。
三、斷開連接:四次揮手
TCP 連接的終止需要經過四次包的交換,因此被稱為四次揮手。在這四次交換中,客戶端或服務器都可以主動發起連接的釋放動作。值得注意的是,TCP 連接是雙向的,因此四次揮手中,前兩次主要用于斷開一個方向的連接,后兩次則用于斷開另一方向的連接。
1. 第一次揮手
客戶端首先發送一個 FIN 報文,其中包含一個序列號 seq=u,表示請求連接終止。在發送完畢后,客戶端停止數據發送,并主動關閉 TCP 連接。此時,客戶端進入 FIN_WAIT_1 狀態,等待服務器的確認。
FIN_WAIT_1 狀態解析:該狀態表示客戶端正在等待遠程 TCP 的連接中斷請求,或者等待先前連接中斷請求的確認。FIN=1 標志著該報文段是一個連接釋放請求。而 seq=u 則代表客戶端向服務器發送的最后一個字節的序號。
2. 第二次揮手
服務端在收到客戶端的 FIN 報文后,會發送一個 ACK 報文作為回應。這個 ACK 報文中,序列號值設為客戶端序號值加 1,意在確認已收到客戶端的報文。隨后,服務端進入 CLOSE_WAIT 狀態,等待本地用戶的連接中斷請求。
CLOSE_WAIT 狀態解析:在此狀態下,服務端等待來自本地用戶的連接釋放請求。ACK 報文中的 ACK=1 表示應答,而 seq=v 則指明了服務端釋放應答報文段的首字節序號。同時,ack=u+1 表明服務端希望從第 u+1 個字節開始接收報文段,并已成功接收了前 u 個字節。
完成第二次揮手后,客戶端到服務端的連接已釋放,服務端不再接收客戶端數據,而客戶端也已無數據待發送。然而,服務端到客戶端的連接仍保持開啟,若服務端在此期間發送數據,客戶端仍需正常接收。此狀態將持續一段時間,直至整個 CLOSE-WAIT 狀態結束。
3. 第三次揮手
服務端在完成數據的發送后,會向客戶端發送一個連接釋放報文。這個報文頭包含 FIN 標志位為 1,以及 ack 序號值為 u+1。由于在 CLOSE_WAIT 狀態期間,服務端可能又發送了一些數據,假設此時的序列號為 seq=w。發送完畢后,服務端進入 LAST_ACK 狀態,等待來自客戶端的連接中斷確認。
4. 第四次揮手
客戶端在收到服務端的 FIN 報文后,會響應一個 ACK 報文,其中 ack 序號值為 w+1,同時將自己的序列值加 1作為 ACK 報文的 seq 序號值,即 seq=u+1。此后,客戶端進入 TIME_WAIT 狀態。
TIME_WAIT:確保遠程 TCP 收到連接中斷請求的確認狀態會持續 2MSL(最長報文段壽命)的時間。在此期間, TCP 連接并未完全釋放。若在這段時間內未收到服務端的重發請求,客戶端將進入 CLOSED 狀態,并撤銷 TCB。
服務端在收到客戶端的確認 ACK 報文后,會立即進入 CLOSED 狀態,并撤銷 TCB,從而結束此次 TCP 連接。值得注意的是,服務端結束 TCP 連接的時間點通常早于客戶端。
四、四次揮手 vs 三次握手
階段 | 三次握手 | 四次揮手 |
目的 | 建立連接,同步序列號 | 安全關閉雙向數據通道 |
交互次數 | 3次 | 4次 |
關鍵標志位 | SYN 、ACK | FIN 、ACK |
狀態復雜度 | 簡單(直接建立) | 復雜(需處理半關閉和TIME_WAIT) |
五、關聯面試題
1. 為何 TCP 建立連接時采用三次握手而非兩次或四次?
- 兩次不夠:無法確認客戶端的接收能力(若服務端的SYN-ACK丟失,客戶端不知服務端已就緒)。
- 四次冗余:三次已能確保雙向通信能力,無需額外交互。
具體原因如下:
- 確保雙方都能發送和接收:三次握手可以確保雙方都具備發送和接收數據的能力。在第一次握手時,客戶端發起請求,第二次握手時,服務器確認收到了請求并表示自己也可以通信,第三次握手時,客戶端確認收到了服務器的響應。這保證了通信的可靠性。
- 防止重復連接初始化問題:假設沒有三次握手,而是兩次握手,那么可能會出現一種情況:客戶端發送了一個連接請求(SYN),但由于網絡問題,服務器沒有及時收到或者延遲了。客戶端在等待了一段時間后認為請求失敗,重新發送了一個新的 SYN,而此時第一個 SYN 報文延遲到達服務器,服務器誤認為客戶端又發起了一次新的連接請求,從而產生混亂。通過三次握手,能夠有效避免這種問題,確保連接的一致性。
- 同步初始序列號:三次握手過程中,客戶端和服務器會相互交換各自的初始序列號,以保證接下來的數據傳輸能夠按順序接收和處理。這是實現TCP可靠傳輸的關鍵步驟之一。
三次握手的設計主要是為了確保在不可靠的網絡環境中,TCP連接的建立過程能夠具有可靠性和一致性,并能夠防止潛在的錯誤連接。
2. 為何 TCP 關閉連接時需要四次揮手?
- 保證雙方數據完整性:在關閉連接之前,雙方需要確認所有數據已經被成功接收。第一次揮手時,客戶端發送 FIN 報文,表示自己不再發送數據,但仍然可以接收數據。第二次揮手時,服務器確認收到這個 FIN,并且可以繼續發送數據。第三次揮手時,服務器發送自己的 FIN 報文,表示自己也不再發送數據。最后,客戶端確認服務器的 FIN,確保雙方都完成了數據傳輸。
- 分半關閉(Half-Close):TCP 連接是全雙工的,這意味著數據可以雙向流動。四次揮手允許連接的一方關閉數據發送通道,但仍然可以接收數據,直到另一方也關閉發送通道。因此,四次揮手過程允許雙向關閉連接,確保雙方都能完成數據傳輸。
- 確保完整關閉:客戶端在進入TIME-WAIT狀態后,會等待一段時間以確保服務器收到了最后的 ACK 報文。這段時間可以避免因網絡延遲等問題導致的重復數據問題,確保連接完全關閉后再釋放資源。
3. 為何 TIME_WAIT 狀態需持續 2MSL 后才能轉為 CLOSE 狀態?
當 TCP 連接的一方完成連接釋放后,會進入 TIME_WAIT 狀態。這個狀態需要持續 2 倍的最大段壽命(Maximum Segment Lifetime,MSL)的時間,這是為了確保在傳輸過程中可能存在的延遲數據包能夠被對方完全接收。只有當 2MSL 時間過去后,確認對方已收到所有數據,該 TCP 連接才能完全關閉,進 入CLOSE 狀態。
(1) 確保服務端能夠接收到客戶端的確認應答。
如果客戶端在發送完確認應答后立即進入 CLOSED 狀態,而該應答不幸丟失,服務端在等待超時后將嘗試重新發送連接釋放請求。但此時,由于客戶端已經關閉,無法再作出響應,這會導致服務端無法正常關閉 TCP 連接。因此,TIME_WAIT 狀態的持續存在,是為了保證服務端能夠接收到并處理客戶端的確認應答,從而確保連接的平滑關閉。
(2) 防止“三次握手”中提及的“已失效的連接請求報文段”干擾當前連接。
當客戶端發送完最后一個確認報文后,經過 2MSL 的時間間隔,可以確保在本連接持續時間內產生的所有報文段都已從網絡中清除。這樣,新建立的連接就不會受到舊連接請求報文的影響。
(3) 為什么是 2MSL?
2MSL 的時間是從客戶端收到 FIN 報文段后發送給服務器 ACK 開始計時的,考慮到重傳的因素,那么就需要服務器再次給客戶端傳 FIN+ACK 報文段。
保證在兩個傳輸方向上的尚未被接收或遲到的報文段都消失,理論上保證最后一個報文可靠到達,就需要 2MSL,一個方向一個 1MSL。
4. CLOSE_WAIT 狀態有什么影響?
當服務器收到客戶端的 FIN,并回復了 ACK 后,會進入 CLOSE_WAIT 狀態,此時 TCP 鏈接處于半關閉狀態。CLOSE_WAIT 狀態一直存在就說明服務器沒有調用 close 并沒有發送 FIN 報文段。而服務期長期保持這個狀態,就會一直占用這大量的 socket 文件描述符,大量的 CLOSE_WAIT 狀態存在就會導致文件描述符被占用,一些客戶端無法連接。
5. TIME_WAIT和CLOSE_WAIT的區別?
CLOSE_WAIT 狀態是被動關閉的一端在接收到另一端關閉請求過后并將 ACK 發送出去后所處的狀態。這種狀態表示:收到了對端關閉的情況,但是本端還沒有完成工作,未關閉。
TIME_WAIT 狀態是主動關閉一端在本端已經關閉的前提下,收到對端的關閉請求并且將 ACK 發送出去所處的狀態。這種狀態表示:雙方都已經完成工作,只是為了確保遲來的數據報能被識別丟棄,可靠的終止 TCP 連接。
6. 為什么連接的時候是三次握手,關閉的時候卻是四次揮手?
因為當服務端收到客戶端的 SYN 連接請求報文后,可以直接發送 SYN+ACK 報文。其中 ACK 報文是用來應答的,SYN 報文是用來同步的。
但是關閉連接時,當服務端收到 FIN 報文時,很可能并不會立即關閉 SOCKET,所以只能先回復一個 ACK 報文,告訴客戶端,“你發的 FIN 報文我收到了”。只有等到我服務端所有的報文都發送完了,我才能發送 FIN 報文,因此不能一起發送。故需要四步握手。
7. 如果已經建立了連接,但是客戶端突然出現故障了怎么辦?
TCP 設有一個保活計時器,顯然,客戶端如果出現故障,服務器不能一直等下去,白白浪費資源。服務器每收到一次客戶端的請求后都會重新復位這個計時器,時間通常是設置為 2 小時,若 2 小時還沒有收到客戶端的任何數據,服務器就會發送一個探測報文段,以后每隔 75 分鐘發送一次。若一連發送 10 個探測報文仍然沒反應,服務器就認為客戶端出了故障,接著就關閉連接。
8. 在三次握手中,SYN 和 ACK 的作用是什么?
在 TCP 的三次握手中,SYN 和 ACK 確保了連接的建立和通信的同步。
- SYN(Synchronize)是 TCP 協議中的一個標志位,用于在建立連接時進行通信的同步。在三次握手的第一次交互中,客戶端發送一個 SYN=1,ACK=0 標志的數據包給服務端,請求建立連接。這個 SYN 包的作用是向服務端發起連接請求,并附帶一個序列號(Sequence Number),用于標識后續發送的數據包。SYN 標志位的設置表示客戶端希望建立一個新的連接或確認一個連接請求;
- ACK(Acknowledgement)是確認標志,用于確認接收到的數據包。在第二次握手中,服務端收到客戶端的 SYN 包后,會發送一個 SYN=1,ACK=1 標志的數據包給客戶端。這個 SYN+ACK 包的作用是告訴客戶端,服務端已經收到了連接請求,并允許建立連接。同時,ACK=1 表示服務端對客戶端發送的 SYN 包進行了確認。此外,服務端也會發送自己的序列號給客戶端,用于后續的數據傳輸。
- 在第三次握手中,客戶端收到服務端的 SYN+ACK 包后,會發送一個 SYN=0,ACK=1 的數據包給服務端。這個 ACK 包的作用是告訴服務端,客戶端已經收到了 SYN+ACK 包,并對服務端的 SYN 包進行了確認。
- 至此,三次握手完成,TCP 連接建立成功,雙方可以開始進行數據傳輸。
在整個過程中,SYN 和 ACK 標志位確保了連接的建立和通信的同步。SYN 用于發起連接請求和標識序列號,而ACK 用于確認接收到的數據包。這種機制可以有效地確保數據的可靠傳輸和連接的穩定性。
9. 三次握手過程中可以攜帶數據嗎?
在 TCP 的三次握手過程中,SYN 和 SYN+ACK 報文段是不攜帶數據的,它們僅僅用于建立連接時的同步和確認。但是,最后一次的 ACK 報文段是可以攜帶數據的。這是因為當發送方收到對方的 SYN+ACK 報文段后,連接就已經建立了,此時發送方就可以立即發送數據,而這個數據就可以和 ACK 報文段一起發送,從而提高了效率。
雖然第三次握手可以攜帶數據,但在實際網絡編程中,并不推薦這樣做。因為這樣做可能會帶來一些問題,比如接收方可能無法及時準備好接收數據,導致數據丟失或亂序。
因此,通常建議將數據的發送放在三次握手完成之后進行,以確保數據的可靠傳輸。
10. TCP 連接中的半連接隊列和全連接隊列是什么?
TCP 連接中的半連接隊列(也稱為 SYN 隊列)用于存儲處于 TCP 三次握手過程中第一步的連接請求。
當服務端收到客戶端發起的 SYN 請求后,內核會把該連接存儲到半連接隊列中,等待完成三次握手的過程。此時,連接請求還沒有完成握手,因此被認為是“半連接”。如果半連接隊列滿了,新來的連接請求可能會被丟棄或者根據系統配置發送 RST 報文。
全連接隊列就是已經完成三次握手,建立起連接的就會放在全連接隊列中。
半連接隊列的主要作用是管理并跟蹤那些尚未完全建立的連接,確保在三次握手完成之前,這些連接請求能夠得到妥善的處理。它是 TCP 協議保證連接可靠性和性能的重要機制之一。
需要注意的是,當服務端并發處理大量請求時,如果 TCP 半連接隊列過小,就容易出現溢出的情況,導致后續的請求被丟棄,從而影響服務端的請求處理能力。因此,合理設置和調整半連接隊列的大小對于優化網絡性能和提升系統穩定性具有重要意義。
11. TCP 連接中 ISN(Initial Sequence Number)是什么?
在 TCP 連接中,ISN(Initial Sequence Number,初始序列號)是每個 TCP 連接在建立時由 TCP 協議為每一個數據包所賦予的序列號。它是 TCP 可靠傳輸的一個重要組成部分,用于確保數據的順序性和完整性。
- 當 TCP 連接建立時,客戶端和服務器都會選擇一個初始序列號(ISN)作為它們發送的第一個數據包的序列號。這個序列號是一個隨機值,通常不會重復,用于標識該連接中的每一個數據包的順序。
- 在數據傳輸過程中,TCP 協議會根據數據包的發送順序為每個數據包分配一個遞增的序列號。ISN 可以看作是一個 32 比特的計數器,每 4ms 加 1 。
- 通過序列號,接收方可以準確地按照發送方的發送順序來重組數據,從而確保數據的順序性。
- 此外,序列號還可以用于檢測丟失或重復的數據包。當接收方發現數據包的序列號不連續時,它會向發送方發送一個 ACK(確認)消息,請求發送方重傳丟失的數據包。同樣地,如果接收方接收到一個重復的數據包(即具有相同序列號的數據包),它可以通過序列號來識別并丟棄這個重復的數據包。
因此,ISN 在 TCP 連接確保了數據的順序性和完整性,為 TCP 的可靠傳輸提供了基礎。
12. 為什么 TCP 連接建立需要發送序列號?
TCP 連接建立需要發送序列號的原因主要有以下幾點:
- 確保數據的順序性: TCP 是一個面向連接的、可靠的、基于字節流的傳輸層通信協議。在 TCP 通信中,發送方和接收方都需要按照數據的發送順序來接收和處理數據。序列號用于標識每一個發送的數據包,確保接收方能夠按照正確的順序重組數據。
- 實現可靠傳輸: TCP 通過序列號來實現數據的可靠傳輸。當接收方收到數據包后,會向發送方發送一個確認(ACK)消息,告知已經成功接收到的數據包的序列號。如果發送方在某個時間點內沒有收到某個數據包的 ACK,它會認為該數據包丟失,并重新發送該數據包。序列號使得發送方能夠準確地知道哪些數據包已經成功發送并被接收,哪些數據包需要重傳。
- 處理網絡中的數據包亂序: 在網絡傳輸過程中,由于網絡擁塞、路由變化等原因,數據包可能會亂序到達接收方。通過序列號,接收方能夠識別并重新排序這些亂序的數據包,確保數據的完整性和正確性。
- 流量控制和擁塞控制: TCP 還利用序列號來實現流量控制和擁塞控制。發送方會根據接收方的確認消息和當前的網絡狀況來調整發送速率,以避免網絡擁塞和數據丟失。序列號在這個過程中起到了關鍵作用,幫助發送方和接收方協調數據的發送和接收。
TCP 連接建立需要發送序列號是為了確保數據的順序性、實現可靠傳輸、處理網絡中的數據包亂序以及實現流量控制和擁塞控制。這些功能共同保證了 TCP 能夠提供高效、可靠的數據傳輸服務。
13. 在 TCP 通信中,如果一方突然崩潰,另一方如何知道?
在 TCP 通信中,如果一方突然崩潰(例如,由于硬件故障、操作系統崩潰或應用程序異常終止),另一方通常通過以下幾種機制來檢測這種情況:
- 心跳機制(Keep-Alive): TCP 本身并沒有一個顯式的“心跳”機制,但許多操作系統和應用程序層協議實現了這種機制。通過定期發送小的數據包(通常稱為“探測包”或“心跳包”),接收方可以通知發送方它仍然存活并且連接仍然有效。如果發送方在一段時間內沒有收到響應,它可能會認為接收方已經崩潰,并關閉連接。
- 超時重傳和重試: TCP 使用超時重傳機制來處理丟失的數據包。當發送方發送一個數據包后,它會等待一個確認(ACK)。如果在一定的超時時間內沒有收到 ACK,發送方會重傳該數據包。如果經過多次重傳仍然未收到確認,發送方會認為連接已經中斷,并關閉連接。同樣,接收方在收到亂序的數據包或數據包丟失時,也會通過發送重復 ACK 或通知發送方進行快速重傳來處理。
- 應用層協議: 除了 TCP 本身的機制外,應用層協議(如 HTTP、FTP 等)通常也會實現自己的連接管理和錯誤處理機制。這些協議可能會定義特定的消息或命令來通知對方連接已經中斷,或者通過超時和重試策略來處理突然的崩潰。
- 操作系統和網絡棧的通知: 在某些情況下,當一方崩潰時,操作系統或網絡棧可能會向另一方發送一個特殊的信號或錯誤消息。例如,當 TCP 連接的一方異常終止時,操作系統可能會向另一方發送一個 RST(重置)數據包來關閉連接。
由于網絡的復雜性和不確定性,有時候即使一方崩潰,另一方也可能無法立即檢測到。這取決于網絡狀況、操作系統的實現以及應用層協議的設計。因此,在設計和實現基于 TCP 的應用程序時,應該考慮到這種可能性,并采取相應的錯誤處理和恢復策略。