CP三次握手和四次揮手,是面試官最愛問的“開場白”之一
別看它基礎,真要講清楚細節,分分鐘讓你冷汗直流!
這玩意兒就跟程序員相親一樣:
表面上問的是“你老家哪的”
實際上是在試探你有沒有房、有沒有車、能不能落戶!
很多同學一開始答得頭頭是道
結果面試官一連追問:“為啥不是兩次握手?”、“TIME_WAIT干啥的?”、“為啥揮手要四次?”
很多小伙伴在面試中可能會這樣答:
“三次握手就是客戶端和服務端互相確認連接狀態的過程,四次揮手則是斷開連接時雙方分別關閉各自的發送通道。”
答案對嗎?當然對!
但距離面試官的期望,可能還差那么一點點……
記住:簡單的回答只是開始,深入的探討才是關鍵。
其中有較多的細節問題,本篇文章全部會詳細講解!
接下來,希望大家能帶著思考進行閱讀,收獲會更大。
Part1什么是TCP協議
你可以把它理解成互聯網世界里的“老司機”
專治各種不可靠網絡環境下的數據傳輸問題
它的口號是:“我不光要傳得出去,還得收得回來!
簡單來說,TCP 是一種 面向連接、可靠傳輸 的協議
它存在的意義,就是為了在那些“信號差、丟包多、誰都不靠譜”的網絡環境下,給我們提供一條穩定、有序、不丟包、不亂序的數據傳輸高速公路。
Part2那TCP中的面向連接呢?
就是說,在真正開始傳數據之前
客戶端和服務器必須先坐下來“握個手”,確認彼此都在線、都能聽懂對方說話
不能像發短信一樣發完就走,也不管對方有沒有收到。
就像相親前先加個微信聊兩句
總比直接上門提親強吧?
Part3可靠傳輸?怎么保證?
- 數據是按順序傳送的,不會前面的還沒到,后面的先來了;
- 每次傳數據都會做校驗,要是數據在路上被“壓碎”了,接收方立馬能發現;
- 如果發現丟了包、錯亂了,TCP 就會自動重傳、排序,直到你滿意為止。
總之,TCP 的原則是:寧可慢一點,也不能錯一點!
Part4那三次握手又是干嘛的?
為了建立這個“穩如老狗”的連接
TCP 設計了一個流程叫?三次握手
目的很簡單:確認雙方的發送和接收能力都沒毛病!
第一次握手:客戶端 → 服務端
客戶端主動發起請求,發送一個?SYN=1?的報文(同步標志),告訴服務端:“我想和你建立連接。”
同時指定了自己的初始序列號?seq=x。
這個 SYN 報文不能攜帶數據,但它會消耗一個序號。
此時客戶端進入?SYN_SENT?狀態,
心里默念:“我在等你回消息……”
第二次握手:服務端 → 客戶端
服務端收到 SYN 后,表示收到了連接請求,于是它也回一個?SYN=1,并帶上自己的初始序列號?seq=y。
同時,把客戶端的?seq+1?作為確認號?ack=x+1,
意思是:“我已經收到了你的SYN,老鐵。”
這次回應中?SYN=1,ACK=1,
服務端進入?SYN_RCVD?狀態,
心想:“兄弟挺有誠意,我也回應一波。”
第三次握手:客戶端 → 服務端
客戶端收到服務端的 SYN 后,再回一個?ACK=1?的確認報文,
將服務端的?seq+1?作為確認號?ack=y+1,
表示:“我也收到了你的SYN,咱們可以正式開始了!”
這時候客戶端進入?ESTABLISHED?狀態,
而服務端在收到這個 ACK 后,也進入?ESTABLISHED?狀態,
連接就正式建立了!
用大白話來講就是:
第一次:客戶端喊一聲:“喂,你能聽到嗎?”
第二次:服務端回應:“我能聽到,你能聽到我嗎?”
第三次:客戶端再確認:“我也能聽到,咱們可以開始了。”
這三輪對話一過,連接就算正式建立了
接下來就可以放心傳數據了,不怕斷、不怕亂、不怕丟!
Part5要三次握手?兩次不行嗎?
🤔 先問個問題:為啥非得三次握手?
三次握手的目的很簡單:
確保雙方的發送和接收能力都正常,并且同步初始序列號(ISN),為后續可靠傳輸打基礎。
🚀 那兩次握手行不行呢?
想象一下這個場景:
- 客戶端發出連接請求
- ,但因網絡原因,第一個請求報文丟了。
- 客戶端再發一次請求,這次服務端收到了并且回應了確認。
- 數據傳輸完畢后,連接釋放了。
- 然而,之前丟失的那個請求報文在某個角落里“冬眠”了一段時間,突然醒了,又飄到了服務端。
- 服務端以為這是個新的連接請求,于是又開始建立連接,結果發現客戶端不理它……
這就導致了一個問題:服務端一直在等待一個永遠不會來的數據包,浪費了資源!
因此,兩次握手不夠保險,必須通過第三次握手來確保雙方的狀態都正常。
Part6什么是半連接隊列?
🔍 半連接隊列是啥?
當服務端第一次收到客戶端的 SYN 請求時,會進入?SYN_RCVD?狀態。
此時,連接還沒完全建立,服務端會把這種狀態下的請求放在一個隊列中,這就是半連接隊列。
還有個全連接隊列,用于存放已經完成三次握手的連接。
如果隊列滿了,可能會導致丟包現象。
? 關于重傳次數
服務端發送完 SYN-ACK 后,如果沒收到客戶端的確認,會進行重傳。
每次重傳的時間間隔通常會指數增長(如 1s, 2s, 4s, 8s……),直到超過系統規定的最大重傳次數,才會從半連接隊列中刪除該連接。
Part7ISN 是固定的嗎?
🔢 ISN 是動態變化的
ISN 是一個隨時間變化的32位計數器,每4毫秒加1。
這樣設計是為了防止舊的分組在網絡中滯留太久,導致錯誤解釋。
固定ISN的風險:
如果ISN是固定的,攻擊者很容易猜出后續的確認號,從而發起攻擊。
所以,ISN是動態生成的,以增加安全性。
Part8三次握手可以攜帶數據嗎?
第三次握手是可以攜帶數據的,但第一次和第二次握手不可以。
為什么呢?
假如第一次握手可以攜帶數據,惡意攻擊者就可以在 SYN 報文中放入大量數據,瘋狂重復發送 SYN 報文,導致服務器花費大量時間和內存處理這些垃圾報文。
- 第一次握手不能帶數據
- :防止惡意攻擊。
- 第三次握手可以帶數據
- :因為此時客戶端已處于?ESTABLISHED?狀態,知道服務端的接收和發送能力正常。
Part9什么是 SYN 攻擊?
?? SYN 攻擊是什么?
SYN 攻擊是一種典型的 DoS/DDoS 攻擊手段。
攻擊者會在短時間內偽造大量不存在的 IP 地址,向服務器發送大量的 SYN 請求。
服務端回復確認包后,等待客戶端確認,但由于源地址是偽造的,這些確認包永遠不會到達,導致服務端資源被占用。
🚨 如何檢測和防御?
檢測:
你可以使用以下命令來檢測是否遭受 SYN 攻擊:
netstat?-n -p TCP | grep SYN_RECV
如果你看到大量來自隨機IP地址的半連接狀態,基本上可以斷定你正在遭受 SYN 攻擊。
防御方法:
- 縮短超時時間(SYN Timeout)
- :減少等待確認的時間。
- 增加最大半連接數
- :提高系統承受能力。
- 過濾網關防護
- :設置防火墻規則,限制異常流量。
- SYN cookies技術
- :通過算法生成SYN-ACK響應,減少對內存資源的依賴。
Part10TCP 四次揮手
如果說三次握手是“確認關系”,那四次揮手就是“和平分手”。
TCP 是個很講究感情的協議,它不會像 UDP 那樣“說完就走”,而是要一步步說清楚:“我要走了”、“我知道你要走了”、“我也要走了”、“收到,拜了個拜。”
整個過程就像情侶分手一樣,不是一句話就能搞定的。
而且,分手之后還要等一會兒才能徹底斷開連接,這叫“2MSL 等待狀態”。
💬 初始狀態:
- 客戶端和服務端都處于?ESTABLISHED?狀態;
- 假設客戶端想主動關閉連接,開始四次揮手。
第一次揮手:客戶端 → 服務端
客戶端發了一個?FIN 報文,表示:“我不再傳數據了。”
此時客戶端進入?FIN_WAIT1?狀態。
FIN = 1,seq = u
不再發送新數據,但仍可以接收數據。
第二次揮手:服務端 → 客戶端
服務端收到 FIN 后,馬上回一個?ACK 報文,表示:“我知道你要走了。”
并把客戶端的序號+1作為確認號返回。
此時服務端進入?CLOSE_WAIT?狀態,客戶端進入?FIN_WAIT2?狀態。
ACK = 1,ack = u+1,seq = v
這時候只是單向關閉,服務端還能繼續發數據給客戶端。
第三次揮手:服務端 → 客戶端
服務端處理完自己的數據后,也準備關閉連接,于是也發一個?FIN 報文,表示:“我也要走了。”
此時服務端進入?LAST_ACK?狀態。
FIN = 1,ACK = 1,seq = w,ack = u+1
第四次揮手:客戶端 → 服務端
客戶端收到服務端的 FIN 后,回一個?ACK 報文,表示:“收到啦,咱們正式拜拜。”
然后客戶端進入?TIME_WAIT?狀態,等待一段時間后才真正關閉連接。
服務端收到 ACK 后直接進入?CLOSED?狀態。
ACK = 1,ack = w+1,seq = u+1
客戶端必須等 2MSL 時間才能徹底關閉連接。
Part11揮手為什么需要四次?兩次不行嗎?
因為 TCP 是全雙工通信,雙方都能發數據
所以不能像三次握手那樣“合并發送”。
舉個栗子🌰:
當服務端收到客戶端的 FIN 時
它可能還有數據沒發完,不能立馬斷開連接
只能先回一個 ACK 表示“我收到了”
等自己數據發完了,再發一個 FIN 表示“我也好了”。
所以中間這個緩沖期,就導致了四次揮手。
如果強行合并成兩次,那就可能出現一邊關了另一邊還在傻等的情況
這就叫“半死不活”的連接,系統資源浪費嚴重!
Part12TIME_WAIT狀態為什么要等2MSL?
這個“2MSL”聽起來很神秘,其實它就是:
Maximum Segment Lifetime 的兩倍時間,也就是報文段在網絡中能存活的最長時間。
比如 MSL 是 60 秒,那 2MSL 就是 120 秒。
為啥要等這么久?
兩個關鍵原因:
? 1. 確保最后一個 ACK 能到達服務端
萬一客戶端發的這個 ACK 丟了怎么辦?
服務端會因為沒收到 ACK 而重新發 FIN
這時候客戶端還沒徹底關閉,就能重新響應 ACK
避免服務端一直卡在?LAST_ACK?狀態
如果不等 2MSL,客戶端一發完就跑路
服務端收不到確認,就會無限重傳,直到超時
? 2. 防止舊連接的報文干擾新連接
假設客戶端不等 2MSL,直接關閉連接
而某個老報文在路上繞了一圈又回來了
這時候剛好有個新連接使用了同樣的五元組(IP + 端口)
那這個老報文就可能被當成新連接的數據處理,造成混亂。
等 2MSL 的目的,就是讓網絡上所有屬于舊連接的報文都“壽終正寢”
這樣新連接就不會誤判這些“幽靈報文”
為幫助理解 TCP 連接建立(三次握手)與終止(四次揮手)的狀態變化,特提供一張典型的狀態變遷圖(見下圖)。其中,粗實線箭頭指示客戶端的標準狀態變遷,粗虛線箭頭指示服務器端的標準狀態變遷。
Part13TIME_WAIT 和 CLOSE_WAIT
TCP 中的兩個“尷尬狀態”:TIME_WAIT 和 CLOSE_WAIT
這兩個狀態可以說是 TCP 通信中的“分手后遺癥”
一個是因為分手太慢被卡住(TIME_WAIT)
另一個是因為對方遲遲不分手而僵持(CLOSE_WAIT)
下面我來給你講清楚它們到底啥意思、為啥會出問題、怎么解決!
🕒 TIME_WAIT 狀態 —— 主動分手者的等待期
“我說拜拜了,你確定收到沒?再等一會兒我才徹底走。”
? 它是誰觸發的?
由主動關閉連接的一方觸發。比如客戶端調用了?close()?或?shutdown(),它就進入了“分手流程”。
🤔 它為什么存在?
主要是為了兩個目的:
- 確保最后一個 ACK 能到達服務端
如果這個 ACK 丟了,服務端就會重傳 FIN,客戶端還能響應。 - 防止舊連接的報文干擾新連接
報文在網絡里最多活 MSL 時間,等 2MSL 就能保證這些“幽靈包”都消失了。
MSL 是 Maximum Segment Lifetime,即報文段的最大生存時間,一般是 30s~60s。
所以 TIME_WAIT 的持續時間就是 2 × MSL,大約 60s 到 120s 不等。
?? 它的問題是什么?
- 占用本地端口號資源;
- 大量 TIME_WAIT 可能導致端口耗盡;
- 對高并發短連接服務器影響較大(如 Web 服務器);
🔧 怎么解決大量 TIME_WAIT?
可以用?setsockopt?設置套接字選項,允許地址和端口復用:
int?opt =?1;
setsockopt(listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt,?sizeof(opt));
這樣就能避免因為 TIME_WAIT 導致的端口占用問題啦!
🐌 CLOSE_WAIT 狀態 —— 被動分手者的拖延癥
“你說要走了,我也收到了,但我還沒關呢,你在等我……我在等你……”
? 它是誰觸發的?
由被動關閉連接的一方觸發。比如客戶端先發 FIN,服務端回 ACK 后進入 CLOSE_WAIT。
🤔 它為什么存在?
表示服務端已經知道客戶端要關閉連接了,但它還在等應用程序去處理剩下的數據,并手動關閉 socket。
?? 它的問題是什么?
如果程序忘了調用?close()?去關閉 socket,
那這個連接就會一直卡在 CLOSE_WAIT,
最終導致連接數爆炸、資源泄漏、服務崩潰……
💡 常見原因:
- 忘記關閉 socket;
- 沒有對 read/write 返回值做正確判斷;
- 多線程/異步編程中未及時釋放資源;
🔧 怎么排查和解決?
可以使用如下命令查看當前系統的 CLOSE_WAIT 數量:
netstat?-antp | grep CLOSE_WAIT
一旦發現很多 CLOSE_WAIT,就要檢查代碼有沒有漏掉?close()?或者異常退出沒有清理資源。
總結
TCP 的連接和斷開,看似只是幾個來回的數據包,但背后卻藏著網絡通信中最核心的設計理念:可靠傳輸、資源管理、防止混亂。
從“三次握手”到“四次揮手”,再到“TIME_WAIT”和“CLOSE_WAIT”,這些狀態不是為了難為我們,而是為了讓整個互聯網更穩定、更安全地運行。
作為開發者,理解它們不僅有助于應對面試官的靈魂拷問,更重要的是能在實際工作中排查問題、優化系統、寫出更健壯的網絡程序!
📌?如果你是初學者:建議動手抓個包看看三次握手和四次揮手的實際過程(Wireshark 走起);
📌?如果你是進階者:不妨研究下 TCP 的保活機制、端口復用、以及高并發下的連接管理;
📌?如果你是面試黨:記住一句話——能畫圖、能舉例、能說出坑在哪,才是真掌握!
📢面試中TCP高頻面試題部分列舉
- 請畫出三次握手和四次揮手的示意圖
- 為什么連接的時候是三次握手?
- 什么是半連接隊列?
- ISN(Initial Sequence Number)是固定的嗎?
- 三次握手過程中可以攜帶數據嗎?
- 如果第三次握手丟失了,客戶端服務端會如何處理?
- SYN攻擊是什么?
- 揮手為什么需要四次?
- 四次揮手釋放連接時,等待2MSL的意義?
- TCP和UDP的主要區別是什么?
- TCP是如何保證可靠傳輸的?(關鍵機制)
- TCP的滑動窗口機制是什么?它解決了什么問題?(流量控制)
- TCP的擁塞控制算法主要有哪些階段?描述其基本原理。(慢啟動、擁塞避免、快重傳、快恢復)
- 什么是TCP的粘包和拆包問題?為什么會出現?如何解決?
- TCP的Keepalive機制是什么?它的作用是什么?有什么缺點?與應用層心跳有何區別?
- 描述TCP的狀態機(重點描述連接建立和關閉過程中的狀態變遷)。
- 為什么要有TIME_WAIT狀態?過多的TIME_WAIT狀態有什么影響?如何優化?
- TCP頭部結構包含哪些關鍵字段?(至少說出6-8個并說明作用)
- TCP的最大報文段長度(MSS)是什么?它是如何確定的?與MTU有什么關系?
- 什么是Nagle算法?它的目的是什么?在什么場景下可能需要關閉它?
- 什么是延遲確認(Delayed ACK)?它的目的是什么?
- TCP真的100%可靠嗎?(可靠性的邊界)
- MTU和MSS有什么區別?
- TCP如何檢測和處理丟包?(超時重傳 vs 快速重傳)
- TCP連接建立后,如果通信雙方一直不發送數據,連接會一直保持嗎?(涉及Keepalive和中間設備超時)
往期推薦
【大廠標準】Linux C/C++ 后端進階學習路線
C/C++ 高頻八股文面試題1000題(一)
C/C++ 高頻八股文面試題1000題(二)
點擊下方關注【Linux教程】,獲取編程學習路線、項目教程、簡歷模板、大廠面試題、大廠面經、編程交流圈子等等。