題幹與適用場景
傳送端連續送出五個 1000 位元組的 TCP 資料段,起始序號為 1000,其中第二段遺失,後三段抵達接收端。請推導 ACK、SACK 和重傳過程,並比較逾時重傳、快速重傳及現代遺失偵測的適用邊界。
題目預設連線已建立,五段都承載資料,接收視窗與壅塞視窗足以讓它們在途,接收端能暫存亂序資料,而且雙方在交握時協商了 SACK。序號按位元組計數,區間使用左閉右開表示。若取消 SACK,累積 ACK 與經典快速重傳仍然成立,只是傳送端少了哪些高序位元組已抵達的資訊。
這題適合後端、基礎設施、SRE、用戶端、網路和通用軟體工程職位。核心能力是從傳輸協定狀態推導結果並驗證現場證據,因此歸為 general;它不考 HTTP/3 的跨串流隔離,也不考連線建立、關閉和 TIME_WAIT。
面試官考察點
第一,能否說準序號和 ACK 的單位。TCP 為位元組編號;資料段的序號是第一個資料位元組的編號,累積 ACK 表示接收端下一個期待的位元組,不是「收到第幾個封包」。
第二,能否沿時間線推導。第二段遺失後,後續亂序段不會推進累積 ACK,但會觸發重複 ACK;第三個重複 ACK 是 RFC 5681 經典快速重傳的遺失訊號。把最初確認第一段的 ACK 也算成重複 ACK,會提前一步得到錯誤結論。
第三,能否區分復原機制。RTO 處理沒有足夠 ACK 回饋的遺失;快速重傳利用後續資料產生的重複 ACK 提前復原;SACK 描述已收到的非連續位元組區塊,協助傳送端避免重傳已抵達資料,但不取代累積 ACK,也不單獨決定壅塞視窗。
第四,能否解釋不確定性。重複 ACK 也可能來自重排或複製,逾時也可能是 RTT 突增而非真實遺失。可靠傳輸、遺失偵測與壅塞控制互相作用,但不是同一個概念。
第五,能否超越課本口號。強回答會說明小在途量和尾端遺失可能湊不出三次重複 ACK,並指出 RACK-TLP 等實作可以使用傳送時間與 SACK 回饋,減少對固定封包數門檻和 RTO 的依賴。
回答前需要澄清的問題
- 序號和長度是否按位元組提供? 若題目給的是相對封包編號,先轉成位元組區間;SYN 和 FIN 也占序號空間,但本題只有已建立連線中的資料段。
- SACK 是否在交握時協商? 未協商時只能依靠累積 ACK 與所用復原演算法;協商後,重複 ACK 可以攜帶已收到的亂序區塊。
- 在途量是否足以產生三個重複 ACK? 中間段之後至少還要有三個新段抵達,經典快速重傳才有足夠回饋;尾段遺失通常沒有後續資料製造重複 ACK。
- 討論 RFC 5681 的經典演算法還是某個實際核心? 經典門檻適合推導,真實系統可能使用可設定門檻、SACK 復原、RACK-TLP 或其他擴充,封包擷取結論必須結合作業系統與版本。
- 問題要求協定正確性還是效能診斷? 前者說明最後如何按序交付;後者還要觀察 RTT、RTO、壅塞視窗、重排、擷取位置和網卡卸載。
30 秒回答框架
「TCP 的序號按位元組計數,累積 ACK 是下一個期待的位元組。第一段涵蓋 1000 到 1999,收到後回覆 ACK 2000。第二段 2000 到 2999 遺失;後三段雖然抵達並可暫存,但缺口仍從 2000 開始,所以每到一段都再次回覆 ACK 2000。若協商了 SACK,這些 ACK 還會報告已經收到的 3000 到 6000。
按照 RFC 5681 的經典路徑,第三個重複 ACK 觸發對 2000 到 2999 的快速重傳,不必等待 RTO。缺口補齊後,累積 ACK 一次前進到 6000。若在途資料太少、尾段遺失或 ACK 回饋中斷,傳送端可能只能等待 RTO;RTO 根據平滑 RTT 與 RTT 波動計算,逾時後還會指數退避並更大幅縮小壅塞視窗。SACK 提升多重遺失復原精度,現代 RACK-TLP 還能用傳送時間、SACK 和探測段更快辨識尾端或重傳遺失。判讀封包擷取時,我會同時檢查累積 ACK、SACK 區塊、重傳間隔和實際協定堆疊,避免把重排或分析器標籤直接當成遺失證明。」
分步驟深入解答
第一步:先固定位元組區間與累積 ACK
五段的邏輯位元組範圍如下:
A: SEQ=1000, LEN=1000 -> [1000, 2000) 抵達
B: SEQ=2000, LEN=1000 -> [2000, 3000) 遺失
C: SEQ=3000, LEN=1000 -> [3000, 4000) 抵達
D: SEQ=4000, LEN=1000 -> [4000, 5000) 抵達
E: SEQ=5000, LEN=1000 -> [5000, 6000) 抵達A 抵達後,接收端已連續擁有 1000 到 1999,因此回覆 ACK=2000。這個 ACK 首次推進確認邊界,是新 ACK,不是重複 ACK。
B 遺失後,C 雖然在接收視窗內,卻無法填補從 2000 開始的缺口。接收端可以暫存 C,但累積確認仍是 ACK=2000。D、E 也一樣。RFC 9293 把 ACK 欄位定義為接收端下一個期待序號,這說明 ACK 為何不會跳到每個亂序段的末端。
第二步:推導三次重複 ACK 與快速重傳
在經典 RFC 5681 路徑中,C、D、E 每抵達一段都會讓接收端立即回覆一個重複 ACK=2000:
收到 A -> ACK 2000 新 ACK
收到 C,B 有缺口 -> ACK 2000 + SACK [3000,4000) 第 1 個重複 ACK
收到 D,B 有缺口 -> ACK 2000 + SACK [3000,5000) 第 2 個重複 ACK
收到 E,B 有缺口 -> ACK 2000 + SACK [3000,6000) 第 3 個重複 ACK
傳送端重傳 B -> SEQ 2000, LEN 1000
接收端收到 B -> ACK 6000 缺口補齊,累積前進第三個重複 ACK 抵達時,傳送端推斷 B 很可能遺失並快速重傳 [2000,3000),不必等待重傳計時器到期。接收端先前已暫存 C、D、E,所以 B 一到,連續位元組範圍立刻延伸到 5999,累積 ACK 可以直接從 2000 前進到 6000。
「三個重複 ACK」是遺失啟發式,不是數學證明。網路重排會讓高序段先到,一樣產生重複 ACK;複製的資料段或 ACK 也可能製造這個訊號。門檻在快速復原與誤判重排之間取折衷,實際協定堆疊可以採用額外演算法。
第三步:說明 SACK 提供什麼、沒有提供什麼
累積 ACK 只能告訴傳送端「2000 之前連續收到」。SACK 選項可以再報告接收緩衝區中的非連續位元組區塊,例如 [3000,6000)。它補上資訊缺口:傳送端知道高序資料已經抵達,復原多個缺口時可以跳過這些區塊。
SACK 有三個邊界:
- 必須在 SYN 階段透過 SACK-Permitted 協商,之後接收端才能在 ACK 中攜帶 SACK 區塊。
- SACK 不會把累積 ACK 從 2000 推到 6000;只有
[2000,3000)的缺口補齊,累積邊界才前進。 - SACK 是接收端提供的建議性資訊。它協助傳送端維護復原記分板,具體重傳順序與壅塞反應仍由傳送端演算法決定。
若 B 和 D 都遺失,只靠一次快速重傳只能先修 B;沒有 SACK 的經典復原較難判斷 D 是否已到。SACK 會報告 C 與 E 的非連續區塊,讓傳送端更精確地定位兩個缺口,避免連已暫存的 C、E 都一起重傳。
第四步:解釋 RTO 為何仍不可缺少
快速重傳依賴 ACK 回饋。若只傳送 A、B,而 B 是尾段,B 遺失後沒有 C、D、E 觸發重複 ACK;若回程 ACK 也中斷,傳送端同樣收不到三個重複 ACK。此時重傳計時器提供最後保障。
RFC 6298 維護平滑往返時間 SRTT、往返時間變異 RTTVAR 和時鐘粒度 G:
首次 RTT 樣本為 R:
SRTT = R
RTTVAR = R / 2
RTO = SRTT + max(G, 4 * RTTVAR)
後續樣本 R':
RTTVAR = (1 - 1/4) * RTTVAR + 1/4 * abs(SRTT - R')
SRTT = (1 - 1/8) * SRTT + 1/8 * R'
RTO = SRTT + max(G, 4 * RTTVAR)例如首次樣本 R=120 ms,且 G 不超過 240 ms,則 SRTT=120 ms、RTTVAR=60 ms,公式原值是 360 ms。RFC 6298 建議把小於 1 秒的 RTO 向上取到 1 秒;還沒有 RTT 樣本時也建議初始 RTO 為 1 秒。真實核心可能有更新演算法與實作差異,因此不能只因「不是剛好 1 秒」就否定逾時重傳。
計時器到期後,傳送端重傳最早的未確認資料,並把 RTO 加倍後重新計時。指數退避可避免在路徑持續壅塞或中斷時反覆注入資料。對重傳段直接取 RTT 會出現「這個 ACK 確認原段還是重傳段」的歧義;沒有 timestamp 消除歧義時,應遵守 Karn 演算法,不用該樣本更新 RTT。
第五步:把可靠性復原與壅塞反應分開
重傳回答「如何補回遺失位元組」,壅塞控制回答「之後允許多少資料在途」。兩者由同一遺失訊號觸發,但不能混為一談。
RFC 5681 的經典處理把 RTO 視為較嚴重的訊號:ssthresh 不高於 max(FlightSize/2, 2*SMSS),cwnd 降到不超過一個滿尺寸段,再從慢啟動復原。三個重複 ACK 表示後續段仍在抵達、ACK 時鐘尚存,傳送端進入快速重傳與快速復原,縮小視窗但不執行同樣強烈的 RTO 重置。
還要區分流量控制。接收端通告的 rwnd 保護接收緩衝區,傳送端的 cwnd 保護網路,實際可傳送上限同時受兩者限制。SACK 描述接收狀態,不會放大 rwnd 或 cwnd。
第六步:補上現代 RACK-TLP 的邊界
固定三個重複 ACK 對短流、尾端遺失、重傳再次遺失和較強重排並不理想。RFC 8985 將 RACK-TLP 推薦為傳統重複 ACK 計數的替代方案:RACK 結合每段最近傳送時間、RTT 與 SACK 回饋,以時間順序推斷較早傳送的段是否遺失;TLP 在 ACK 回饋稀少的尾端送出探測,盡量在 RTO 前恢復 ACK 時鐘。
因此,面試中應先準確解釋經典機制,再補一句實作邊界:真實封包擷取可能在不足三個重複 ACK 時出現復原,也可能看見尾端探測;應核對協定堆疊演算法和版本。把所有實作都描述為「嚴格等三個重複 ACK,否則一定等 RTO」會把課本模型誤當完整現況。
第七步:用封包證據驗證,不只看重傳次數
驗證時至少建立一條可對齊的時間線:
- 在傳送端與接收端同時擷取,確認原段在哪一側消失,排除只在單一觀察點不可見。
- 對齊
SEQ、LEN、累積ACK和 SACK 區塊,確認是否真的存在位元組缺口。 - 從上一個新 ACK 之後開始計算重複 ACK,不能把基準 ACK 算進去。
- 比較重傳與第三個重複 ACK 或 RTO 的相對時間,並核對壅塞視窗、RTT 和協定堆疊演算法。
- 結合應用延遲與介面遺失、重排、佇列指標,避免只憑一個 Wireshark 分析標籤判定根因。
Wireshark 的 tcp.analysis.fastretransmission、tcp.analysis.retransmission 和 tcp.analysis.duplicateack 是分析器依目前擷取內容推斷的結果,不是 TCP 標頭中的旗標。TSO、GRO 等卸載也可能讓主機擷取到的資料段邊界不同於線上封包。重要結論應由雙端序號空間與時間證據交叉確認。
高品質示範回答
「我先按位元組序號推導。A 的 SEQ=1000、長度 1000,所以收到 A 後,接收端下一個期待位元組是 2000,回覆的新 ACK 是 2000。B 涵蓋 2000 到 2999 且遺失。C、D、E 雖然分別涵蓋 3000 到 5999 並已抵達,但它們無法填上 2000 的缺口,因此累積 ACK 一直停在 2000。每個亂序段會產生一個重複 ACK;若交握協商了 SACK,接收端還會逐步報告已暫存的 3000 到 6000。
按照 RFC 5681 的經典演算法,C、D、E 造成三個重複 ACK 2000。第三個抵達後,傳送端快速重傳 B,不等 RTO。B 抵達時,接收緩衝區中的資料變成連續區間,累積 ACK 一次跳到 6000。這裡最容易算錯的是把 A 後面的第一個 ACK 2000 當成重複 ACK;它其實是推進確認邊界的新 ACK。
如果遺失的是尾段或在途量不足,就沒有三個後續段製造重複 ACK,最後保障是 RTO。RTO 由平滑 RTT 加四倍 RTT 變異估算,RFC 6298 也規定逾時後指數退避。RTO 通常伴隨比快速復原更強的壅塞視窗縮減,因為 ACK 時鐘可能已消失。SACK 的作用是告訴傳送端哪些非連續高序位元組已收到,尤其有利於多個缺口;它不會自行推進累積 ACK,也不是壅塞控制演算法。
真實系統還可能使用 RACK-TLP,依傳送時間和 SACK 回饋辨識遺失並探測尾端,所以三個重複 ACK 是必須掌握的經典推導,不是所有封包擷取唯一可能的觸發器。排查時我會在雙端對齊序號、長度、累積 ACK、SACK 區塊和重傳時間,核對協定堆疊與卸載設定,再區分真實遺失、重排、ACK 路徑問題和分析器誤判。」
常見錯誤
- 把 ACK 當成封包編號 → TCP 確認的是連續位元組空間 → 用
SEQ + LEN推導下一個期待位元組。 - 把 A 後的 ACK 2000 算成第一個重複 ACK → 它首次推進累積確認 → 從其後相同 ACK 號且未推進邊界的 ACK 開始計數。
- 說收到 C 後回覆 ACK 4000 → B 的位元組缺口仍存在 → 累積 ACK 維持 2000,並以 SACK 報告 C。
- 說 SACK 取代累積 ACK → TCP 仍以累積 ACK 推進連續邊界 → 把 SACK 當成額外的非連續區塊資訊。
- 說三個重複 ACK 證明遺失 → 重排和複製也能產生相同訊號 → 稱它為經典遺失啟發式,並核對時間線。
- 說任何遺失都能快速重傳 → 尾端或小在途量可能沒有足夠重複 ACK → 保留 RTO,並說明 RACK-TLP 的改進。
- 把重傳與壅塞控制當成一件事 → 補資料與限制傳送速率處理不同問題 → 分別說明復原動作、
cwnd與rwnd。 - 把 Wireshark 標籤當成線上的協定位元 → 標籤由擷取內容推斷且可能受卸載影響 → 用雙端序號空間和時序交叉驗證。
追問及應對
追問一:如果只傳送兩段,第二段遺失,會發生快速重傳嗎?
經典路徑不會,因為沒有更高序資料抵達接收端,也就沒有三個重複 ACK。傳送端通常等待 RTO;使用 RACK-TLP 的協定堆疊可能送出尾端探測來爭取 ACK 回饋,但探測失敗後仍需 RTO 兜底。
追問二:如果 C 只是重排,B 稍後自行抵達,會怎樣?
接收 C 會產生重複 ACK 2000 與對應 SACK。若 B 在達到遺失門檻前抵達,累積 ACK 會前進並避免重傳;若重排夠深而先觸發經典三個重複 ACK,可能出現偽快速重傳。RACK 使用時間型重排視窗,降低固定封包數門檻在這類路徑上的誤判。
追問三:B 和 D 同時遺失,為什麼 SACK 更有價值?
接收端可以報告已收到的 C 與 E 區塊,傳送端據此維護缺口記分板,跳過已 SACK 的範圍並復原 B、D。沒有 SACK 時,累積 ACK 只暴露最左缺口,經典 Reno 對一個視窗內多個遺失的復原資訊較少,可能需要部分 ACK 或最後等到 RTO。
追問四:首次 RTT 樣本是 120 ms,為什麼不能把 RTO 直接設為 120 ms?
RTT 會受排隊與路徑變化影響。RFC 6298 首次將 RTTVAR 設為 R/2;在 G 不超過 4 × RTTVAR 時,原始 RTO 為 R + 4 × R/2 = 3R。本例是 360 ms,並建議向上取到至少 1 秒。直接使用單次 RTT 會讓輕微抖動觸發偽逾時和無謂重傳。
追問五:封包擷取只看到重傳,沒有三個重複 ACK,能否認定是 RTO?
不能。擷取可能漏掉回程 ACK,觀察點可能位於卸載前後,協定堆疊也可能使用 RACK-TLP 或其他復原演算法。先比較重傳與上一個 ACK 的時間、檢查 SACK 與尾端探測、核對傳送端核心狀態,再用接收端擷取補齊證據。
追問六:為什麼逾時後的壅塞反應通常比快速復原更強?
三個重複 ACK 表示後續段仍離開網路並抵達接收端,ACK 時鐘尚在;RTO 則可能表示整批在途資料或回饋路徑都沒有進展。經典 RFC 5681 因此在逾時後把 cwnd 降到不超過一個滿尺寸段並重新慢啟動,而快速復原保留一個縮小後的傳送視窗。