一、概述
1.1 背景介紹
TCP 三次握手和四次揮手,大概是網絡領域被問爛了的面試題。但真正能把狀態變遷、序列號變化、抓包細節講清楚的人并不多。很多人背了八股文,一到生產環境看 Wireshark 抓包就懵了——SYN_RECV 隊列溢出怎么排查?TIME_WAIT 堆積幾萬個怎么處理?RST 到底是誰發的?這些問題光靠背書解決不了。
這篇文章從 TCP 報文格式講起,逐字節拆解三次握手和四次揮手的完整過程,配合 Wireshark 和 tcpdump 的實際抓包輸出,把每個狀態變遷都對應到真實的網絡包上。同時覆蓋 TCP 重傳機制、擁塞控制算法對比(BBR vs Cubic),以及生產環境中最常見的 TCP 問題排查和內核參數調優。
1.2 技術特點
報文級拆解:從 TCP Header 的每個字段出發,理解握手揮手的本質而非死記硬背
抓包驅動:所有理論都配合 Wireshark/tcpdump 的真實抓包輸出驗證
狀態機完整:覆蓋 TCP 全部 11 種狀態及其轉換條件
生產導向:重點放在 TIME_WAIT 優化、半連接隊列溢出、RST 排查等實際問題
1.3 適用場景
場景一:面試準備,需要深入理解 TCP 連接管理機制而非停留在八股文層面
場景二:生產環境 TCP 連接異常排查,需要通過抓包定位具體問題
場景三:高并發服務的內核網絡參數調優,解決 TIME_WAIT 堆積、SYN Flood 等問題
場景四:微服務架構下的連接管理優化,理解連接復用和優雅關閉
1.4 環境要求
| 組件 | 版本要求 | 說明 |
|---|---|---|
| 操作系統 | Ubuntu 24.04 LTS / RHEL 9.x | 內核 6.8+,BBR v3 支持 |
| Wireshark | 4.4+ | GUI 抓包分析工具 |
| tcpdump | 4.99+ | 命令行抓包工具 |
| ss / iproute2 | 6.x+ | 替代 netstat 的連接狀態查看工具 |
| curl | 8.x+ | HTTP 請求測試 |
二、TCP 報文格式
在聊握手揮手之前,先把 TCP 報文頭的結構搞清楚。不理解報文格式,后面看抓包就是看天書。
2.1 TCP Header 結構
TCP 報文頭最小 20 字節,最大 60 字節(含 Options)。結構如下:
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Port | Destination Port | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Acknowledgment Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Data | |C|E|U|A|P|R|S|F| | | Offset| Rsrvd |W|C|R|C|S|S|Y|I| Window | | | |R|E|G|K|H|T|N|N| | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Checksum | Urgent Pointer | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Options (variable) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
2.2 關鍵字段解析
重點關注幾個和握手揮手直接相關的字段:
序列號(Sequence Number,32 位):標識從 TCP 發送端向接收端發送的數據字節流中的第一個字節的編號。握手階段的初始序列號(ISN)是隨機生成的,不是從 0 開始,這是為了防止歷史連接的殘留包干擾新連接。
確認號(Acknowledgment Number,32 位):期望收到對方下一個報文段的第一個數據字節的序號。只有 ACK 標志位為 1 時這個字段才有效。
標志位(Flags):這 6 個標志位是握手揮手的核心:
| 標志位 | 含義 | 握手/揮手中的作用 |
|---|---|---|
| SYN | 同步序列號 | 發起連接,攜帶初始序列號 |
| ACK | 確認 | 確認收到對方的數據 |
| FIN | 結束 | 請求關閉連接 |
| RST | 重置 | 強制中斷連接 |
| PSH | 推送 | 要求接收方立即將數據交給應用層 |
| URG | 緊急 | 緊急指針有效 |
窗口大小(Window,16 位):接收方告訴發送方自己還能接收多少字節的數據。配合 Window Scale 選項可以擴展到 30 位。
2.3 TCP Options
握手階段的 SYN 包通常攜帶以下 Options:
MSS (Maximum Segment Size) : 通告對方自己能接收的最大報文段長度,通常 1460(以太網 MTU 1500 - IP頭20 - TCP頭20) Window Scale : 窗口縮放因子,允許窗口大小超過 65535 SACK Permitted : 告知對方支持選擇性確認 Timestamps : 用于 RTT 計算和 PAWS(防止序列號回繞)
三、三次握手詳細過程
3.1 握手全景
三次握手的本質是:雙方交換初始序列號(ISN),并確認對方的接收能力。
客戶端 服務端 | | | [CLOSED] [LISTEN] | | | |----------- SYN, Seq=x ----------------------->| | [SYN_SENT] | | [SYN_RCVD] | |<---------- SYN+ACK, Seq=y, Ack=x+1 ----------| ? ? ? | ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? |----------- ACK, Seq=x+1, Ack=y+1 ----------->| | [ESTABLISHED] [ESTABLISHED] | | |
3.2 逐包拆解
第一次握手:客戶端發送 SYN
客戶端調用connect()系統調用,內核構造一個 SYN 報文發出去:
標志位:SYN=1
序列號:Seq=x(隨機生成的 ISN)
確認號:Ack=0(此時還沒有需要確認的數據)
Options:MSS=1460, WS=7, SACK_PERM, TSval=xxx
客戶端狀態從 CLOSED 變為SYN_SENT。
Wireshark 中看到的樣子:
No. Time Source Destination Protocol Info 1 0.000000 192.168.1.10 10.0.0.1 TCP 54321 > 443 [SYN] Seq=0 Win=65535 Len=0 MSS=1460 WS=128 SACK_PERM TSval=1234567
注意 Wireshark 默認顯示的是相對序列號(從 0 開始),實際的 ISN 是一個 32 位隨機數。可以在 Edit -> Preferences -> Protocols -> TCP 中關閉 "Relative sequence numbers" 查看真實值。
第二次握手:服務端回復 SYN+ACK
服務端收到 SYN 后,從半連接隊列(SYN Queue)中分配一個條目,構造 SYN+ACK 報文:
標志位:SYN=1, ACK=1
序列號:Seq=y(服務端自己的 ISN)
確認號:Ack=x+1(確認收到客戶端的 SYN,期望下一個字節是 x+1)
Options:MSS=1460, WS=7, SACK_PERM, TSval=yyy
服務端狀態從 LISTEN 變為SYN_RCVD。
No. Time Source Destination Protocol Info 2 0.000543 10.0.0.1 192.168.1.10 TCP 443 > 54321 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=1460 WS=128 SACK_PERM
第三次握手:客戶端發送 ACK
客戶端收到 SYN+ACK 后,connect()返回成功,同時發送最后一個 ACK:
標志位:ACK=1
序列號:Seq=x+1
確認號:Ack=y+1(確認收到服務端的 SYN)
客戶端狀態變為ESTABLISHED。服務端收到這個 ACK 后,從半連接隊列移到全連接隊列(Accept Queue),狀態也變為ESTABLISHED。
No. Time Source Destination Protocol Info 3 0.000012 192.168.1.10 10.0.0.1 TCP 54321 > 443 [ACK] Seq=1 Ack=1 Win=65536 Len=0
這個 ACK 包是可以攜帶數據的(TCP Fast Open 就利用了這一點),但通常情況下 Len=0。
3.3 為什么是三次而不是兩次
這是面試高頻問題,標準答案有兩個層面:
層面一:防止歷史連接的初始化
假設只有兩次握手,客戶端發了一個 SYN 包因為網絡延遲滯留在網絡中,客戶端超時后重新發起連接并完成通信。之后那個滯留的 SYN 包到達服務端,服務端以為是新連接請求,直接進入 ESTABLISHED 狀態分配資源——但客戶端根本不知道這個連接的存在。三次握手中,服務端回復 SYN+ACK 后必須等客戶端的 ACK 確認,客戶端收到一個不認識的 SYN+ACK 會回復 RST,避免了這個問題。
層面二:雙方都需要確認對方的收發能力
建立可靠連接需要確認四件事:客戶端的發送能力、客戶端的接收能力、服務端的發送能力、服務端的接收能力。三次握手剛好完成這四個確認:
第一次握手(SYN) :服務端確認 -> 客戶端的發送能力 OK 第二次握手(SYN+ACK) :客戶端確認 -> 服務端的接收能力 OK + 服務端的發送能力 OK 第三次握手(ACK) :服務端確認 -> 客戶端的接收能力 OK
兩次握手少了最后一步,服務端無法確認客戶端的接收能力。
3.4 半連接隊列與全連接隊列
這兩個隊列是理解 SYN Flood 攻擊和連接建立失敗的關鍵:
半連接隊列(SYN Queue):存放收到 SYN 但還沒收到第三次 ACK 的連接,狀態為 SYN_RCVD。隊列長度由tcp_max_syn_backlog控制。
全連接隊列(Accept Queue):存放已完成三次握手但還沒被accept()取走的連接,狀態為 ESTABLISHED。隊列長度由min(backlog, somaxconn)決定。
# 查看半連接隊列溢出次數 netstat -s | grep"SYNs to LISTEN" # 查看全連接隊列溢出次數 netstat -s | grep"overflowed" # 用 ss 查看當前隊列狀態 # Recv-Q: 當前全連接隊列中的連接數 # Send-Q: 全連接隊列的最大長度 ss -lnt | grep :443
隊列溢出時的表現:半連接隊列滿了,新的 SYN 包會被直接丟棄(客戶端表現為連接超時);全連接隊列滿了,服務端會根據tcp_abort_on_overflow參數決定是丟棄 ACK 還是發送 RST。
四、四次揮手詳細過程
4.1 揮手全景
TCP 連接的關閉需要四次揮手,因為 TCP 是全雙工的——每個方向的關閉需要獨立進行。主動關閉方發 FIN 只是說"我不再發數據了",但還可以接收數據,對方可能還有數據沒發完。
主動關閉方 被動關閉方 | | | [ESTABLISHED] [ESTABLISHED] | | | |----------- FIN, Seq=u ----------------------->| | [FIN_WAIT_1] | | [CLOSE_WAIT] | |<---------- ACK, Seq=v, Ack=u+1 --------------| ? ? ? | ?[FIN_WAIT_2] ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? | ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? | ? ? ? ? ? ? ?(被動方繼續發送剩余數據...) ? ? ? ? | ? ? ? | ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? |<---------- FIN, Seq=w, Ack=u+1 --------------| ? ? ? | ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?[LAST_ACK] ? ? | ? ? ? | ?[TIME_WAIT] ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?| ? ? ? |----------- ACK, Seq=u+1, Ack=w+1 ----------->| | [CLOSED] | | (等待 2MSL) | | [CLOSED] |
4.2 逐包拆解
第一次揮手:主動關閉方發送 FIN
應用層調用close()或shutdown(SHUT_WR),內核發送 FIN 報文:
標志位:FIN=1, ACK=1
序列號:Seq=u(當前發送序列號)
確認號:Ack=v(確認對方最后收到的數據)
主動關閉方狀態從 ESTABLISHED 變為FIN_WAIT_1。
No. Time Source Destination Protocol Info 10 5.234100 192.168.1.10 10.0.0.1 TCP 54321 > 443 [FIN, ACK] Seq=500 Ack=800 Win=65536 Len=0
第二次揮手:被動關閉方回復 ACK
被動方內核收到 FIN 后自動回復 ACK,應用層通過read()返回 0 感知到對方關閉:
標志位:ACK=1
確認號:Ack=u+1
被動關閉方狀態變為CLOSE_WAIT,主動關閉方收到后變為FIN_WAIT_2。
No. Time Source Destination Protocol Info 11 5.234650 10.0.0.1 192.168.1.10 TCP 443 > 54321 [ACK] Seq=800 Ack=501 Win=65536 Len=0
第三次揮手:被動關閉方發送 FIN
被動方處理完剩余數據后調用close(),內核發送 FIN:
標志位:FIN=1, ACK=1
序列號:Seq=w(可能在第二次和第三次揮手之間還發送了數據)
被動關閉方狀態變為LAST_ACK。
No. Time Source Destination Protocol Info 15 5.340200 10.0.0.1 192.168.1.10 TCP 443 > 54321 [FIN, ACK] Seq=900 Ack=501 Win=65536 Len=0
第四次揮手:主動關閉方回復 ACK
主動方回復最后一個 ACK,進入TIME_WAIT狀態:
No. Time Source Destination Protocol Info 16 5.340250 192.168.1.10 10.0.0.1 TCP 54321 > 443 [ACK] Seq=501 Ack=901 Win=65536 Len=0
被動方收到 ACK 后直接進入 CLOSED。主動方需要等待 2MSL(Maximum Segment Lifetime)后才進入 CLOSED。
4.3 四次揮手能變成三次嗎
可以。如果被動關閉方在收到 FIN 時恰好也沒有數據要發了,內核會把 ACK 和 FIN 合并成一個包(FIN+ACK),這就變成了三次揮手。在 Wireshark 中經常能看到這種情況,Linux 內核默認開啟了 TCP 延遲確認(Delayed ACK),會嘗試合并 ACK 和 FIN。
No. Time Source Destination Protocol Info 10 5.234100 192.168.1.10 10.0.0.1 TCP 54321 > 443 [FIN, ACK] Seq=500 Ack=800 11 5.234650 10.0.0.1 192.168.1.10 TCP 443 > 54321 [FIN, ACK] Seq=800 Ack=501 <-- ACK和FIN合并 12 ? 5.234700 192.168.1.10 ?10.0.0.1 ? ? ?TCP ? ? ? 54321 > 443 [ACK] Seq=501 Ack=801
4.4 TIME_WAIT 狀態詳解
TIME_WAIT 是 TCP 狀態機中最容易引發生產問題的狀態。主動關閉方在發送最后一個 ACK 后進入 TIME_WAIT,持續 2MSL(Linux 上硬編碼為 60 秒)。
為什么需要 TIME_WAIT:
確保最后一個 ACK 到達:如果最后的 ACK 丟失,被動方會重傳 FIN,主動方需要在 TIME_WAIT 狀態下重新發送 ACK。如果直接進入 CLOSED,收到重傳的 FIN 后會回復 RST,導致被動方異常關閉。
讓舊連接的殘留包在網絡中消亡:TCP 用四元組(源IP、源端口、目的IP、目的端口)標識連接。如果舊連接關閉后立即用相同四元組建立新連接,網絡中殘留的舊包可能被新連接錯誤接收。等待 2MSL 確保舊包全部過期。
TIME_WAIT 堆積的危害:
# 查看 TIME_WAIT 連接數量
ss -s
# 或者
ss -ant | awk'{print $1}'| sort | uniq -c | sort -rn
# 典型輸出
28453 TIME-WAIT
1024 ESTABLISHED
12 LISTEN
3 FIN-WAIT-2
每個 TIME_WAIT 連接占用約 0.25KB 內存(內核用 inet_timewait_sock 結構體,比完整的 tcp_sock 小得多),28000 個也就 7MB,內存不是主要問題。真正的問題是端口耗盡——客戶端的臨時端口范圍默認是 32768-60999,總共 28232 個,如果對同一個目標 IP:Port 的 TIME_WAIT 連接占滿了這個范圍,新連接就建不了了。
TIME_WAIT 優化方案:
# 方案一:開啟 tcp_tw_reuse(推薦) # 允許在 TIME_WAIT 狀態的端口被新的出站連接復用 # 前提是新連接的 timestamp 大于舊連接的最后 timestamp sysctl -w net.ipv4.tcp_tw_reuse=1 # 方案二:縮短 FIN_TIMEOUT(影響 FIN_WAIT_2 超時,不影響 TIME_WAIT) sysctl -w net.ipv4.tcp_fin_timeout=15 # 方案三:擴大臨時端口范圍 sysctl -w net.ipv4.ip_local_port_range="1024 65535" # 方案四:使用連接池復用長連接(應用層方案,最推薦) # HTTP Keep-Alive / gRPC 長連接 / 數據庫連接池
注意:tcp_tw_recycle在 Linux 4.12 之后已經被移除了,不要再用這個參數。它在 NAT 環境下會導致大量連接失敗。
五、Wireshark 抓包實戰
5.1 抓包準備
# 在服務器上用 tcpdump 抓包保存為 pcap 文件,然后用 Wireshark 打開分析 # -i eth0: 指定網卡 # -s 0: 抓完整包 # -w: 保存到文件 # port 443: 只抓 443 端口的流量 sudo tcpdump -i eth0 -s 0 -w /tmp/tcp-handshake.pcap port 443 # 另一個終端發起請求 curl -v https://example.com # 抓完后 Ctrl+C 停止,把 pcap 文件下載到本地用 Wireshark 打開
5.2 Wireshark 過濾器速查
Wireshark 的顯示過濾器是分析抓包的核心技能:
# 基礎過濾 tcp.port == 443 # 源或目的端口是 443 tcp.dstport == 80 # 目的端口是 80 ip.addr == 192.168.1.10 # 源或目的 IP # 標志位過濾(抓握手揮手的關鍵) tcp.flags.syn == 1 && tcp.flags.ack == 0 # 只看 SYN 包(第一次握手) tcp.flags.syn == 1 && tcp.flags.ack == 1 # 只看 SYN+ACK 包(第二次握手) tcp.flags.fin == 1 # 只看 FIN 包 tcp.flags.reset == 1 # 只看 RST 包(排查異常斷連) # 連接狀態過濾 tcp.analysis.retransmission # 重傳包 tcp.analysis.duplicate_ack # 重復 ACK tcp.analysis.zero_window # 零窗口(接收方緩沖區滿) tcp.analysis.window_update # 窗口更新 # 組合過濾 tcp.flags.syn == 1 && ip.dst == 10.0.0.1 # 發往特定服務器的 SYN tcp.stream eq 5 # 只看第 5 條 TCP 流
5.3 TCP 流追蹤
在 Wireshark 中右鍵任意一個 TCP 包,選擇Follow -> TCP Stream,可以看到整條連接從握手到揮手的完整生命周期。這是分析單個連接問題最高效的方式。
流追蹤視圖會用不同顏色區分兩個方向的數據,底部可以切換顯示格式(ASCII/Hex/Raw)。在 Stream 編號旁邊的下拉框可以快速切換不同的 TCP 流。
5.4 實戰:分析一次完整的 HTTPS 連接
用 Wireshark 打開抓包文件后,一次完整的 HTTPS 連接包含以下階段:
包序號 方向 內容 說明 ------ ---- ---- ---- 1 Client -> Server [SYN] TCP 三次握手開始 2 Server -> Client [SYN, ACK] 3 Client -> Server [ACK] TCP 連接建立完成 4 Client -> Server Client Hello TLS 握手開始 5 Server -> Client Server Hello, Cert... 6 Client -> Server Key Exchange, Finished 7 Server -> Client Finished TLS 握手完成 8 Client -> Server Application Data 加密的 HTTP 請求 9 Server -> Client Application Data 加密的 HTTP 響應 10 Client -> Server [FIN, ACK] TCP 四次揮手開始 11 Server -> Client [FIN, ACK] 合并的 FIN+ACK 12 Client -> Server [ACK] 連接關閉完成
重點關注包 1-3 的時間差:第一次握手到第二次握手的時間差就是一個 RTT(Round Trip Time),這是評估網絡延遲的直接指標。如果這個值超過 100ms,說明網絡延遲較高,需要考慮就近部署或 CDN 加速。
六、tcpdump 命令行抓包技巧
生產服務器通常沒有 GUI,tcpdump 是唯一的抓包手段。掌握 tcpdump 的過濾語法能大幅提升排查效率。
6.1 常用抓包命令
# 抓取指定端口的 SYN 包(只看新連接建立) sudo tcpdump -i any'tcp[tcpflags] & (tcp-syn) != 0'and port 80 -nn # 抓取 RST 包(排查連接異常斷開) sudo tcpdump -i any'tcp[tcpflags] & (tcp-rst) != 0'-nn # 抓取 SYN 但不包含 ACK 的包(純 SYN,第一次握手) sudo tcpdump -i any'tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn'-nn # 抓取特定主機之間的流量并保存 sudo tcpdump -i eth0 host 10.0.0.1 and port 443 -s 0 -w /tmp/debug.pcap -c 10000 # 只抓包頭不抓數據(節省磁盤空間) sudo tcpdump -i eth0 -s 96 port 3306 -w /tmp/mysql-headers.pcap # 實時查看 TCP 標志位和序列號 sudo tcpdump -i any port 80 -nn -S -tttt # -nn: 不解析主機名和端口名 # -S: 顯示絕對序列號 # -tttt: 顯示完整時間戳
6.2 tcpdump 輸出解讀
# 典型的三次握手輸出 1401.123456 IP 192.168.1.10.54321 > 10.0.0.1.443: Flags [S], seq 1234567890, win 65535, options [mss 1460,sackOK,TS val 123456 ecr 0,nop,wscale 7], length 0 1401.124012 IP 10.0.0.1.443 > 192.168.1.10.54321: Flags [S.], seq 987654321, ack 1234567891, win 65535, options [mss 1460,sackOK,TS val 654321 ecr 123456,nop,wscale 7], length 0 1401.124050 IP 192.168.1.10.54321 > 10.0.0.1.443: Flags [.], ack 987654322, win 512, length 0
Flags 字段的含義:[S]= SYN,[S.]= SYN+ACK,[.]= ACK,[F.]= FIN+ACK,[R]= RST,[P.]= PSH+ACK。
6.3 高級過濾技巧
# 抓取 TCP 窗口為 0 的包(零窗口,接收方緩沖區滿) sudo tcpdump -i any'tcp[14:2] = 0'-nn # 抓取包含特定 TCP Option 的包(比如 Window Scale) sudo tcpdump -i any'tcp[tcpflags] & tcp-syn != 0 and tcp[20] = 3'-nn # 抓取大于指定長度的包(排查大包問題) sudo tcpdump -i any'tcp and greater 1400'-nn # 按時間輪轉保存(長時間抓包) # -G 3600: 每小時輪轉一次 # -W 24: 最多保留 24 個文件 sudo tcpdump -i eth0 port 443 -w /tmp/capture-%Y%m%d-%H%M%S.pcap -G 3600 -W 24
七、TCP 重傳機制
TCP 的可靠傳輸依賴重傳機制。理解重傳對排查網絡丟包、延遲抖動等問題至關重要。
7.1 超時重傳(RTO Retransmission)
發送方發出數據后啟動一個重傳定時器(RTO,Retransmission Timeout)。如果在 RTO 時間內沒有收到 ACK,就重傳該數據段。
RTO 的計算基于 RTT 的采樣值,Linux 使用 Jacobson 算法動態調整:
SRTT = (1 - α) * SRTT + α * RTT_sample (α = 1/8) RTTVAR = (1 - β) * RTTVAR + β * |SRTT - RTT| (β = 1/4) RTO = SRTT + 4 * RTTVAR
RTO 的最小值是 200ms(TCP_RTO_MIN),最大值是 120s(TCP_RTO_MAX)。每次超時重傳后 RTO 翻倍(指數退避),依次是 200ms、400ms、800ms、1.6s...直到達到最大重傳次數(tcp_retries2默認 15 次,總計約 15 分鐘)。
# 查看當前連接的 RTO 值 ss -ti dst 10.0.0.1:443 # 輸出中的 rto:204 表示當前 RTO 為 204ms
7.2 快速重傳(Fast Retransmit)
等 RTO 超時太慢了。快速重傳機制在收到 3 個重復 ACK(Duplicate ACK)時立即重傳丟失的數據段,不用等定時器超時。
發送方 接收方 |--- Seq=1, Len=1000 ---> | |--- Seq=1001, Len=1000 ---> | (丟失) |--- Seq=2001, Len=1000 ---> | |<-- ACK=1001 (Dup ACK?#1) ---------| ?收到 Seq=2001 但缺 1001,回復 ACK=1001 ? |--- Seq=3001, Len=1000 ---> | |<-- ACK=1001 (Dup ACK?#2) ---------| ? |--- Seq=4001, Len=1000 ---> | |<-- ACK=1001 (Dup ACK?#3) ---------| ?第 3 個重復 ACK ? |--- Seq=1001, Len=1000 ---> | 快速重傳! |<-- ACK=5001 ----------------------| ?SACK 機制下一次確認所有已收到的數據
在 Wireshark 中,快速重傳的包會被標記為[TCP Fast Retransmission],重復 ACK 會被標記為[TCP Dup ACK]。
7.3 SACK(選擇性確認)
沒有 SACK 的情況下,快速重傳只能重傳一個包,后續丟失的包還得等超時。SACK 允許接收方告訴發送方"我收到了哪些數據段",發送方只需要重傳真正丟失的部分。
# 確認 SACK 是否啟用 sysctl net.ipv4.tcp_sack # net.ipv4.tcp_sack = 1 (默認開啟)
在 Wireshark 中查看 SACK 信息:過濾tcp.options.sack,可以看到 SACK 塊的左右邊界,精確標識了接收方已收到的數據范圍。
八、TCP 擁塞控制:BBR vs Cubic
擁塞控制決定了 TCP 發送數據的速率。選錯算法,帶寬利用率可能差幾倍。
8.1 Cubic(Linux 默認)
Cubic 是基于丟包的擁塞控制算法,Linux 從 2.6.19 開始默認使用。核心思路是:沒丟包就加速,丟包了就減速。
工作階段:
慢啟動(Slow Start):窗口從 initcwnd(默認 10 個 MSS)開始,每收到一個 ACK 窗口加 1,指數增長
擁塞避免(Congestion Avoidance):窗口超過 ssthresh 后改為線性增長
丟包響應:檢測到丟包后,窗口乘以一個系數(Cubic 用三次函數恢復)
Cubic 的問題:
在高帶寬高延遲(長肥管道)網絡中,丟包恢復太慢,帶寬利用率低
把丟包等同于擁塞,但現代網絡中丟包可能是隨機的(無線網絡)
緩沖區膨脹(Bufferbloat):在路由器緩沖區很大的情況下,Cubic 會把緩沖區填滿才觸發丟包,導致延遲飆升
8.2 BBR(Bottleneck Bandwidth and RTT)
BBR 是 Google 在 2016 年提出的擁塞控制算法,Linux 4.9 開始支持。BBR v3 在 Linux 6.x 內核中已經相當成熟。
核心思路:不依賴丟包信號,而是主動探測瓶頸帶寬(BtlBw)和最小 RTT(RTprop),讓發送速率 = BtlBw,inflight 數據量 = BtlBw * RTprop。
BBR 的優勢:
高帶寬利用率:在長肥管道中表現遠優于 Cubic
低延遲:不會填滿路由器緩沖區
抗隨機丟包:不把隨機丟包當作擁塞信號
BBR 的注意事項:
BBR 在與 Cubic 共存時可能搶占帶寬(公平性問題),BBR v3 對此有改善
在低延遲局域網中,BBR 和 Cubic 差異不大
需要內核 6.x+ 才能用到 BBR v3 的完整特性
# 查看當前使用的擁塞控制算法 sysctl net.ipv4.tcp_congestion_control # 查看可用的算法 sysctl net.ipv4.tcp_available_congestion_control # 切換到 BBR sysctl -w net.ipv4.tcp_congestion_control=bbr # 持久化配置 cat >> /etc/sysctl.conf <'EOF' net.core.default_qdisc = fq net.ipv4.tcp_congestion_control = bbr EOF sysctl -p
8.3 如何選擇
| 場景 | 推薦算法 | 理由 |
|---|---|---|
| 跨地域/跨國傳輸 | BBR | 高延遲網絡下帶寬利用率遠高于 Cubic |
| CDN 邊緣節點 | BBR | 面對各種網絡質量的客戶端,BBR 適應性更強 |
| 數據中心內部 | Cubic | 低延遲環境差異不大,Cubic 公平性更好 |
| 視頻流媒體 | BBR | 低延遲 + 高吞吐的組合適合實時傳輸 |
| 與大量 Cubic 流共存 | Cubic 或 BBR v3 | BBR v1/v2 的公平性問題在 v3 中有改善 |
九、最佳實踐
9.1 連接管理最佳實踐
服務端:
# /etc/sysctl.conf 推薦配置 # 全連接隊列長度,配合應用的 listen backlog 使用 net.core.somaxconn = 65535 # 半連接隊列長度 net.ipv4.tcp_max_syn_backlog = 65535 # 開啟 SYN Cookie 防御 SYN Flood net.ipv4.tcp_syncookies = 1 # TIME_WAIT 相關 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_fin_timeout = 15 net.ipv4.ip_local_port_range = 1024 65535 net.ipv4.tcp_max_tw_buckets = 262144
應用層:
使用 HTTP/2 或 gRPC 多路復用,減少連接數
配置合理的 Keep-Alive 超時,避免空閑連接占用資源
使用連接池管理數據庫和緩存連接
優雅關閉:先shutdown(SHUT_WR)再close(),給對方發送剩余數據的機會
9.2 抓包分析最佳實踐
抓包時用 BPF 過濾器縮小范圍,避免抓到無關流量導致文件過大
生產環境抓包設置-c(包數量限制)或-G -W(時間輪轉),防止磁盤寫滿
分析時先用capinfos查看 pcap 文件概況,再用 Wireshark 的 Statistics -> Conversations 看連接分布
用tshark(Wireshark 的命令行版本)做批量分析和統計
# 用 tshark 統計重傳率 tshark -r capture.pcap -q -z io,stat,0,"tcp.analysis.retransmission" # 用 tshark 導出特定流的數據 tshark -r capture.pcap -Y"tcp.stream eq 5"-w stream5.pcap
十、常見 TCP 問題排查
10.1 連接超時(Connection Timeout)
現象:客戶端connect()長時間無響應,最終超時報錯。
排查思路:
# 第一步:確認服務端端口是否在監聽 ss -lnt | grep :443 # 第二步:在客戶端抓包看 SYN 是否發出去了 sudo tcpdump -i any'tcp[tcpflags] & tcp-syn != 0'and dst host 10.0.0.1 -nn # 第三步:在服務端抓包看 SYN 是否到達 sudo tcpdump -i any'tcp[tcpflags] & tcp-syn != 0'and dst port 443 -nn # 第四步:檢查防火墻規則 iptables -L -n -v | grep 443 nft list ruleset | grep 443
常見原因和解決方案:
| 原因 | 診斷方法 | 解決方案 |
|---|---|---|
| 防火墻攔截 SYN | 客戶端有 SYN 發出但服務端沒收到 | 檢查中間防火墻/安全組規則 |
| 半連接隊列溢出 | netstat -s | grep "SYNs to LISTEN" 計數增長 | 增大tcp_max_syn_backlog |
| 全連接隊列溢出 | netstat -s | grep "overflowed" 計數增長 | 增大somaxconn+ 應用 backlog |
| 服務端 CPU 打滿 | SYN+ACK 延遲回復 | 排查服務端性能問題 |
| 路由不通 | traceroute 中間斷開 | 檢查路由表和網絡設備 |
10.2 RST 包排查
RST 是 TCP 的"緊急剎車",收到 RST 意味著連接被強制中斷。排查 RST 的關鍵是搞清楚誰發的、為什么發。
常見 RST 場景:
# 場景一:連接不存在的端口 # 服務端沒有進程監聽該端口,內核直接回復 RST curl http://10.0.0.1:9999 # 抓包看到:SYN -> RST,ACK # 場景二:應用異常關閉連接 # 應用設置了 SO_LINGER l_onoff=1, l_linger=0,close() 時發 RST 而非 FIN # 或者應用在接收緩沖區還有未讀數據時調用 close() # 場景三:防火墻/LB 超時 # 中間設備(防火墻、負載均衡器)的連接跟蹤表超時,后續包被回復 RST # 典型表現:空閑一段時間后的第一個請求收到 RST # 場景四:全連接隊列溢出且 tcp_abort_on_overflow=1 sysctl net.ipv4.tcp_abort_on_overflow # 如果為 1,隊列滿時服務端對第三次握手的 ACK 回復 RST
RST 排查命令:
# 抓取所有 RST 包并顯示詳細信息
sudo tcpdump -i any'tcp[tcpflags] & tcp-rst != 0'-nn -tttt
# 統計 RST 包的來源分布
sudo tcpdump -i any'tcp[tcpflags] & tcp-rst != 0'-nn -c 1000 2>/dev/null |
awk'{print $3}'| cut -d. -f1-4 | sort | uniq -c | sort -rn
# 用 ss 查看連接被 RST 的統計
ss -s
# 關注 "reset" 相關的計數
10.3 半連接隊列溢出(SYN Flood)
SYN Flood 攻擊通過大量偽造源 IP 的 SYN 包填滿服務端的半連接隊列,導致正常連接無法建立。
檢測方法:
# 查看 SYN_RECV 狀態的連接數 ss -ant state syn-recv | wc -l # 查看半連接隊列溢出計數(持續增長說明有問題) netstat -s | grep"SYNs to LISTEN" # 查看 SYN Cookie 觸發次數 netstat -s | grep"SYN cookies" # 用 nstat 看增量統計(比 netstat -s 更適合監控) nstat -az TcpExtListenDrops TcpExtListenOverflows TcpExtSyncookiesSent
防御方案:
# 開啟 SYN Cookie(必須開啟) sysctl -w net.ipv4.tcp_syncookies=1 # 增大半連接隊列 sysctl -w net.ipv4.tcp_max_syn_backlog=65535 # 減少 SYN+ACK 重傳次數(加快清理無效半連接) sysctl -w net.ipv4.tcp_synack_retries=2 # 配合 iptables 限速 iptables -A INPUT -p tcp --syn -mlimit--limit500/s --limit-burst 1000 -j ACCEPT iptables -A INPUT -p tcp --syn -j DROP
十一、內核參數調優完整方案
11.1 生產環境推薦配置
以下是經過大量生產驗證的 TCP 內核參數配置,適用于高并發 Web 服務場景:
# /etc/sysctl.d/99-tcp-tuning.conf # ============ 連接隊列 ============ # 全連接隊列最大長度(需要應用 listen backlog 配合) net.core.somaxconn = 65535 # 半連接隊列最大長度 net.ipv4.tcp_max_syn_backlog = 65535 # ============ TIME_WAIT 優化 ============ # 允許 TIME_WAIT 端口復用(僅對出站連接有效) net.ipv4.tcp_tw_reuse = 1 # FIN_WAIT_2 超時時間(秒) net.ipv4.tcp_fin_timeout = 15 # TIME_WAIT 狀態的最大數量 net.ipv4.tcp_max_tw_buckets = 262144 # 臨時端口范圍 net.ipv4.ip_local_port_range = 1024 65535 # ============ SYN Flood 防御 ============ net.ipv4.tcp_syncookies = 1 # SYN+ACK 重傳次數 net.ipv4.tcp_synack_retries = 2 # SYN 重傳次數(影響 connect 超時時間) net.ipv4.tcp_syn_retries = 3 # ============ Keep-Alive ============ # 空閑多久后開始發送 Keep-Alive 探測(秒) net.ipv4.tcp_keepalive_time = 600 # 探測間隔(秒) net.ipv4.tcp_keepalive_intvl = 15 # 探測次數,超過后斷開連接 net.ipv4.tcp_keepalive_probes = 5 # ============ 緩沖區 ============ # TCP 接收緩沖區(最小值、默認值、最大值,單位字節) net.ipv4.tcp_rmem = 4096 87380 16777216 # TCP 發送緩沖區 net.ipv4.tcp_wmem = 4096 65536 16777216 # 全局接收/發送緩沖區 net.core.rmem_max = 16777216 net.core.wmem_max = 16777216 # ============ 擁塞控制 ============ net.core.default_qdisc = fq net.ipv4.tcp_congestion_control = bbr # ============ 其他優化 ============ # 開啟 TCP Fast Open(客戶端和服務端都支持) net.ipv4.tcp_fastopen = 3 # 開啟 SACK net.ipv4.tcp_sack = 1 # 開啟窗口縮放 net.ipv4.tcp_window_scaling = 1 # 全連接隊列溢出時的行為(0=丟棄ACK,1=發RST) # 生產環境建議設為 0,讓客戶端重試而非收到 RST net.ipv4.tcp_abort_on_overflow = 0
# 應用配置 sudo sysctl -p /etc/sysctl.d/99-tcp-tuning.conf # 驗證配置生效 sysctl net.core.somaxconn net.ipv4.tcp_tw_reuse net.ipv4.tcp_congestion_control
11.2 參數調優注意事項
| 參數 | 踩坑點 | 建議 |
|---|---|---|
| somaxconn | 只改內核不改應用的 listen backlog 沒用 | Nginx:listen 80 backlog=65535 |
| tcp_tw_reuse | 只對出站連接(客戶端角色)有效 | 服務端 TIME_WAIT 多要從應用層解決 |
| tcp_max_tw_buckets | 超過限制后直接銷毀 TIME_WAIT,會打日志 | 設大一點,別讓它觸發 |
| tcp_keepalive_time | 應用層的 Keep-Alive 設置會覆蓋內核參數 | 優先在應用層配置 |
| tcp_rmem/wmem | 最大值設太大會導致內存占用過高 | 根據實際連接數和可用內存計算 |
| tcp_fastopen | 需要客戶端和服務端都支持,且中間設備不能剝離 TFO Cookie | 先在非關鍵服務上測試 |
11.3 Kubernetes 環境特殊處理
在 K8s 環境中,Pod 的內核參數需要通過 securityContext 設置:
apiVersion:v1 kind:Pod metadata: name:web-server spec: securityContext: sysctls: -name:net.core.somaxconn value:"65535" -name:net.ipv4.tcp_tw_reuse value:"1" -name:net.ipv4.ip_local_port_range value:"1024 65535" containers: -name:nginx image:nginx:1.27
注意:K8s 默認只允許設置 "safe" sysctls(net.ipv4.ip_local_port_range等),"unsafe" sysctls(如net.ipv4.tcp_syncookies)需要在 kubelet 配置中顯式允許。
十二、總結
12.1 技術要點回顧
TCP 報文格式:理解 Sequence Number、Acknowledgment Number、Flags 這三個字段是看懂抓包的基礎
三次握手:本質是交換 ISN + 確認雙方收發能力,半連接隊列和全連接隊列是生產問題的高發區
四次揮手:全雙工關閉需要雙向獨立進行,TIME_WAIT 是主動關閉方的必經狀態
TIME_WAIT 優化:tcp_tw_reuse+ 擴大端口范圍 + 連接池是三板斧,tcp_tw_recycle已廢棄
Wireshark 過濾器:tcp.flags.syn、tcp.flags.reset、tcp.analysis.retransmission是排查三件套
tcpdump:-nn -S -tttt是標準參數組合,BPF 過濾器語法必須掌握
重傳機制:超時重傳兜底,快速重傳加速,SACK 精確定位丟失數據段
擁塞控制:跨地域選 BBR,數據中心內部 Cubic 夠用,BBR v3 改善了公平性
內核調優:somaxconn+tcp_max_syn_backlog+tcp_tw_reuse是高并發服務的必調參數
12.2 面試高頻問題速查
| 問題 | 關鍵答案 |
|---|---|
| 為什么三次握手不是兩次 | 防止歷史連接初始化 + 確認雙方收發能力 |
| 為什么揮手是四次不是三次 | 全雙工關閉,被動方可能還有數據要發(但可以合并為三次) |
| TIME_WAIT 存在的意義 | 確保最后 ACK 到達 + 讓舊包在網絡中消亡 |
| SYN Flood 怎么防御 | SYN Cookie + 增大半連接隊列 + 限速 |
| RST 和 FIN 的區別 | FIN 是優雅關閉(四次揮手),RST 是強制中斷(不等對方確認) |
| BBR 和 Cubic 的區別 | Cubic 基于丟包,BBR 基于帶寬和延遲探測 |
| 全連接隊列滿了會怎樣 | 默認丟棄 ACK(客戶端重試),tcp_abort_on_overflow=1時發 RST |
12.3 參考資料
RFC 9293 - TCP 規范(2022 年更新版,合并了多個舊 RFC)
BBR Congestion Control - Google Research
Wireshark TCP Analysis Documentation
Linux Kernel Networking - TCP Implementation
tcpdump Manual Page
附錄
A. TCP 狀態機完整圖
+---------+ --------- active OPEN
| CLOSED | -----------
+---------+<--------- ? ? create TCB
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ^ ? ? ? ? ? ? ? ? ?snd SYN
? ? ? ? ? ? ? ? ? ?passive OPEN | ? ? | ? CLOSE ? ? ? ? ?
? ? ? ? ? ? ? ? ? ?------------ | ? ? | ---------- ? ? ? ?
? ? ? ? ? ? ? ? ? ? create TCB ?| ? ? | delete TCB ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? V ? ? | ? ? ? ? ? ? ? ? ? ? ? ? V
? ? ? ? ? ? ? ? ? +----------+ ? ? ? +----------+ ? ? ? +---------+
? ? ? ? ? ? ? ? ? | ?LISTEN ?| ? ? ? | FIN_WAIT | ? ? ? | SYN ? ? |
? ? ? ? ? ? ? ? ? +----------+ ? ? ? | ? ?_2 ? ?| ? ? ? | SENT ? ?|
? ? ? ?rcv SYN ? | ? ? | ? ?| ? ? ? +----------+ ? ? ? +---------+
? ? ? ---------- | ? ? | ? ?| ?CLOSE ? | ? ? ^ ? ?rcv SYN+ACK ?|
? ? ? snd SYN,ACK/ ? ? | ? ?| ------- | ? ? | ? ?---------- ? |
? ? ? ? ? ? ? ? ?/ ? ? ?| ? ?| snd FIN | ? ? | ? ? snd ACK ? ?|
? ? ? ? ? ? ? ? V ? ? ? | ? ?V ? ? ? ? | ? ? | ? ? ? ? ? ? ? ?|
? ? ? ? ?+---------+ ? ?| ?+-------+ ? | ? ? | ? ? ? ? ? ? ? ?V
? ? ? ? ?|SYN_RCVD | ? ?| ?|CLOSING| ? | ? ? | ? ? ? ? +---------+
? ? ? ? ?+---------+ ? ?| ?+-------+ ? | ? ? | ? ? ? ? | ?ESTAB ?|
? ? ? ? ? ?| rcv ACK ? ?| ?rcv ACK| ? ?| ? ? | ? ? ? ? +---------+
? ? ? ? ? ?| ------- ? ?| ?-------| rcv FIN ?| ?CLOSE ? ?| rcv FIN
? ? ? ? ? ?| x ? ? ? ? ?| ?x ? ? ?| ------- ?| ?------- ?| -------
? ? ? ? ? ?V ? ? ? ? ? ?| ? ? ? ? V snd ACK ?| ?snd FIN ?V snd ACK
? ? ? ? ?+---------+ ? ?| ?+---------+ ? ? ? | ? ? ? ? +---------+
? ? ? ? ?| ?ESTAB ?| ? ?| ?|TIME_WAIT| ? ? ? | ? ? ? ? |CLOSE_ ? |
? ? ? ? ?+---------+ ? ?| ?+---------+ ? ? ? | ? ? ? ? | ?WAIT ? |
? ? ? ? ? ? ? | ? ? ? ? | ? ?| 2MSL timeout ?| ? ? ? ? +---------+
? ? ? ? ? ? ? | ? ? ? ? | ? ?| ---------- ? ?| ? ? ? ? CLOSE |
? ? ? ? ? ? ? | ? ? ? ? | ? ?| delete TCB ? ?| ? ? ? ? ------|
? ? ? ? ? ? ? | ? ? ? ? | ? ?V ? ? ? ? ? ? ? | ? ? ? ? snd FIN
? ? ? ? ? ? ? | ? ? ? ? | +---------+ ? ? ? ?| ? ? ? ? ? ? ? V
? ? ? ? ? ? ? | ? ? ? ? +>| CLOSED | | +---------+
| +---------+ +-------->|LAST_ACK |
| +---------+
| rcv ACK |
| --------|
| x V
| +---------+
+--------------------------------------->| CLOSED |
+---------+
B. 命令速查表
# 連接狀態查看 ss -ant # 查看所有 TCP 連接狀態 ss -s # TCP 連接統計摘要 ss -lnt # 查看監聽端口和隊列狀態 ss -ant state time-wait | wc -l # 統計 TIME_WAIT 數量 ss -ti dst 10.0.0.1 # 查看到特定目標的連接詳情(含 RTT/RTO) # 抓包 tcpdump -i any port 80 -nn -S -tttt # 標準抓包參數組合 tcpdump -i any'tcp[tcpflags] & tcp-syn != 0'-nn # 只抓 SYN 包 tcpdump -i any'tcp[tcpflags] & tcp-rst != 0'-nn # 只抓 RST 包 # 內核統計 nstat -az TcpExtListenDrops # 全連接隊列溢出次數 nstat -az TcpExtListenOverflows # 全連接隊列溢出次數(另一個計數器) nstat -az TcpExtTCPSynRetrans # SYN 重傳次數 nstat -az TcpExtTCPTimeouts # TCP 超時次數 # 內核參數查看 sysctl -a | grep tcp # 查看所有 TCP 相關參數 sysctl net.core.somaxconn # 查看全連接隊列上限 sysctl net.ipv4.tcp_congestion_control # 查看擁塞控制算法
六、總結
6.1 技術要點回顧
三次握手/四次揮手的狀態機是 TCP 排障的基本功。生產環境里遇到連接異常,第一反應應該是ss -ant看狀態分布,而不是盲目抓包。SYN_RECV 堆積指向半連接隊列溢出或 SYN Flood,CLOSE_WAIT 堆積說明應用層沒有正確關閉連接,FIN_WAIT_2 長期存在則要檢查對端是否還活著。狀態機搞清楚了,排查方向就不會跑偏。
TIME_WAIT 不是病,但大量堆積需要干預。TIME_WAIT 存在的意義是防止舊連接的延遲報文干擾新連接,2MSL 的等待時間是協議設計的一部分。真正需要處理的是短連接高并發場景下 TIME_WAIT 數量達到幾萬甚至十幾萬的情況。優先考慮tcp_tw_reuse(安全地復用 TIME_WAIT 端口),配合連接池和長連接從根源上減少連接創建頻率。tcp_tw_recycle在 NAT 環境下會導致丟包,內核 4.12 之后已經移除,別再用了。
BBR 擁塞控制在高延遲高丟包場景下優勢明顯。傳統的 Cubic 算法基于丟包檢測來調整窗口,在跨地域專線、衛星鏈路等高 RTT 場景下表現保守,帶寬利用率上不去。BBR 通過主動探測瓶頸帶寬和最小 RTT 來驅動發送速率,不依賴丟包信號,在這類場景下吞吐量提升顯著。但 BBR 也不是銀彈——在淺緩沖交換機環境下可能造成較高的排隊延遲,部署前需要實測驗證。
Wireshark 和 tcpdump 是網絡排障的瑞士軍刀。tcpdump 負責在服務器上輕量抓包,Wireshark 負責離線深度分析。掌握 BPF 過濾表達式、TCP 流追蹤、IO Graph、RTT 統計這幾個核心功能,絕大多數 TCP 層面的問題都能定位到根因。關鍵是養成習慣:先用ss/nstat看宏觀統計,縮小范圍后再針對性抓包,避免在海量數據里大海撈針。
6.2 進階學習方向
QUIC 協議(HTTP/3 的傳輸層)
QUIC 把 TCP 的連接管理、TLS 握手、多路復用全部搬到了用戶態 UDP 之上,從根本上消除了隊頭阻塞問題。三次握手 + TLS 1.3 握手合并為 1-RTT 甚至 0-RTT 建連,對移動網絡和弱網環境的體驗提升非常大。理解了 TCP 狀態機之后再看 QUIC 的連接遷移(Connection Migration)和丟包恢復機制,會更容易理解它的設計取舍。
實踐建議:用 Wireshark 4.x 抓取 HTTP/3 流量,對比同一請求在 TCP+TLS 1.3 和 QUIC 下的握手耗時差異。
eBPF 網絡追蹤(bpftrace / tcplife / tcpconnect)
傳統的 tcpdump 抓包開銷不小,而且只能看到報文層面的信息。eBPF 可以直接在內核態掛載探針,零拷貝地追蹤 TCP 連接的生命周期、RTT 變化、重傳事件,開銷比抓包低一個數量級。BCC 工具集里的tcplife能實時輸出每條連接的存活時間和傳輸字節數,tcpretrans能精確定位重傳發生在哪條流上,排障效率遠超傳統方式。
實踐建議:從bpftrace -e 'kprobe:tcp_retransmit_skb { printf("retrans: %s:%d ", ntop(args->sk->__sk_common.skc_daddr), args->sk->__sk_common.skc_dport); }'這類單行腳本入手,逐步深入。
內核網絡棧源碼閱讀
讀過net/ipv4/tcp_input.c和tcp_output.c之后,很多之前靠經驗判斷的問題都能找到確定性答案。比如 SYN Cookie 的具體實現邏輯、快速重傳的觸發條件、窗口縮放因子的協商過程,這些在源碼里都是幾十行代碼的事情。配合ftrace或perf跟蹤內核函數調用路徑,能把抓包現象和內核行為完整對應起來。
推薦從 Linux 6.x 內核入手,代碼結構比早期版本清晰很多,注釋也更完善。
6.3 參考資料
RFC 793 - Transmission Control Protocol- TCP 協議的原始規范,三次握手和四次揮手的權威定義,狀態機圖就出自這里
RFC 8312 - CUBIC for Fast Long-Distance Networks- Linux 默認擁塞控制算法 CUBIC 的規范,理解其凹凸函數窗口增長模型
RFC 9002 - QUIC Loss Detection and Congestion Control- QUIC 的丟包檢測和擁塞控制機制,BBR 在 QUIC 中的應用參考
Wireshark User's Guide- Wireshark 官方用戶手冊,TCP 流分析和過濾器語法的權威參考
tcpdump & libpcap- tcpdump 手冊頁,BPF 過濾表達式的完整語法說明
《TCP/IP 詳解 卷1:協議》(W. Richard Stevens 著) - TCP 協議學習的經典教材,第 17-24 章覆蓋了 TCP 連接管理、重傳、擁塞控制的完整細節
Linux 內核源碼 net/ipv4/tcp*.c- Bootlin 提供的在線內核源碼瀏覽,直接看 TCP 實現比讀任何二手資料都準確
-
網絡
+關注
關注
14文章
8264瀏覽量
94715 -
TCP
+關注
關注
8文章
1424瀏覽量
83501 -
Wireshark
+關注
關注
0文章
51瀏覽量
6960
原文標題:TCP 三次握手與四次揮手:Wireshark 抓包分析面試必考題
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
TCP/IP協議工作過程的三次握手和四次揮手
TCP三次握手過程及四次揮手過程說明
TCP三次握手和四次揮手以及11種狀態資料下載
如何使用WireShark進行TCP三次握手
TCP建立連接概述及三次握手、四次揮手的流程
TCP三次握手與四次揮手的詳細過程
評論