TCP(傳輸控制協議)是互聯網最重要的協議之一,它保證了數據的可靠、有序傳輸。連接建立時的“三次握手”和連接關閉時的“四次揮手”是其核心機制,涉及特定的報文交換和狀態變化。
一、TCP 三次握手(Three-Way Handshake) - 建立連接
目的:同步雙方的初始序列號(Sequence Number,簡稱 Seq),確認對方能夠正常收發數據,建立雙向連接。
狀態變化詳細過程:
- CLOSED -> LISTEN(服務器端):
- 服務器啟動,調用
listen()
進入LISTEN
狀態,準備接受連接請求。
- 服務器啟動,調用
- SYN_SENT(客戶端):
- 客戶端調用
connect()
主動發起連接請求。 - 客戶端發送一個
SYN
報文:SYN
標志位設置為1
。- 隨機生成一個初始序列號
client_seq = x
。 - 無確認號(因為還沒收到對方的任何序列號)。
- 客戶端進入
SYN_SENT
狀態(表示已發送 SYN,等待對方的 SYN+ACK)。
- 客戶端調用
- LISTEN -> SYN_RCVD(服務器端):
- 服務器處于
LISTEN
狀態,接收到客戶端發來的SYN
報文。 - 服務器決定接受連接,并發送一個
SYN + ACK
報文:SYN
標志位設置為1
。ACK
標志位設置為1
。- 隨機生成一個初始序列號
server_seq = y
。 - 將確認號
ack = x + 1
(因為客戶端的 SYN 占用了序列號 x,所以服務器期望收到客戶端發來的下一個數據字節的序列號是 x+1)。
- 服務器進入
SYN_RCVD
狀態(表示已發送 SYN+ACK,等待客戶的 ACK)。
- 服務器處于
- SYN_SENT -> ESTABLISHED(客戶端):
- 客戶端處于
SYN_SENT
狀態,收到服務器發來的SYN + ACK
報文。 - 客戶端發送一個
ACK
報文:ACK
標志位設置為1
。- 序列號
seq = x + 1
(因為客戶端的 SYN 報文占用了序列號 x,所以下一個報文的序列號是 x+1)。 - 確認號
ack = y + 1
(因為服務器的 SYN 報文占用了序列號 y,所以客戶端期望收到服務器發來的下一個數據字節的序列號是 y+1)。
- 客戶端進入
ESTABLISHED
狀態(表示連接已建立)。
- 客戶端處于
- SYN_RCVD -> ESTABLISHED(服務器端):
- 服務器處于
SYN_RCVD
狀態,收到客戶端發來的ACK
報文。 - 服務器驗證該 ACK 報文的確認號
ack
是否等于y + 1
。 - 驗證通過后,服務器也進入
ESTABLISHED
狀態(表示連接已建立)。
- 服務器處于
至此,雙向通信通道建立完成。
二、TCP 四次揮手(Four-Way Handshake) - 關閉連接
目的:雙方都確認對方不再發送數據,安全地終止雙向連接。
狀態變化詳細過程:
- ESTABLISHED -> FIN_WAIT_1(主動關閉方 - 通常是客戶端):
- 主動關閉方(A)調用
close()
或進程退出。 - A 發送一個
FIN
報文:FIN
標志位設置為1
。- 序列號
seq = u
(等于 A 要發送的下一個數據的序列號,但 FIN 消耗一個序列號)。
- A 進入
FIN_WAIT_1
狀態(表示已發送 FIN,等待對方的 ACK 或 對方的 FIN)。
- 主動關閉方(A)調用
- ESTABLISHED -> CLOSE_WAIT(被動關閉方):
- 被動關閉方(B)處于
ESTABLISHED
狀態,收到 A 發來的FIN
報文。 - B 知道自己該方向的數據發送通道將被關閉(應用程序會收到 EOF)。
- B 發送一個
ACK
報文:ACK
標志位設置為1
。- 序列號
seq = v
(B 自己的下一個序列號)。 - 確認號
ack = u + 1
(表示收到了 A 的 FIN 報文)。
- B 進入
CLOSE_WAIT
狀態(表示收到對方的 FIN,自己的關閉過程開始,等待自己的應用程序通知關閉)。此時:A 到 B 的發送通道已關閉(A不再發送數據),但 B 到 A 的發送通道可能還有數據需要發送。
- 被動關閉方(B)處于
- FIN_WAIT_1 -> FIN_WAIT_2(主動關閉方):
- A 處于
FIN_WAIT_1
狀態,收到 B 發來的ACK
報文。 - A 驗證該 ACK 報文的確認號
ack
是否等于u + 1
。 - 驗證通過后,A 進入
FIN_WAIT_2
狀態(表示已收到對方對 FIN 的 ACK,等待對方發送 FIN)。
- A 處于
- CLOSE_WAIT -> LAST_ACK(被動關閉方):
- B 處于
CLOSE_WAIT
狀態,完成了自己方向的數據發送(應用程序調用close()
)。 - B 發送一個
FIN
報文:FIN
標志位設置為1
。- 序列號
seq = w
(這個序列號可能等于v
(如果CLOSE_WAIT
期間 B 沒有發數據)或大于v
(如果 B 發過數據))。 - 確認號
ack
仍然是u + 1
(因為在這個方向上 A 沒有新數據)。
- B 進入
LAST_ACK
狀態(表示自己已發送 FIN,等待對方最后的 ACK)。
- B 處于
- FIN_WAIT_2 -> TIME_WAIT(主動關閉方):
- A 處于
FIN_WAIT_2
狀態,收到 B 發來的FIN
報文。 - A 發送一個
ACK
報文:ACK
標志位設置為1
。- 序列號
seq = u + 1
(之前的 FIN 報文序列號是 u)。 - 確認號
ack = w + 1
(確認 B 的 FIN 報文)。
- A 進入
TIME_WAIT
狀態(也稱為2MSL Wait
狀態)。此狀態會持續一段時間(通常為2 * Maximum Segment Lifetime (MSL)
,默認在 Linux 中是 60 秒)。
- A 處于
- LAST_ACK -> CLOSED(被動關閉方):
- B 處于
LAST_ACK
狀態,收到 A 發來的ACK
報文。 - B 驗證該 ACK 報文的確認號
ack
是否等于w + 1
。 - 驗證通過后,B 關閉連接,進入
CLOSED
狀態。
- B 處于
- TIME_WAIT -> CLOSED(主動關閉方):
- A 在
TIME_WAIT
狀態等待2MSL
時間。 - 目的:
- 確保最后一個 ACK 報文送達 B: 如果 B 沒有收到最后一個 ACK(它在 LAST_ACK 狀態等待),會在超時后重發 FIN。處于 TIME_WAIT 的 A 收到這個重發的 FIN,會再次發送 ACK。
- 確保網絡中所有舊的報文段消散: 防止具有相同四元組(源IP、源端口、目的IP、目的端口)的下一個新連接,錯誤地接收和處理上一個舊連接的殘留報文。
2MSL
時間到,A 關閉連接,進入CLOSED
狀態。
- A 在
至此,連接完全終止。
核心問題解答
為什么需要“三次”握手?兩次握手不行嗎?
不行。 原因主要有:
- 避免失效連接請求造成的資源浪費和歷史連接問題:
- 場景: 假設客戶端發送了第一個
SYN=1
請求(X),但因網絡擁塞嚴重延遲。客戶端超時未收到響應,會重發第二個SYN=1
請求(Y)。Y 成功到達,服務器響應SYN+ACK
,客戶端也響應ACK
,完成三次握手建立連接。通信結束,連接關閉。 - 問題: 現在那個延遲了的初始 SYN 請求 X 終于到達了服務器。如果只有兩次握手(服務器收到 SYN -> 響應 SYN+ACK -> 建立連接),服務器會誤以為這是一個新的連接請求(歷史連接),并立即為其分配資源(建立連接表項、分配緩沖區等)。
- 后果: 服務器資源被無意義占用(因為客戶端此時根本不知道還有這個連接存在,也不會發送數據)。此外,如果這個“幽靈連接”被分配了新的序列號,也可能干擾后續真正的新連接(相同端口復用)。
- **解決:**三次握手中的第三次握手(ACK)就是客戶端的明確確認信號。 當那個延遲的 SYN 包 X 到達服務器時,服務器會發送 SYN+ACK,但客戶端已經關閉了最初的連接意圖,它會忽略這個 SYN+ACK 或者發送 RST 拒絕。這樣服務器就不會為這個無效請求建立連接。
- 場景: 假設客戶端發送了第一個
- 初始序列號同步的雙向確認:
- 通信雙方都需要知道對方的初始序列號。兩次握手只能確保客戶端知道服務器的初始序列號(在服務器的 SYN+ACK 中),并確認服務器的發送能力。但服務器在第二次握手時發送的 SYN+ACK,并未得到客戶端的確認(僅兩次握手的話,服務器在第二步就認為連接建立了)。
- 問題: 服務器不知道客戶端是否成功收到了自己的序列號
y
。如果這個 SYN+ACK 丟失了,服務器認為自己建立了連接(已發送SYN+ACK -> 等待ACK),但客戶端還在等待 SYN+ACK(它沒收到),雙方狀態不一致。 - **解決:**第三次握手的
**ACK**
報文明確告訴服務器:“我收到了你的初始序列號**y**
,我期望的下一個序列號是**y+1**
”。 這完成了服務器序列號的同步確認。
總結: 三次握手是保證可靠性、防止建立錯誤連接、且完成序列號雙向同步確認的最小開銷方案。
為什么需要“四次”揮手?
主要原因是:TCP 連接是全雙工(Full-Duplex)的。
- 雙向獨立的關閉通道:
- 當客戶端發送
FIN
時,表示它數據發送完畢(不再往服務器寫數據),但服務器到客戶端方向的數據發送通道并未立即關閉。 - 服務器收到
FIN
后,知道自己該方向的數據接收通道已關閉(收到EOF),但需要檢查自己的應用程序是否還有數據要發送給客戶端。如果還有數據要發送(比如最后要確認客戶端請求操作成功的消息),服務器會在 CLOSE_WAIT 狀態繼續發送這些數據。 - 只有等待服務器自己也確定所有數據都發送完畢(應用層調用
close()
)時,它才會發送自己的FIN
來關閉“服務器->客戶端”方向的發送通道。 - 因此,關閉連接需要四個步驟:
- A -> B 發送 FIN:關閉 A 的發送通道。
- B -> A 發送 ACK:確認收到 A 的 FIN。此時 B 的接收通道關閉(知道 A 不再發數據)。
- B -> A 發送 FIN:關閉 B 的發送通道。(這條 FIN 的發送時間獨立于前面的 ACK)
- A -> B 發送 ACK:確認收到 B 的 FIN。
- 當客戶端發送
- FIN 和 ACK 可能無法合并:
- 當 B 收到 A 的 FIN 時,它必須立即響應一個 ACK(這是協議要求)。但此時 B 的應用程序可能還未決定關閉/發送完自己的數據(處于 CLOSE_WAIT 狀態),因此 B 的 FIN 報文無法像第二次握手(SYN+ACK)那樣與此時的 ACK 合并發送。ACK 必須在收到 FIN 后立即發送,而 FIN 必須等 B 準備好關閉其發送端時才發送。
總結: 四次揮手是 TCP 全雙工特性決定的。發送 FIN 意味著“我這邊不準備再發送數據了”。兩次揮手只能關閉一個方向(A->B),四次揮手才能徹底關閉兩個獨立的數據發送通道(A->B 和 B->A),確保數據完整性。