目錄
- TCP 是什么:面向連接 + 可靠 + 字節流
- 三次握手:為什么不是兩次
- 四次揮手與 TIME_WAIT:誰等誰
- 序列號/確認號與去重、排序、確認
- 重傳機制:超時重傳與快速重傳
- 滑動窗口與流量控制
- 擁塞控制:慢啟動/擁塞避免/快重傳/快恢復
- 保活機制與長連接
- 半連接隊列、全連接隊列與 SYN 攻擊
- RST 的語義:什么時候會被動斷開
- 常見高頻面試問答(含易錯點)
- 抓包與排錯清單
- 參考與延伸閱讀
TCP 是什么:面向連接 + 可靠 + 字節流
- 面向連接:通信前需要建立連接(三次握手)。
- 可靠交付:通過序列號、確認號、校驗和、重傳、窗口等保證不丟、不重、按序到達(應用層“看起來”按序)。
- 字節流:沒有消息邊界,數據是連續字節流,分段與拼包在傳輸層完成。
簡圖(字節流與按序):
[App 寫入字節] => [TCP 分段 seq=100..] => [網絡] => [TCP 重組、去重、排序] => [App 讀到連續字節]
三次握手:為什么不是兩次
三次握手的目標:
- 交換初始序列號(ISN)并建立雙向通信能力。
- 讓客戶端和服務端都“確認”對方的收發能力都正常。
流程(簡化):
CLOSE -> SYN-SENT --SYN(x)--> LISTEN
SYN-RCVD <--SYN(y),ACK(x+1)-- LISTEN
ESTABLISHED --ACK(y+1)--> ESTABLISHED
為什么不能兩次?
- 若兩次握手,服務端無法判斷當前請求是否“歷史連接”重放;第三次 ACK 讓客戶端基于上下文確認“我確實與這個服務端建立了當前連接”,避免“僵尸連接”占用資源。
我常用的比喻:
- 第一次:我能發(SYN)。
- 第二次:我能收能發(SYN+ACK)。
- 第三次:我也能收(ACK),雙方都齊活。
注:半連接隊列記錄“收到 SYN 尚未完成握手”的請求;若第三次 ACK 不到,條目會因超時被清理。
四次揮手與 TIME_WAIT:誰等誰
為什么“揮手”通常是四次?
- 關閉是“單向”的:一方
FIN
只表示“我不再發了”,對方讀到FIN
但仍可繼續發送,故需要兩對報文確保雙方各自完成“發送通道”的關閉。
典型序列:
(主動關閉) FIN -> ACK (被動方進 CLOSE_WAIT)...對方數據發送完...FIN -> ACK (主動方進 TIME_WAIT)
TIME_WAIT 的意義:
- 等 2MSL,確保:
- 最后的 ACK 若丟失,對方重發 FIN,我還能重發 ACK;
- 舊連接的遲到報文不會影響未來新的同四元組連接。
調優提示:
- 服務端側可讓“短連接風格”的一方盡量成為被動關閉者,降低其 TIME_WAIT 壓力(視業務/棧實現)。
序列號/確認號與去重、排序、確認
我面試常用“翻書”比喻:
能力 | 解決的問題 | 類比 |
---|---|---|
序列號(seq) | 去重與排序 | 頁碼防重排 |
確認號(ack) | 成功到達的確認 | 勾選“已讀” |
窗口(win) | 節流與并行度 | 讀寫節拍 |
發送端維護已發送未確認的數據集合,接收端通過累計 ACK 告知“我已經連續收到哪一位點前的所有字節”。
重傳機制:超時重傳與快速重傳
- 超時重傳(RTO):發送后啟動定時器,超時無 ACK 則重發;RTO 自適應基于 RTT 與抖動(通常 > RTT 的一定倍數)。
- 快速重傳:收到 3 個重復 ACK(如
ACK=101,101,101
),判定某段可能丟了,提前重傳,減少等待 RTO 的成本。
要點:
- 重傳報文的
seq
與原始相同,可能合并為更大段(取決于實現)。 - 局部亂序也會觸發重復 ACK,但不等同于丟包;擁塞控制會進一步介入(見下)。
滑動窗口與流量控制
- 接收窗口(rwnd):接收端緩沖可用空間,通過報文通告給發送端,防止“接收方處理不過來”。
- 發送窗口(swnd):發送端根據
min(cwnd, rwnd)
決定實際并發在途數據量。 - 零窗口探測:若
rwnd=0
,發送端定期發探測報文,等待窗口開放。
可視化(簡略):
|--- 已確認 ---|--- 已發送未確 ---|--- 可發送窗口 ---|^ base ^ nextSeq
擁塞控制:慢啟動/擁塞避免/快重傳/快恢復
- cwnd:擁塞窗口,代表“網絡可能承受的并發在途量”的猜測值。
- 慢啟動:從 1 MSS 開始指數增長,閾值
ssthresh
之前翻倍,之后線性增加(擁塞避免)。 - 快重傳/快恢復:
- 3 次重復 ACK 觸發:
ssthresh = cwnd/2
,cwnd = ssthresh
或ssthresh + 3*MSS
(依實現),進入快速恢復,避免回到 1 MSS 的冷啟動。
- 3 次重復 ACK 觸發:
- 超時:說明更嚴重擁塞,通常將
cwnd
置 1 MSS,重新慢啟動。
保活機制與長連接
- KeepAlive:長時間無數據時周期性發送探測包;若多次無響應,判定連接死亡并關閉。默認關/開與探測周期依 OS 而異,可配置。
- 應用層心跳:例如 HTTP/2、WebSocket 自帶 ping/pong,更靈活。
半連接隊列、全連接隊列與 SYN 攻擊
- 半連接隊列(SYN 隊列):收到
SYN
,發送SYN+ACK
,等待第三次握手的ACK
。若超時未到達則過期剔除。 - 全連接隊列(Accept 隊列):三次握手完成的連接,等待應用層
accept()
取走。 - 防護思路:
- SYN Cookies、縮短 SYN/ACK 重傳與過期時間、增大隊列。
- 使用負載均衡/防火墻清洗異常 SYN 洪泛。
RST 的語義:什么時候會被動斷開
- RST 表示“連接非法/不存在或異常狀態”,常見觸發:
- 目標端口無進程監聽;
- 應用層提前關閉 socket,仍收到對端數據;
- 抓包/半開異常導致狀態機不同步。
- 面試提示:用 RST 終止連接不會進入 TIME_WAIT(與 FIN 流程不同)。
常見高頻面試問答(含易錯點)
- 為什么是三次握手不是兩次?
- 需要確認“雙方收發能力”,并防歷史連接的重放,占用資源。
- 為什么揮手要四次?
- 關閉是單向的,兩個方向分別 FIN/ACK。
- TIME_WAIT 為什么在主動關閉方?
- 負責兜住最后 ACK 丟失與舊報文消散。
- 3 個重復 ACK 一定是丟包嗎?
- 不一定,可能亂序;但為降低時延會觸發快速重傳并調整擁塞窗口。
rwnd
與cwnd
誰說了算?
- 實際可發窗口取兩者較小值:
min(cwnd, rwnd)
。
- 半連接隊列爆了怎么辦?
- 開啟 SYN Cookies、調大隊列、縮短重試、前置抗 DDoS。
- 為什么 TCP 是字節流,UDP 是報文?
- TCP 為可靠按序的連續字節,UDP 不保證順序、丟失可見,保留消息邊界。
抓包與排錯清單
- 觀察三次握手:過濾
tcp.flags.syn==1 && tcp.flags.ack==0
。 - 快速重傳:看是否出現 3 次重復 ACK 與
Dup ACK
標記。 - RTO 觸發:同一
seq
的報文重發,時間間隔接近 RTO。 - 零窗口:
Window Size Value = 0
與周期性探測包。 - 擁塞事件:
[TCP Previous segment not captured]
、[Retransmission]
、窗口驟降。
Wireshark 過濾示例:
# 僅看 TCP 握手
tcp.flags.syn==1 || tcp.flags.fin==1 || tcp.flags.reset==1
# 指定四元組
ip.addr==A && ip.addr==B && tcp.port in {PORT1,PORT2}
參考與延伸閱讀
- RFC 793、RFC 5681:TCP 規范與擁塞控制
- 《Linux高性能服務器編程》《TCP/IP 詳解 卷一》
- Wireshark 官方文檔與實踐