一、Nagle算法概述
之前我們介紹過,有一些交互式應用會傳遞大量的小包(稱呼為tinygrams),這些小包的負載可能只有幾個bytes,但是TCP和IP的基本頭就有40bytes,如果大量傳遞這種小包,會嚴重降低網絡利用率,還可能造成網絡擁塞。福特公司就曾經遇到過這種問題,John Nagle提出了一種通過ACK報文控制TCP發包的方法解決了這種問題,這種方法也就以Nagle名字命名,稱為Nagle算法。Nagle算法最開始的標准為RFC896,但是RFC896目前已經被RFC7805移動到了Historic狀態,原因是RFC1122和RFC6633描述的內容已經取代了RFC896。
二、Nagle算法概述
用一句話簡單的描述Nagle算法就是:在任意時刻,最多只能有一個未被ACK確認的小包。
按照RFC1122描述,如果有發送出去的但是還沒有被ACK確認的數據,那么發送方應該緩存所有的用戶數據,直到發出去的數據被ACK確認或者緩存的TCP數據可以發出一個full-sized的報文。full-sized報文一般是指達到了SMSS的報文。這樣實際上按照這樣的要求,對於小包的處理,TCP實際上進入了停等式(stop-and-wait)。這里的協議描述並沒有限制發送出去還未確認的報文是否為小包,而在linux實現上則會進一步判斷如果有發出去的還沒確認的小包(小於SMSS的報文)才會進一步判斷是否使能Nagle算法(請參考wireshark示例)。
RFC1122還說明一個TCP主機應該實現Nalge算法來把小包匯聚,但是一定要實現一個接口可以讓應用層單獨在一個TCP連接上禁用Nagle算法。linux中Nagle算法默認是打開的,應用程序可以通過TCP_NODELAY選項來設置socket關閉Nagle算法(注意是關閉)。
三、Nagle算法與延遲ACK
當Nagle算法和延遲ACK同時使能的時候,可能會造成發送端等待ACK確認包以發送剩下的小包數據,而接收方等待應用層數據或者等待接收新的數據以超過一個接收MSS后在發送ACK確認包。這樣發送發和接收方互相等待對方(稱呼為deadlock),最終延遲ACK超時。這種情況下就會提升平均時延,降低網絡性能,因此可能需要禁用Nagle算法或者延遲ACK算法。參考下面的wireshark示例。
四、wireshark示例
在下面的相關測試中需要關閉網卡的TSO和GSO功能,關閉方法
ethtool -K lo tso off
ethtool -K lo gso off
查看網卡TSO和GSO相關配置可以通過下面命令查看
ethtool -k lo
TSO和GSO功能是指TCP模塊發送報文的時候,可以以整數倍的MSS大小發送,然后網卡進行分段,最終發出去的報文是沒有超過MSS的,但是wireshark在TSO之前抓包,看到的數據包的大小會為MSS的整數倍。例如下面示例1如果不關閉TSO和GSO功能,則第一次write寫入105bytes的數據后,wireshark會顯示先發送了100bytes的數據,然后又發送了5bytes的數據。
另外下面示例圖中,除了TLP和Nagle交互的示例外,其余示例的截圖中的高亮的數據包是我為了解釋標記出來的,並不是wireshark標記的異常包。
1、默認打開Nalge算法場景
如下圖所示,server與client建立連接后,client發送一個No4數據包,server回復ACK確認,連接正常建立。其中在No1報文中可以看到client端通告的MSS選項中MSS為62bytes。server端扣除TSopt選項的12bytes后(其中還包括兩個nop選項),最大只能發送50bytes的數據。接着server端連續兩次write寫入,第一次寫入105bytes,第二次寫入58bytes。可以看到在以MSS大小發出去No6和No7報文之后,剩余的5bytes同樣也發出去了,並沒有因為之前有未ACK確認的full-sized數據包而阻止發送。接着第二次寫入58byte后,其中前50bytes可以構成一個full-sized包,因此可以直接發送出去,但是剩余的8bytes因為不能構成一個full-sized報文,同時還沒有收到前面No8這個小包的ACK確認包,因此這8bytes會暫時被Nagle算法阻止發送,直到收到No8的確認包No12后,最后的8bytes才得以傳輸出去。
2、設置關閉Nagle場景
同樣運行上面的測試用例,但是這次server端代碼中通過TCP_NODELAY設置關閉Nagle算法。結果如下圖,與上面示例對比,這里不再額外解釋。
3、Nagle算法和延遲ACK同時打開的場景
client與server端建立連接后先寫入20個數據包,每個包的長度為58bytes,接着client每隔3ms寫入3bytes的數據,最終結果如下圖所示
之前我們已經介紹過一般連接建立后第36個數據包以后才會啟動延遲ACK,從下圖可以看到No36的確認包No37確認發生了延遲。但是No38的確認包並沒有延遲,原因是No39需要更新窗口(對比圖中No39和No37的Win字段)。No40以后可以看到基本上每次server端回復ACK都需要延遲等待大約40ms,而client受限與nagle算法只能等到server端回復ACK確認包后才能發出新的數據。可以看到這個時候,client端每個數據包的平均延遲被將會變大,而且延遲ACK的延遲時間ato還會動態調整(一般最大不超過RTO),所以對於時延比較敏感的交互式應用應該關閉Nalge算法。
在打開Nagle算法的場景下,測試完成后wireshark顯示總共交換了87個數據包,總耗時1.004s,關閉Nagle算法后進行同樣的測試,wireshark顯示總共交換了361個數據包,總耗時0.967s。另外從應用層看每個數據包的平均耗時打開Nagle算法和關閉Nagle算法對比,將會更長。關閉Nagle算法測試大的wireshark抓包文件可以自行下載查看,此處不再贅述。
4、Nagle和TLP交互
之前我們介紹TLP的時候,曾經提到過TLP的Loss Probe探測包並不受到Nagle的約束,示例如下,No10為TLP的Loss Probe報文,可以看到並沒有受到Nagle算法的約束。
補充說明:
1、windows下面可以通過添加DWORD注冊表項HKLM/SOFTWARE\Misrosoft\MSMQ\Parameters\TCPNoDelay全局關閉Nagle算法。