設計問題-透過具有 Winsock 的 TCP 傳送小型資料片段

當您需要透過 TCP 傳送小型資料封包時,您的 Winsock 應用程式的設計尤其重要。 這種設計不會考慮延遲認可、Nagle 演算法和 Winsock 緩衝的互動,可能會大幅影響效能。 本文將使用幾種案例分析討論這些問題。 它也會衍生一系列的建議,用以有效地從 Winsock 應用程式傳送小型的資料封包。

原始產品版本:  Winsock
原始 KB 編號:  214397

Background

當 Microsoft TCP 堆疊收到資料包時,200-毫秒延遲計時器會關閉。 當傳送 ACK 時,延遲計時器會重設,當接下來的封包接到時,將會啟動另一個200毫秒延遲。 為了提高 Internet 和內部網路應用程式的效率,TCP 堆疊會使用下列準則來決定何時針對接收的資料封包傳送一個 ACK:

  • 如果在延遲計時器到期之前接收第二封資料包,就會傳送 ACK。
  • 如果在接收第二封資料包之前,以與 ACK 相同的方向傳送資料,且延遲計時器到期,則會以資料段 piggybacked ACK,並立即傳送。
  • 當延遲計時器到期時,即會傳送 ACK。

為了避免在網路上 congest 小型資料封包,TCP 堆疊預設會啟用 Nagle 演算法,此演算法會從多個傳送呼叫中將小型資料緩衝區上傳,並延遲傳送,直到先前從遠端主機收到所傳送之資料包的 ACK 為止。 以下是 Nagle 演算法的兩個例外:

  • 如果堆疊合併的資料緩衝區大於最大傳輸單位(MTU),則會立即傳送完整大小的封包,而不會等候來自遠端主機的 ACK。 在乙太網路上,TCP/IP 的 MTU 為1460位元組。

  • TCP_NODELAY通訊端選項是用來停用 Nagle 演算法,使小型資料封包可以不經延遲傳送至遠端主機。

為了在應用層級優化效能,Winsock 會將應用程式傳送呼叫的資料緩衝區從應用程式傳送呼叫複製到 Winsock 內核緩衝區。 然後,堆疊會使用自己的試探法(如 Nagle 演算法)來決定何時實際將封包放在網路上。 您可以使用 SO_SNDBUF 選項(預設值為8k),變更分配給通訊端的 Winsock 內核緩衝區數量。 必要時,Winsock 可以緩衝超過 SO_SNDBUF 緩衝區大小。 在大多數情況下,應用程式中的 [傳送完成] 只會指出應用程式傳送呼叫中的資料緩衝區會複製到 Winsock 內核緩衝區,而不表示資料已命中網路媒體。 唯一例外是當您設定為0時停用 Winsock 緩衝 SO_SNDBUF

Winsock 使用下列規則來指出向應用程式的傳送完成(取決於呼叫 send 的方式,完成通知可能是從封鎖通話傳回的函數、發出事件的信號,或是呼叫通知功能等等):

  • 如果通訊端仍在 SO_SNDBUF 配額內,Winsock 會從應用程式傳送中複製資料,並指出傳送完成至應用程式。

  • 如果通訊端超出 SO_SNDBUF 配額,且只有一個先前緩衝的傳送仍在堆疊內核緩衝區中,Winsock 會從應用程式傳送中複製資料,並指出傳送完成至應用程式。

  • 如果通訊端超出 SO_SNDBUF 配額,且堆疊內核緩衝區中的先前緩衝傳送超過一個,Winsock 會從應用程式傳送中複製資料。 Winsock 直到堆疊完成時,才會指示傳送完成至應用程式,直到堆疊完成之後,才會將通訊端放在配額內, SO_SNDBUF 或只有一個未完成的傳送條件。

案例研究1

Winsock TCP 用戶端需要將10000記錄傳送至 Winsock TCP 伺服器以儲存在資料庫中。 記錄大小的長度會從20個位元組長度變化為100位元組。 為了簡化應用程式邏輯,設計如下:

  • 用戶端只會封鎖傳送。 伺服器只會封鎖 recv
  • 用戶端通訊端會將設定 SO_SNDBUF 為0,如此一來,每個記錄就會移出一個資料段。
  • 伺服器呼叫 recv 迴圈中。 投遞于中的緩衝 recv 是200個位元組,所以每一筆記錄都可以一個 recv 呼叫接收。

效能

在測試期間,開發人員會發現用戶端每秒只能傳送五筆記錄給伺服器。 10000個記錄,最大值為 976 kb 的資料(10000 * 100/1024),一小時以上的時間會傳送至伺服器。

分析

因為用戶端未設定 TCP_NODELAY 選項,所以 Nagle 演算法會強制 TCP 堆疊等候 ACK,之後它才能在網路上傳送其他的封包。 不過,用戶端已透過將此選項設定為0,以停用 Winsock 緩衝 SO_SNDBUF 。 因此,必須個別傳送和 ACK'ed 「10000傳送來電」。 每個 ACK 都會延遲200毫秒,因為伺服器的 TCP 堆疊上會發生下列情況:

  • 當伺服器取得封包時,其 200-毫秒延遲計時器會關閉。
  • 伺服器不需要傳送任何內容,因此無法 piggybacked ACK。
  • 除非先前的封包承認,否則用戶端將不會傳送其他的封包。
  • 伺服器上的延遲計時器已到期,且 ACK 傳回。

如何改善

這個設計有兩個問題。 首先,會發生延遲計時器問題。 用戶端必須能夠在200毫秒內,將兩封資料包傳送至伺服器。 因為用戶端預設會使用 Nagle 演算法,所以應該只使用預設的 Winsock 緩衝,而不是設定 SO_SNDBUF 為0。 一旦 TCP 堆疊將緩衝區放大至最大傳輸單位(MTU)後,就會立即傳送完整大小的封包,而不需等待來自遠端主機的 ACK。

其次,此設計為這類小型大小的每一筆記錄呼叫一次傳送。 傳送此小的大小是不太有效率。 在此情況下,開發人員可能會想要從一個用戶端傳送呼叫一次80,將每一筆記錄都從一個用戶端傳送至100位元組。 若要讓伺服器知道總傳送的記錄數目,用戶端可能會想要從修正大小標頭(包含要遵循的記錄數目)開始進行通訊。

案例研究2

Winsock TCP 用戶端應用程式會開啟兩個與 Winsock TCP 伺服器應用程式的連線,提供股票報價服務。 第一個連接是用來將股票符號傳送至伺服器的命令通道。 第二個連接是用來接收股市報價的資料通道。 在建立兩個連線後,用戶端會透過命令通道將股票符號傳送至伺服器,並等候股市報價傳回資料通道。 只有在接收第一個股市報價後,才會將下一個股票符號要求傳送至伺服器。 用戶端和伺服器不會設定 SO_SNDBUFTCP_NODELAY 選項。

  • 效能

    在測試期間,開發人員會發現用戶端每秒只能取得五個引號。

  • 分析

    這項設計只允許一次未完成的股票報價要求。 第一個股票符號會透過命令通道(連線)傳送至伺服器,回應會立即透過資料通道(連接)從伺服器傳送回用戶端。 然後,用戶端會立即傳送第二個股票符號要求,而傳送呼叫中的要求緩衝區會立即傳送至 Winsock 內核緩衝區。 不過,用戶端 TCP 堆疊無法立即從其內核緩衝區傳送要求,因為第一次透過命令通道傳送尚未認可。 在伺服器命令通道上的200毫秒延遲計時器到期後,第一個符號要求的 ACK 會傳回用戶端。 然後,第二個報價要求會在延遲200毫秒之後成功傳送至伺服器。 第二個股票符號的引號會立即傳回資料通道,因為在此時間,用戶端資料通道上的延遲計時器已過期。 伺服器會接收上一個報價回應的 ACK。 (請記住,用戶端無法傳送第二個股票報價要求的200毫秒,因此會提供用戶端上延遲計時器的時間,使其到期並將 ACK 傳送至伺服器。)因此,用戶端會取得第二個報價回應,而且可能發出另一個報價要求,該要求會受到相同週期的制約。

  • 如何改善

    這兩種連線(通道)設計不是必要的。 如果您只對股市報價要求和回應使用一個連線,則報價要求的 ACK 會在報價回應上 piggybacked,並立即回復。 若要進一步提高效能,用戶端可以將多個股票報價要求複用至伺服器的一個傳送呼叫,而且伺服器也可以將多個報價回應複用成對用戶端的一個傳送呼叫。 如果兩個單向通道設計都是必要的原因,雙方都應該設定 TCP_NODELAY 選項,這樣就可以立即傳送小型封包,而不需要等候先前的封包的 ACK。

建議

雖然這兩種案例研究都是虛構的,但有助於說明一些最壞案例案例。 當您設計涉及大量小型資料片段傳送的應用程式時 recvs ,應考慮下列指導方針:

  • 如果資料區段不是時間很重要,應用程式應該將它們合併成較大的資料區塊,以傳遞給傳送呼叫。 因為傳送緩衝區很可能會複製到 Winsock 內核緩衝區,所以緩衝區應該不會太大。 稍微小於8K 是有效的。 只要 Winsock 內核取得的區塊大於 MTU,它就會傳送多個完整大小的封包,並使用任何留下的內容傳送最後一個資料包。 200-毫秒延遲計時器不會擊中傳送端(最後一個封包除外)。 最後的封包(如果是奇數封包)仍會受到延遲的認可演算法的影響。 如果傳送端堆疊取得的另一個區塊大於 MTU,它仍然可以略過 Nagle 演算法。

  • 請盡可能避免使用單向資料流程的通訊端連線。 透過單向通訊端的通訊會更容易受到 Nagle 和延遲的認可演算法的影響。 如果通訊遵循要求和回應流程,您應該使用單一通訊端來執行這兩種傳送,這樣就能 recvs 在回應上 PIGGYBACKED ACK。

  • 若所有的小型資料段都必須立即傳送,請 TCP_NODELAY 在傳送端設定選項。

  • 除非您想要在 Winsock 顯示傳送完成時,將封包傳送到網路上,否則您不應該將設定 SO_SNDBUF 為零。 實際上,預設的8K 緩衝區已經過 heuristically,但您不應該變更它,除非您已測試新的 Winsock 緩衝區設定可提供比預設更好的效能。 此外,設定 SO_SNDBUF 為零對於執行大量資料傳輸的應用程式而言最為有益。 甚至,為了獲得最大效能,您應該將它與雙緩衝搭配使用(在任何指定時間超過一個未完成的傳送)和重疊的 I/O。

  • 如果無需保證資料傳遞,請使用 UDP。

參考

如需延遲認可及 Nagle 演算法的詳細資訊,請參閱下列各項:

Braden,1989],RFC 1122,Internet 主機的需求--通訊層,網際網路工程工作人員。