TCP 參數對延遲和傳輸量的影響

fcamel
fcamel的程式開發心得
6 min readNov 22, 2017

man 7 tcp 列了許多 TCP 相關的參數,這篇討論幾個常聽到參數的影響。

TCP_MAXSEG

決定 TCP 封包的資料大小 (maximum segment size, MSS),理論上愈大愈好,可以降低 IP 和 TCP 標頭的成本。實際上因為某些 router 沒遵守 ICMP,設太大可能會被 router 默默地丟包,應用程完全不會知道被丟包,所以不能設太大。

此外,OS 會幫你設最佳的值,所以實務上不要動這個參數是最佳結果。詳細說明見《TCP maximum segment size 是什麼以及是如何決定的》

TCP_QUICKACK (not portable)

TCP 預設會用 Nagle’s Algorithm 和 delayed ACK 減少傳輸量,傳送端和接收端都會盡可能減少傳送小封包。然而,應用程若沒有留意而產生太多小封包,兩者合用會增加延遲時間。詳細說明見《Nagle’s Algorithm 和 Delayed ACK 以及 Minshall 的加強版》

TCP_QUICKACK 是建議 OS 少用 delayed ACK,但是它的效果並不顯著,相關測試和分析見《Linux 上 TCP QUICKACK 的效果》

TCP_NODELAY

TCP_NODELAY 為真時,會關掉 Nagle’s Algorithm,這也是遇到「不明原因延遲」時常見的建議。然而,關掉 Nagle’s Algorithm 會增加大量小封包,後面有實驗說明用 TCP_NODELAY 的代價。

TCP_CORK (not portable)

啟用 TCP_CORK 會 buffer 資料,減少產生小封包。目前 Linux 的實作最多 buffer 200ms。啟用 TCP_CORK 時會關掉 TCP_NODELAY。

實驗設置

tcp_test.c 的 server 會接收連線,然後和 client 互傳資料。server 和 client 在兩台不同的 VM (Ubuntu 16.04 和 Ubuntu 14.04) 執行,使用同樣的設定。

執行流程是:

  1. server 將 1024 bytes 切成 K 份依序送給 client。
  2. client 收完全部資料。
  3. client 將 1024 bytes 切成 K 份依序送給 server。
  4. server 收完全部資料。
  5. 重覆 1 ~ 4 直到總共作了 10 次。

每組 TCP 參數設定作四次: K = 1、10、100、1000。

這裡不實驗傳送 >MSS 的資料,因為 sliding window 沒滿的情況,TCP 會立即傳送塞滿 MSS 的封包。兩台鄰近機器互傳沒有網路延遲,sliding window 一直都很空,>MSS 的部份會馬上傳出,測不出差異。

TCP 參數配置

共有四組設定:

  • default: 關閉 TCP_NODELAY 和 TCP_CORK。
  • TCP_CORK: 開啟 TCP_CORK。
  • TCP_NODELAY: 開啟 TCP_NODELAY。
  • TCP_NODELAY + MSG_MORE: 開啟 TCP_NODELAY,並且在呼叫 send() 傳送前 1023 bytes 時附帶參數 MSG_MORE,然後在最後 1 byte 不帶 MSG_MORE。MSG_MORE 用來告訴 kernel 之後還有資料要送。

摘錄 MSG_MORE 的說明:

The caller has more data to send.  This flag is used with TCP
sockets to obtain the same effect as the TCP_CORK socket
option (see tcp(7)), with the difference that this flag can be
set on a per-call basis.

觀察方法

用 tshark 記錄資料:

$ sudo tshark -i enp0s5 -f "src port 8000 or dst port 8000" > log.txt
  • 傳輸時間的計算方式: 連線完成後,server 傳送第一份資料到 server 傳 FIN 之間的時間 (單位: 秒)。
  • 傳送資料的計算方式: server 傳的全部封包。直接用 tshark 回報的大小,所以除了 TCP 和 IP 的標頭,每個封包還會多算到 Ethernet 的標頭 (單位: byte)。

實驗結果

  • 和 default 相比,TCP_NODELAY 在 K = 10、100、1000 的延遲時間都比 default 短,沒有被接受端的 delayed ACK 擔誤。但也造成額外可觀的傳輸量,K = 1000 時傳輸量突破天際,花了 default 15倍左右的傳輸量。
  • TCP_CORK 有顯著較高的延遲時間。
  • default 在 K = 10、100、1000 時沒有什麼差異。觀察 tshark 抓的結果,發現傳送端呼叫多次 send() 被 kernel 存起來,待收到 ACK 後再一起送出,所以切 10 / 100 / 1000 份,沒什麼差異。Nagle’s Algorithm 的 buffer 效果很好。

實驗結果 (去掉 outlier)

去掉 outlier 可明顯看出 TCP_NODELAY + MSG_MORE 不管是傳輸時間還是傳輸量都優於其它設定。不只延遲時間比單用 TCP_NODELAY 短,傳輸量也比 TCP_CORK 好。

詳細數據

結論

想要延遲時間短、傳輸量又低,應用層自己完全掌控效果最好。畢竟只有應用層自己知道什麼時候該立即送出,什麼時候可以先 buffer 起來。

應用層可能的作法有:

  1. 自己 buffer 資料,收齊資料再呼叫 send()。
  2. 呼叫 send() 時總是帶 MSG_MORE,最後再用 send() 不帶 MSG_MORE 送 1 byte 資料要求 kernel “flush” 資料。

作法 2 不用自己管 buffer,可以減少複製資料,並且資料湊齊 MSS 會立即送出去,感覺會比較好實作?另一方面作法 1 提供自動壓縮資料的機會,這是作法 2 無法提供的。有閒找時間實作一個通用的模組看看。

--

--