更新紀錄(3 筆,最後更新 2026年09月04日)
本文的修改紀錄。已保存的更新前版本,可透過附有 DOI 的永久連結閱讀。
- 已將繁體中文版改寫為日文原文的完整翻譯。先前的繁體中文版只譯出日文原文的一部分,遺漏了章節、表格、Mermaid 圖、圖說與 FAQ。本次依日文原文將這些內容全部補回,並新增本文的知識地圖章節。技術主張與日文版一致。 查看更新前的版本 (DOI: 10.5281/zenodo.22279330)
- 補上了日文原文中已有的諮詢引導(consultation_services)。內文沒有改動。
- 修正了參考連結等處含有豎線(管線)符號的行被算繪為表格、導致連結無法點擊的顯示錯誤。內文內容未變動。
- 初次發布
引用本文(DOI: 10.5281/zenodo.21616251)
本文保存於 Zenodo。以下同時提供一律指向最新版本的 DOI,以及固定於您正在閱讀版本的 DOI。
Go Komura(2026)。〈TCP 重送導致工業相機通訊停頓的原因與釐清方法〉。小村軟體有限公司。https://doi.org/10.5281/zenodo.21616251 https://comcomponent.com/zh-TW/blog/2026/03/11/001-tcp-retransmission-rfc1323-industrial-camera/
- DOI(最新版本)
- 10.5281/zenodo.21616251
- DOI(此版本)
- 10.5281/zenodo.22297084
在工業相機或設備控制的通訊中,最棘手的現象是平均很快,卻 偶爾停上幾秒。重現率很低,平時什麼事也沒有,於是 UI、執行緒、GC、相機 SDK、NIC、交換器,全部看起來都有一點嫌疑。
這次要談的,是控制工業相機的應用程式與主機之間的 TCP 通訊,偶爾會有幾秒完全不動的現象。實際查下去,真正的原因並不是應用程式停住,而是 封包遺失所引起的 TCP 重送等待。再進一步,啟用 RFC1323 系的 timestamp 機制(以目前的標準整理來說是 RFC 7323)之後,這套系統的等待時間就被壓到了最低。
設備名稱、組態與數值都做過一般化,但整套思路可以直接用在實務上。
目錄
- 先講結論(一句話)
- 症狀看起來的樣子
- 2.1. 應用程式還活著,只有回應停了幾秒
- 2.2. 頻率低,光看日誌很難發現
- 實際發生了什麼(圖解)
- 3.1. 從封包遺失進入重送等待
- 3.2. 秒級的停頓與 RTO 的形狀吻合
- 調查時檢視的重點
- 4.1. 先排除應用程式內部的停頓原因
- 4.2. 用封包擷取確認重送
- 4.3. 查看實際協商到的 TCP 選項
- RFC1323 timestamp 有效的理由
- 5.1. timestamp 是為了 RTTM 與 PAWS
- 5.2. 可以消除重送時 RTT 量測的模糊
- 5.3. 本案例能壓縮等待時間的理由
- 實際採取的對策
- 6.1. 啟用 timestamp
- 6.2. 在 SYN / SYN-ACK 上確認 TSopt
- 6.3. 這樣做仍然沒效時該看哪裡
- Wireshark 上的觀察重點
- 大致的取捨
- 總結
- 參考資料
圖中實線表示始終成立的關係,虛線表示附帶條件的關係(成立條件寫在詳細頁面中各關係的說明中)。關係的完整清單(共 19 條,附依據與可信度)以及主要概念的定義,彙整在知識地圖詳細頁面(日文)。資料:JSON-LD / Turtle
1. 先講結論(一句話)
- 偶爾停上幾秒的 TCP 通訊,真正的原因往往不是應用程式停住,而是 封包遺失之後的重送等待
- 如果封包擷取看得到
Retransmission與明顯的時間差,而且停頓時間與 RTO 的等待方式吻合,嫌疑就相當大 - TCP timestamps option 是為了 RTT 量測與 PAWS 而存在的機制,同時也能消除重送時 RTT 量測的模糊
- 在這個案例中,啟用 RFC1323 系的 timestamp 機制之後,RTO 估計停留在過時且保守狀態的時間變短,秒級的停頓被壓到了最低
- 不過,這 不是消除遺失本身的魔法。物理層、NIC、交換器、中介設備、驅動程式與緩衝區設計,仍然需要另外重新檢視
簡單說,如果「偶爾停上幾秒」的真面目是 TCP 內部的等待時間,那麼只在應用程式的重試上使力就會打偏。先看 wire,先確定是不是重送等待,反而比較快。
flowchart TB
accTitle: 本文結論的流程
accDescr: 說明偶爾停上幾秒的 TCP 通訊要先看 wire 確定是不是重送等待,若是重送等待則 timestamps 有機會壓縮等待時間,但封包遺失來源仍須另外調查的圖。
sym["偶爾停上幾秒的 TCP 通訊"] --> wire["先看 wire"]
wire --> conf["確定是不是重送等待"]
conf -.-> ts["timestamps 有機會壓縮等待"]
conf -.-> loss["遺失來源要另外調查"]
圖 1: 在應用程式的重試上使力之前,先用 wire 鎖定「到底卡在哪裡等」。
接下來 TCP 的縮寫會接連出現。為了不以網路為專長的 Windows 開發者,先在這裡整理一次。只要先掌握這些,後面應該就不會卡住。
| 術語 | 全稱 | 意義 |
|---|---|---|
| ACK | Acknowledgment | 確認回應。用來告訴對方「我收到這裡為止了」的機制 |
| RTT | Round-Trip Time | 往返時間。從送出到 ACK 回來為止的時間 |
| RTO | Retransmission Timeout | 重送計時器。在這段時間內等不到 ACK 就重送。數值由 RTT 的估計值算出 |
| RTTM | Round-Trip Time Measurement | RTT 的量測。這是 timestamp 機制的目的之一 |
| PAWS | Protect Against Wrapped Sequences | 針對序號繞回一圈這種事故的防護。這是 timestamp 機制的另一個目的 |
| TSopt | TCP Timestamps Option | 掛在 TCP 標頭上的選項之一。種類 8、長度 10 位元組,會攜帶下面兩個值 |
| TSval | Timestamp Value | 送出端填入的值,也就是自己這一端的時鐘值 |
| TSecr | Timestamp Echo Reply | 把收到的 TSval 原樣送回的欄位。有了它就知道「這是在回應哪一次送出的 ACK」 |
| SACK | Selective Acknowledgment | 接收端逐段告知「自己收到了哪些範圍」的機制 |
| Dup ACK | Duplicate ACK | 指向同一個序號的 ACK 重複回來。這是中間有缺漏的訊號 |
| fast retransmit | — | Dup ACK 累積到一定數量後,不等 RTO 到期就重送的機制。Windows 的預設是「收到 3 個指向同一序號的 ACK(最初的 1 個加上重複的 2 個)」 |
| Karn 演算法 | — | 規定不可以從重送過的區段取 RTT 樣本。因為分不出那是在回應哪一次送出 |
2. 症狀看起來的樣子
2.1. 應用程式還活著,只有回應停了幾秒
一開始最麻煩的是,整個應用程式看起來並沒有凍住。
- UI 沒有完全死掉
- 處理程序也沒有當掉
- CPU 也沒有一直滿載
- 只有相機控制命令的回應會 偶爾 缺個幾秒
這種症狀很難跟應用程式內部的死結或無窮迴圈分辨開來。而且在設備控制上,一次數秒的停頓就會直接給人整條產線停線的印象。就算平均值很漂亮,第一線的體感還是會差很多。
2.2. 頻率低,光看日誌很難發現
這類缺陷麻煩的地方在於發生頻率很低。可能一小時一次、半天一次,或是只有幾個條件疊在一起時才出現。
只靠日誌追,大概會變成這樣。
- 應用程式日誌上停在「送出了」「沒回來」
- 接收端日誌看起來是「什麼都沒收到」
- 剛好同一個時段還發生了別的事件,嫌疑就分散掉了
這種時候,如果只想用應用程式日誌去還原因果關係,很容易就陷入泥沼。往下降一層到通訊層來看,反而比較快。
flowchart TB
accTitle: 光看日誌無法鎖定原因的結構
accDescr: 說明應用程式日誌停在送出了卻沒回來,接收端日誌看起來什麼都沒收到,加上同一時段的其他事件讓嫌疑分散,所以不要只用應用程式日誌還原因果,往下降到通訊層比較快的圖。
l1["應用程式日誌:送出了卻沒回來"] --> lost["嫌疑分散而陷入泥沼"]
l2["接收端日誌:什麼都沒收到"] --> lost
l3["同一時段的其他事件"] -.-> lost
lost --> down["往下降一層到通訊層"]
圖 2: 低頻率的停頓只靠日誌追會陷入泥沼。用封包看比較快。
3. 實際發生了什麼(圖解)
3.1. 從封包遺失進入重送等待
這次的來龍去脈很單純。封包在途中某處被丟掉,送出端等 ACK,等不到就等 RTO 到期後才重送。
sequenceDiagram
participant Host as 主機應用程式
participant Net as 網路
participant Cam as 相機端
Host->>Net: 控制命令 (Seq=N)
Note over Net: 在這裡遺失
Note over Host: 等不到 ACK 只好繼續等
Note over Host: 這次要求的恢復要先等 RTO
Host->>Net: 重送控制命令
Net->>Cam: 重送封包送達
Cam-->>Net: ACK
Net-->>Host: ACK
Note over Host: 通訊在這裡恢復
圖 3: 封包被丟掉時 ACK 就不會回來,要等 RTO 到期後才重送。這段時間在應用程式眼中就是「數秒的停頓」。
從應用程式的角度看像是「停了幾秒」,但以 TCP 的角度來說,只是「ACK 還沒回來,所以在等重送計時器到期」而已。雖然不起眼,這種停頓方式其實相當常見。
這次的控制通訊多半是小型的 request/response,一次來回並不會有大量還沒被 ACK 的資料在飛。因此在累積到足夠的 duplicate ACK 去觸發 fast retransmit 之前,RTO 等待 就很容易先浮上檯面。
flowchart TB
accTitle: 控制通訊容易讓 RTO 等待浮上檯面的理由
accDescr: 說明以小型 request/response 為主的控制通訊中未被 ACK 的資料很少,duplicate ACK 累積不足因此難以觸發 fast retransmit,結果 RTO 等待就浮上檯面的圖。
small["以小型 request/response 為主"] --> few["未被 ACK 的資料很少"]
few --> nodup["Dup ACK 累積不到足夠數量"]
nodup --> nofr["難以觸發 fast retransmit"]
nofr --> rto["RTO 等待浮上檯面"]
圖 4: 和大量資料流動的通訊不同,控制通訊發生遺失時容易變成等 RTO 到期。
3.2. 秒級的停頓與 RTO 的形狀吻合
TCP 的重送等待雖然有實作差異,大體上都採取保守的等待方式。RFC 6298 中,初始 RTO 以 1 秒為基準,計算結果若小於 1 秒就向上取到 1 秒,發生逾時就加倍。
flowchart LR
A[封包遺失] --> B[等不到 ACK]
B --> C[等待 RTO]
C --> D[重送]
D --> E{ACK 回來了嗎?}
E -- 是 --> F[通訊恢復]
E -- 否 --> G[把 RTO 加倍]
G --> C
圖 5: RTO 每次等不到 ACK 就加倍。1 秒、2 秒、4 秒這種等法就是從這個形狀來的。
所以,即使是希望在幾百毫秒內結束的場合,條件一差也可能看起來像 1 秒、2 秒、4 秒這樣的等待。這次的「偶爾停上幾秒」,跟這個形狀吻合得相當自然。
4. 調查時檢視的重點
4.1. 先排除應用程式內部的停頓原因
沒有一開始就認定是 TCP,而是先把應用程式端的典型原因排除掉。
| 檢查的項目 | 檢查的理由 | 本次的結論 |
|---|---|---|
| UI 執行緒/工作者執行緒 | 檢查是否停止回應或互相等待 | 不是主因 |
| CPU 使用率 | 檢查高負載造成的處理延遲 | 停頓時也不是滿載狀態 |
| GC/記憶體壓力 | 檢查是否有暫停 | 停頓時間的形狀對不上 |
| 相機 SDK 呼叫 | 檢查 SDK 內部的等待 | 與 wire 上的延遲不一致 |
| 封包擷取 | 檢查通訊層的重送 | 在這裡看見了原因的線索 |
這裡重要的是,不要只憑應用程式日誌的時刻就認定原因。在設備控制的應用程式中,上層的等待往往只是把下層的等待照樣映出來而已。
flowchart TB
accTitle: 先排除應用程式內部原因的釐清順序
accDescr: 說明不要一開始就認定是 TCP,先排除執行緒、CPU、GC、相機 SDK 這些應用程式內部的典型停頓原因,再用封包擷取確認通訊層重送的釐清順序的圖。
app["先排除執行緒、CPU、GC、SDK"] --> cap["用封包擷取檢視通訊層"]
cap --> found["重送的線索浮現出來"]
app -.-> warn["不要只憑日誌的時刻認定原因"]
圖 6: 不要一開始就認定。先排除應用程式內部的典型原因,再往 wire 下去。
4.2. 用封包擷取確認重送
抓下封包擷取之後,可以看到停頓的時段出現 TCP Retransmission,而且緊接在它之前的 ACK 根本沒有回來。
該看的地方大致是這幾個。
- 是否出現相同
Seq的重送 - 到重送為止的時間差是否與停頓時間一致
- 看起來是不是等 RTO 到期,而不是
Dup ACK或Fast Retransmission - 出問題的連線是否每次都落在同一個
tcp.stream
這幾點對上之後,「應用程式停住」的說法就會退位,「TCP 在等重送」的可能性會相當高。
flowchart TB
accTitle: 確定是重送等待的檢查點
accDescr: 說明相同 Seq 是否出現重送、到重送為止的時間差是否與停頓時間一致、看起來是否為等 RTO 到期這三點對上之後,就能判斷不是應用程式停住而是 TCP 在等重送的圖。
c1["相同 Seq 是否出現重送"] --> conc["TCP 正在等重送"]
c2["時間差是否與停頓時間一致"] --> conc
c3["看起來是否為等 RTO 到期"] --> conc
conc -.-> not["並不是應用程式停住"]
圖 7: 這 3 點都對上,「TCP 在等重送」的可能性就遠大於「應用程式停住」。
4.3. 查看實際協商到的 TCP 選項
接著看的是連線建立時的 SYN / SYN-ACK。timestamp 是在 TCP 連線的三向交握階段協商的,所以這裡如果沒有出現 TSopt,那條連線就不會用到它。
sequenceDiagram
participant Host as 主機
participant Cam as 相機端
Host->>Cam: SYN + TSopt ?
Cam-->>Host: SYN/ACK + TSopt ?
Host->>Cam: ACK
Note over Host,Cam: 在這裡協商成功,之後的區段才用得到 TSopt
圖 8: timestamp 是在三向交握中協商的。SYN / SYN-ACK 沒有 TSopt,那條連線就不會使用它。
不看這裡就只動 OS 設定的話,就會出現「明明已經啟用了卻沒有效」這種同樣不起眼的意外。設定值比不上 wire 上的事實有力。
5. RFC1323 timestamp 有效的理由
實務上還留著「RFC1323 的 timestamp」這種叫法,但現行的標準整理是 RFC 7323。本文沿用慣稱寫成 RFC1323,意思指的就是 TCP timestamps option。
5.1. timestamp 是為了 RTTM 與 PAWS
TCP 的 timestamps option 主要用在兩個目的上。
- RTTM(Round-Trip Time Measurement)
- PAWS(Protect Against Wrapped Sequences)
這次派上用場的是 RTTM 這一邊。送出的區段所帶的 TSval,會由對方在 ACK 的 TSecr 中送回,送出端因此更容易把 RTT 量得更細、更準。
flowchart TB
accTitle: timestamps option 的兩個目的
accDescr: 說明 TCP 的 timestamps option 用在 RTTM 也就是往返時間的量測,以及 PAWS 也就是序號繞回一圈的防護這兩個目的上,而本案例派上用場的是 RTTM 這一邊的圖。
tso["timestamps option"] --> rttm["RTTM(往返時間的量測)"]
tso --> paws["PAWS(序號繞回一圈的防護)"]
rttm --> hit["這次派上用場的是這一邊"]
rttm -.-> how["把 TSval 用 ACK 的 TSecr 送回來量"]
圖 9: timestamp 的目的是 RTTM 與 PAWS 兩個。這個案例派上用場的是 RTT 量測那一邊。
5.2. 可以消除重送時 RTT 量測的模糊
一旦發生重送,沒有 timestamp 就會分不清楚「這個 ACK 是在回應最初的那次送出,還是在回應重送」。這正是 Karn 演算法所在意的地方。
RFC 6298 規定,重送過的區段不可以拿來取 RTT 樣本。理由是分不出那是在回應哪一次送出。不過只要有 timestamps option,就能把這個模糊拿掉。因為看 ACK 帶回來的 TSecr,就能辨認出是帶著哪一個 TSval 的區段送達了。
sequenceDiagram
participant Host as 送出端
participant Cam as 接收端
Host->>Cam: Seq=N, TSval=1000
Note over Host,Cam: 這個區段遺失
Note over Host: 等不到 ACK 只好繼續等
Host->>Cam: 重送 Seq=N, TSval=2000
Cam-->>Host: ACK, TSecr=2000
Note over Host: 可以判別是在回應哪一次送出
圖 10: 看 TSecr 就能判別那個 ACK 是在回應最初的送出還是重送。
這裡就是這次改善的核心。
5.3. 本案例能壓縮等待時間的理由
這個案例中,封包遺失時不時就會發生,每發生一次,RTT/RTO 的估計就容易往保守的方向偏。啟用 timestamp 之後,即使在包含重送的場面,RTT 的估算也比較容易更新,於是 RTO 估計維持在過時狀態一路膨脹的時間就被壓下來了。
這裡很容易跳過推論,所以再拆解得細一點。
首先,RTO 的下限本身並不會因為有沒有 timestamp 而改變。 RFC 6298 規定,算出來的 RTO 若小於 1 秒就應該向上取到 1 秒。實作也可能自己另有下限,在 Windows 上就是 Get-NetTCPSetting 會列出的 MinRtoMs(以 10 毫秒為刻度,取 20 至 300 毫秒的範圍)。也就是說,並不是「加了 timestamp 所以下限降低了」。
真正發揮作用的是更前面一步。RFC 6298 定下了以下 3 件事。
- 重送計時器每到期一次,就把 RTO 乘以 2(指數退避)
- 退避後的 RTO,會 在取得新的 RTT 量測值時回到原本的水準
- 那個「新的 RTT 量測值」,只有在送出未經重送的資料並被 ACK 時才拿得到
再加上 Karn 演算法,重送過的區段不可以拿來取 RTT 樣本。不過,使用 timestamps option 時這條限制就解除了。 如同 5.2 所述,看 TSecr 就能判別那是在回應哪一次送出。
把這些排在一起,脈絡就清楚了。在遺失零星持續發生的系統裡,沒有 timestamp 時第 2 點與第 3 點的條件很難同時成立,加倍後的 RTO 要靠實測回到原本水準所需的時間就會拖得很長。 在那個狀態下如果又碰到下一次遺失,等待就不是從 1 秒開始,而是從 2 秒、4 秒開始。有 timestamp 的話,連含有重送的區間也能重新量測,於是這段「回不去的時間」變短了 ── 這就是這次發揮作用的路徑。
flowchart TB
accTitle: 加倍後的 RTO 回復的條件與 timestamp 發揮作用的方式
accDescr: 說明每次逾時 RTO 都會加倍,取得新的 RTT 量測值時才會回到原本水準,但那個量測值只能從未經重送的資料的 ACK 取得,因此用 timestamps 解除這條限制後加倍的 RTO 回不去的時間就縮短的圖。
backoff["每次逾時 RTO 就加倍"] --> ret["靠新的 RTT 量測回到原本水準"]
ret -.-> limit["量測只能來自未重送資料的 ACK"]
ts2["用 timestamps 連重送區間也能量"] --> often["重新量測的機會增加"]
often --> short["加倍後的 RTO 回不去的時間縮短"]
圖 11: 核心不在下限降低,而是加倍後的 RTO 能靠實測更快回到原本水準。
老實寫在這裡:本文並沒有量到改善之後 RTO 的實測值。 已知的只到「秒級的停頓減少到不再造成第一線困擾的程度」為止。若想用數字呈現效果,最近的路徑是用第 7 章的顯示過濾器與 Time delta from previous displayed packet 欄,把到重送為止的時間差分布做成 before/after 的統計。
換句話說,這次做的並不是讓 TCP 變快的魔法,而是 減少 TCP 不必要地觀望過久的時間。
當然,RFC 7323 也沒有說「RTT 樣本變多就什麼都能漂亮解決」。它對 RTO 最佳化的助益確實有其侷限。不過,能消除重送時的模糊 這一點,在這次這種系統上有時會直接見效。
也有要注意的地方。
- 這部分有一些取決於 TCP stack 的實作
- 光靠 timestamp 並不會讓封包遺失本身消失
- 物理層或中介設備有問題的話,根本原因在別的地方
- SACK、NIC 驅動程式、卸載(offload)設定、交換器端的問題,也建議另外檢視
不過,像這次「遺失不是零,但真正痛的是秒級的等待」這種系統,它有時效果相當顯著。
6. 實際採取的對策
6.1. 啟用 timestamp
對策上,是讓連線兩端都處於可以協商 timestamps option 的狀態。在 Windows 系統上有時會被當成 RFC 1323 選項來處理,會受到 OS 設定與網路設定的影響。
把具體步驟寫下來。首先,查看目前是什麼狀態。
netsh interface tcp show global
輸出當中會有 RFC 1323 timestamp 這個項目,看那裡是不是已啟用。若要啟用,就在具備系統管理員權限的命令提示字元中這樣下。
netsh interface tcp set global timestamps=enabled
timestamps 可以指定的值有 disabled/enabled/default 三種,default 是回到系統預設的指定方式。
要從 PowerShell 查看與設定的話是這樣。
Get-NetTCPSetting | Select-Object SettingName, Timestamps, MinRtoMs, InitialRtoMs
Set-NetTCPSetting -SettingName InternetCustom -Timestamps Enabled
Timestamps 可以指定的值是 Enabled/Disabled。可以變更的 SettingName 會隨 Windows 版本而不同,所以不要一上來就打 Set-,先用 Get-NetTCPSetting 查看實際存在的名稱。傳入不存在的名稱就會在那裡停住。
如果是在舊環境直接查看登錄檔,就是下面這個登錄機碼下的 Tcp1323Opts(REG_DWORD)。
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
值的意義是,0 為停用 RFC 1323 選項,1 為只啟用 window scaling,2 為只啟用 timestamps,3 為兩者都啟用。記成位元 0 對應 window scaling,位元 1 對應 timestamps,就不會弄混。
設定時有 3 個要注意的地方。
- 只對新的連線生效。 timestamp 是在三向交握中協商的,所以對已經建立好的連線沒有作用。必須讓設備端重新建立連線
- 兩端都要支援才會成立。 只在主機端啟用,如果相機端沒有在 SYN/ACK 回傳 TSopt,那條連線就不會用到它
- 這個設定也會改變連線的性質。 timestamp 會讓 TCP 標頭變大,因此一個區段能載的資料會少一些
不過在實務上,比起「設定畫面顯示為已啟用」,「SYN / SYN-ACK 的實際封包帶著 TSopt」更重要。這點真的是如此。
flowchart TB
accTitle: 設定 timestamp 時的 3 個注意事項
accDescr: 說明 timestamp 的設定因為在三向交握中協商所以只對新連線生效、兩端都要支援才會成立、標頭變大使一個區段能載的資料略減這三個注意事項的圖。
care["設定時的 3 個注意事項"] --> n1["只對新連線生效"]
care --> n2["需要兩端都支援"]
care --> n3["標頭變大資料略減"]
n1 -.-> re["讓設備端重新建立連線"]
圖 12: 設定完並不算結束。還要重新建立連線,並確認對方是否支援。
6.2. 在 SYN / SYN-ACK 上確認 TSopt
啟用之後確認了 3 點。
- 出問題的連線,SYN 上是否帶有 TSopt
- SYN/ACK 那一側是否也回傳了 TSopt
- 之後的資料區段與 ACK 上,TSopt 是否持續帶著
確認到這裡,才能說「那條連線實際上有在使用 timestamps」。
6.3. 這樣做仍然沒效時該看哪裡
即使啟用了 timestamp,遇到下列情況時改善的幅度仍可能有限。
- 遺失率本身就很高
- 中介設備會破壞、丟棄或改寫 TCP option
- NIC/驅動程式/卸載相關另有其他問題
- 應用程式把整體流程都掛在一個同步呼叫上,一次等待看起來就像全面停頓
- 真正的主因其實不在 TCP,而是相機端處理停住或設備內部佇列塞住
因此,對策照這個順序推進會比較清楚。
- 先在 wire 上確認是重送等待
- 查看 TSopt 有沒有協商成功
- 啟用 timestamps,看改善的差異
- 若問題仍在,再分別追究遺失來源與應用程式設計
flowchart TB
accTitle: 推進對策的順序
accDescr: 說明先在 wire 上確認是重送等待,接著查看 TSopt 有沒有協商,再啟用 timestamps 觀察改善差異,若問題仍在就分別追究遺失來源與應用程式設計的對策順序的圖。
s1["在 wire 上確認重送等待"] --> s2["查看 TSopt 有沒有協商"]
s2 --> s3["啟用 timestamps 觀察差異"]
s3 --> s4["仍在的話分別追究遺失來源與設計"]
圖 13: 依照確認、協商、啟用、追查剩餘問題的順序推進,比較容易釐清沒效的原因。
7. Wireshark 上的觀察重點
先列出釐清問題時好用的顯示過濾器。
tcp.stream eq <目標串流>
tcp.analysis.retransmission
tcp.analysis.fast_retransmission
tcp.analysis.lost_segment
tcp.options.timestamp.tsval
tcp.options.timestamp.tsecr
看的方式也有幾個訣竅。
- 用
tcp.stream只留下目標連線 - 把
Time delta from previous displayed packet顯示出來,直接看停住了幾秒 - 確認問題發生的瞬間有沒有出現
Retransmission - 確認連線開始時的 SYN / SYN-ACK 有沒有協商 TSopt
- 查看 ACK 有沒有帶回
TSecr
畫面看起來是什麼樣子,也用文字寫下來。這個症狀習慣之後一眼就能認出來。
| 觀察的位置 | 看起來是什麼樣子 |
|---|---|
| 封包清單的 Info 欄 | 重送的封包會標上 [TCP Retransmission] |
| 該列的顏色 | 因為符合 Wireshark 預設的著色規則「Bad TCP」,只有那一列會變成黑底紅字。隨手掃過也撿得到 |
| 詳細窗格 | 選取該列後展開 Transmission Control Protocol,再打開其中的 SEQ/ACK analysis,就會顯示被判定為重送 |
| 時間差 | 把 Time delta from previous displayed packet 加成一欄,就能讀出該列距離前一個顯示的封包隔了幾秒。這裡會排出 1 秒、2 秒 |
| SYN 的 Options | 選取 SYN 那一列並展開 Options,看有沒有 Timestamps 這個項目,就知道有沒有 TSopt |
典型的排列方式是這樣。先是相同 Seq 的那一列在 1 秒後出現,再過 2 秒又出現一次,最後那一列之後緊接著 ACK 回來,通訊便從這裡恢復正常來回。只要這段「空白的數秒」與應用程式日誌上回應停住的時間一致,原因大致就確定了。
在把日誌與封包對照時,還要留意應用程式時間與擷取時間的基準差。這裡一旦錯開,就很容易把不相干的事件當成原因。
flowchart TB
accTitle: 在 Wireshark 上確定原因的流程
accDescr: 說明用 tcp.stream 只留下目標連線,用時間差的欄位直接看停住的秒數,若那段空白的數秒與應用程式日誌上回應停住的時間一致就大致確定原因的流程的圖。
filt["用 tcp.stream 只留下目標連線"] --> delta["用時間差的欄位看空白的秒數"]
delta --> re["確認相同 Seq 的重送並排出現"]
re --> match{"與應用程式的停頓時間一致?"}
match -->|"是"| fix["原因大致確定"]
match -->|"否"| other["懷疑時刻基準差或其他因素"]
圖 14: 篩選、看時間差、互相對照。用這 3 步確定「空白的數秒」的真面目。
8. 大致的取捨
| 症狀 | 先懷疑的對象 | 最先要做的事 |
|---|---|---|
| 偶爾停頓數秒 | TCP 的 RTO 等待 | 用封包確認重送與時間差 |
| 每次幾乎都在同一個時機停住 | 應用程式內部的等待、設備端處理、固定的逾時值 | 查看執行緒、SDK 呼叫與設備日誌 |
| 只有高負載時惡化 | CPU、GC、佇列塞住 | 查看 CPU、中斷、記憶體與佇列長度 |
| 大範圍的連線一起變差 | 物理層、交換器、中介設備 | 查看 NIC、網路線、連接埠統計與中介設備日誌 |
| 改了設定卻沒有變化 | TCP option 根本沒有協商成功 | 重新確認 SYN / SYN-ACK |
最後一列真的很常見。動了設定的滿足感,和 wire 上實際被採用的事實,是兩回事。
9. 總結
這次的重點:
- 「偶爾停上幾秒」有時不是應用程式停住,而是 TCP 的重送等待
- 停頓時間與 RTO 的等待方式吻合,而且看得到
Retransmission,方向就相當對 - TCP timestamps option 是 RTTM 與 PAWS 的機制,可以消除重送時 RTT 量測的模糊
- 在這個案例中,啟用 RFC1323 系的 timestamp 之後,RTO 停留在過度保守狀態的時間被壓下來了
想避免的做法:
- 只憑應用程式日誌就認定通訊停頓的原因
- 只看 OS 設定,不看實際封包
- 以為啟用 timestamp 就連遺失的原因也一併消失
實務上有效的做法:
- 先看 wire
- 確認重送與等待時間的形狀
- 確認 TSopt 的協商
- 改善之後,遺失來源與應用程式設計仍要另外追究
也就是說,這類缺陷「先鎖定卡在哪裡等」比「讓它變快」更優先。只要這一點沒有偏掉,調查就會縮短很多。
10. 參考資料
- RFC 1323 - TCP Extensions for High Performance
- RFC 7323 - TCP Extensions for High Performance
- RFC 5681 - TCP Congestion Control
- RFC 6298 - Computing TCP’s Retransmission Timer
- Description of Windows TCP features - Windows Server | Microsoft Learn
- Netsh commands for Interface Transmission Control Protocol - Microsoft Learn
- Set-NetTCPSetting - Microsoft Learn
- Get-NetTCPSetting - Microsoft Learn
- Wireshark User’s Guide - Time Display Formats And Time References
- Wireshark User’s Guide - Packet Colorization
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
用 Application Verifier 打造 Windows 異常情境測試基礎架構
本文梳理 Application Verifier 是什麼,並一併說明如何用 Handles、Heaps、Low Resource Simulation 與 !htrace 打造 Windows 的異常情境測試基礎架構。
工業相機長期運轉當機調查 - 控制代碼洩漏篇
本文以工業相機控制應用程式的案例,從找出控制代碼洩漏與日誌設計這兩個角度,梳理 Windows 應用程式長時間運轉後突然當掉時該怎麼看。
Time Travel Debugging ── 把長期運轉中無法重現的問題「錄下來」再倒回去
一個月才出現一次的問題,當機傾印只拍得到結果。本文說明如何用 WinDbg 的 Time Travel Debugging(TTD) 錄下執行並倒回,涵蓋 TTD.exe 的錄製設計、環形緩衝區、TTD.Calls 查詢,以及和傾印的分工。
從睡眠恢復就壞掉的應用程式 ── 電源事件的機制,以及耐得住恢復的業務應用程式寫法
打開筆電,業務應用程式的連線卻斷了──原因是設計從未把睡眠算進去。本文依一次資訊整理 WM_POWERBROADCAST 的通知流程、Modern Standby 的行為、斷線/重連設計、睡眠抑制,以及調查指令。
DllMain 與載入器鎖定 ── 「DLL 初始化時什麼都別做」真正的理由
為什麼不能在 DllMain 裡呼叫 LoadLibrary 或與其他執行緒同步?本文依據一次資料,從序列化所有 DLL 通知的載入器鎖定機制,一路說明到死結成立的典型情境、延遲初始化等正確設計,以及無回應的調查步驟。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
故障調查 & 長期運行故障
整理間歇性故障、通訊診斷、長期運行當機、失敗路徑測試基礎的主題頁面。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
故障調查 & 根本原因分析
本文談的是用封包與佐證資料釐清難以重現的通訊停頓,與缺陷調查、原因解析這個主題直接相關。
Windows 應用程式開發
以包含設備介接的 Windows 應用程式來說,也能延伸到從實作端重新檢視通訊設計與監控的諮詢。
常見問題
整理諮詢這個主題時常見的問題。
- 什麼是 TCP Retransmission(TCP 重送)?
- 這是當送出的封包等不到 ACK(確認回應)時,TCP 再次送出相同資料的機制。封包在途中某處被丟掉,送出端就會等 ACK,等不到時便等重送計時器(RTO)到期後才重送。從應用程式的角度看像是「停了幾秒」,但以 TCP 的角度來說只是「ACK 還沒回來,所以在等重送計時器到期」,這種停頓方式其實相當常見。在封包擷取中可以觀察到它被標示為 TCP Retransmission。
- 造成 TCP Retransmission 的原因是什麼?
- 直接的觸發點是封包遺失,因為等不到 ACK 才會發生重送。至於遺失的來源,必須分別檢視物理層(網路線與連接埠)、NIC 及其驅動程式與卸載設定,以及交換器或中介設備的問題。另外,啟用 TCP timestamps 之類的做法確實可以壓縮重送之後的等待時間,但那不是消除遺失本身的魔法,因此遺失來源仍要另外調查。也有中介設備破壞或丟棄 TCP 選項的情況,甚至有主因根本不在 TCP,而是設備端處理停住的情況。
- 為什麼 TCP 重送會讓通訊停上好幾秒?
- 因為 TCP 的重送計時器(RTO)採取保守的等待方式。RFC 6298 中初始 RTO 以 1 秒為基準,計算結果比它小也會向上取到 1 秒,而且每發生一次逾時就加倍。因此條件一差,就會出現 1 秒、2 秒、4 秒這樣的等待。在以小型 request/response 為主的控制通訊中,還沒累積到足夠的 duplicate ACK 去觸發 fast retransmit,RTO 等待就先浮上檯面,於是表現為偶發的數秒停頓。啟用 TCP timestamps option 之後,有機會消除重送時 RTT 量測的模糊,縮短 RTO 估計停留在過度保守狀態的時間。
- 要用 Wireshark 調查 TCP Retransmission 該怎麼做?
- 顯示過濾器可以用 tcp.analysis.retransmission、tcp.analysis.fast_retransmission、tcp.analysis.lost_segment 等等。先用 tcp.stream 只留下目標連線,再把 Time delta from previous displayed packet 顯示出來直接查看停頓了幾秒,然後看問題發生的瞬間有沒有出現 Retransmission、重送前的時間差是否與停頓時間一致。至於是否真的用上 TCP timestamps,則要看連線開始時的 SYN / SYN-ACK 有沒有協商 TSopt。比起設定畫面顯示為已啟用,實際封包上帶著 TSopt 這個事實更重要。