TCP之Nagle算法&&延遲ACK


 

糊塗窗口綜合症和Nagle算法

 

 

 

  TCP/IP詳解系列,關於tcp擁塞控制和數據流的地方講的不細致,或許是涉及概念/算法太多,作者略去了一些對初學者來說比較陌生的細節吧。比如SWS未說明是什么就開始介紹其避免方法,還和nagle扯在了一起,直覺告訴我二者一定有貓膩,邊搜索一下,果然很有收獲。今天貼在這里,分享給大家。 

  關鍵字:糊塗窗口綜合症  nagle算法  延遲ACK/clark算法   CORK選項

第一部分 糊塗窗口綜合症

  當發送端應用進程產生數據很慢、或接收端應用進程處理接收緩沖區數據很慢,或二者兼而有之;就會使應用進程間傳送的報文段很小,特別是有效載荷很小。 極端情況下,有效載荷可能只有1個字節;而傳輸開銷有40字節(20字節的IP頭+20字節的TCP頭) 這種現象就叫糊塗窗口綜合症。

發送端求解(negle)

  如果發送端為產生數據很慢的應用程序服務(典型的有telnet應用),例如,一次產生一個字節。這個應用程序一次將一個字節的數據寫入發送端的TCP的緩存。如果發送端的TCP沒有特定的指令,它就產生只包括一個字節數據的報文段。結果有很多41字節的IP數據報就在互連網中傳來傳去。解決的方法是防止發送端的TCP逐個字節地發送數據。必須強迫發送端的TCP收集數據,然后用一個更大的數據塊來發送。發送端的TCP要等待多長時間呢?如果它等待過長,它就會使整個的過程產生較長的時延。如果它的等待時間不夠長,它就可能發送較小的報文段,於是,Nagle找到了一個很好的解決方法,發明了Nagle算法。而他選擇的等待時間是一個RTT,即下個ACK來到時。

接收端求解(delay-ack)

  接收端的TCP可能產生糊塗窗口綜合症,如果它為消耗數據很慢的應用程序服務,例如,一次消耗一個字節。假定發送應用程序產生了1000字節的數據塊,但接收應用程序每次只吸收1字節的數據。再假定接收端的TCP的輸入緩存為4000字節。發送端先發送第一個4000字節的數據。接收端將它存儲在其緩存中。現在緩存滿了。它通知窗口大小為零,這表示發送端必須停止發送數據。接收應用程序從接收端的TCP的輸入緩存中讀取第一個字節的數據。在入緩存中現在有了1字節的空間。接收端的TCP宣布其窗口大小為1字節,這表示正渴望等待發送數據的發送端的TCP會把這個宣布當作一個好消息,並發送只包括一個字節數據的報文段。這樣的過程一直繼續下去。一個字節的數據被消耗掉,然后發送只包含一個字節數據的報文段。

  對於這種糊塗窗口綜合症,即應用程序消耗數據比到達的慢,有兩種建議的解決方法:
  1) Clark解決方法

      Clark解決方法是只要有數據到達就發送確認,但宣布的窗口大小為零,直到或者緩存空間已能放入具有最大長度的報文段,或者緩存空間的一半已經空了。
  2 )延遲確認ACK 

      這表示當一個報文段到達時並不立即發送確認。接收端在確認收到的報文段之前一直等待,直到入緩存有足夠的空間為止。延遲的確認防止了發送端的TCP滑動其窗口。當發送端的TCP發送完其數據后,它就停下來了。這樣就防止了這種症狀。遲延的確認還有另一個優點:它減少了通信量。接收端不需要確認每一個報文段。但它也有一個缺點,就是遲延的確認有可能迫使發送端重傳其未被確認的報文段。可以用協議來平衡這個優點和缺點,例如現在定義了確認的延遲不能超過500毫秒。

 

第二部分:治療辦法

   2.1 nagle算法

        TCP/IP協議中,無論發送多少數據,總是要在數據前面加上協議頭,同時,對方接收到數據,也需要發送ACK表示確認。為了盡可能的利用網絡帶寬,TCP總是希望盡可能的發送足夠大的數據。(一個連接會設置MSS參數,因此,TCP/IP希望每次都能夠以MSS尺寸的數據塊來發送數據)。Nagle算法就是為了盡可能發送大塊數據,避免網絡中充斥着許多小數據塊。
        Nagle算法的基本定義是任意時刻,最多只能有一個未被確認的小段。 所謂“小段”,指的是小於MSS尺寸的數據塊,所謂“未被確認”,是指一個數據塊發送出去后,沒有收到對方發送的ACK確認該數據已收到。
        從Nagle算法生效情況的補集,可以側面理解其本質,參考tcp_output.c文件里tcp_nagle_check函數:

      (1)如果包長度達到MSS,則允許發送;

      (2)如果該包含有FIN,則允許發送;

      (3)設置了TCP_NODELAY選項(意在禁止nagle),則允許發送;

      (4)未設置TCP_CORK選項時,若所有發出去的小數據包(包長度小於MSS)均被確認,則允許發送; 

      (5)上述條件都未滿足,但發生了超時(一般設置延遲ACK,一般為200ms),則立即發送。

   通常我們比較關注的是情況(1)和(5)。

         Nagle算法只允許一個未被ACK的包存在於網絡,它並不管包的大小,因此它事實上就是一個擴展的停-等協議,只不過它是基於包停-等的,而不是基於字節停-等的。Nagle算法完全由TCP協議的ACK機制決定,這會帶來一些問題,比如如果對端ACK回復很快的話,Nagle事實上不會拼接太多的數據包,雖然避免了網絡擁塞,網絡總體的利用率依然很低。另外,他是一個自適應的方法,讀者可以自己按上述規則試驗一下。 

        Nagle算法是silly window syndrome(SWS)預防算法的一個半集,預防SWS不止nagle算法一個途徑。SWS算法預防發送少量的數據,Nagle算法是其在發送方的實現,而接收方要做的時不要通告緩沖空間的很小增長,不通知小窗口,除非緩沖區空間有顯著的增長。這里顯著的增長定義為完全大小的段(MSS)或增長到大於最大窗口的一半。

注意:BSD的實現是允許在空閑鏈接上發送大的寫操作剩下的最后的小段,也就是說,當超過1個MSS數據發送時,內核先依次發送完n個MSS的數據包,然后再發送尾部的小數據包,其間不再延時等待。(假設網絡不阻塞且接收窗口足夠大)

    TCP_NODELAY 選項

        默認情況下,發送數據采用Nagle 算法。這樣雖然提高了網絡吞吐量,但是實時性卻降低了,在一些交互性很強的應用程序來說是不允許的,使用TCP_NODELAY選項可以禁止Negale 算法。此時,應用程序向內核遞交的每個數據包都會立即發送出去。需要注意的是,雖然禁止了Negale 算法,但網絡的傳輸仍然受到TCP確認延遲機制的影響。


 相關:linux環境下的TCP_CORK 選項 

        所謂的CORK就是塞子的意思,形象地理解就是用CORK將連接塞住,使得數據先不發出去,等到拔去塞子后再發出去。設置該選項后,內核會盡力把小數據包拼接成一個大的數據包(一個MTU)再發送出去,當然若一定時間后(一般為200ms,該值尚待確認),內核仍然沒有組合成一個MTU時也必須發送現有的數據(不可能讓數據一直等待吧)。
        然而,TCP_CORK的實現可能並不像你想象的那么完美,CORK並不會將連接完全塞住。內核其實並不知道應用層到底什么時候會發送第二批數據用於和第一批數據拼接以達到MTU的大小,因此內核會給出一個時間限制,在該時間內沒有拼接成一個大包(努力接近MTU)的話,內核就會無條件發送。也就是說若應用層程序發送小包數據的間隔不夠短時,TCP_CORK就沒有一點作用,反而失去了數據的實時性(每個小包數據都會延時一定時間再發送)。

Nagle算法與CORK算法區別

  Nagle算法的初衷:避免發送大量的小包,防止小包泛濫於網絡,理想情況下,對於一個TCP連接而言,網絡上每次只能一個小包存在。它更多的是端到端意義上的優化。
  CORK算法的初衷:提高網絡利用率,理想情況下,完全避免發送小包,僅僅發送滿包以及不得不發的小包。

  很多人都把Nagle算法的目的理解為“提高網絡利用率”,事實上,Nagle算法所謂的“提高網絡利用率”只是它的一個副作用(Side effect...),Nagle算法的主旨在於“避免發送‘大量’的小包”。Nagle算法並沒有阻止發送小包,它只是阻止了發送大量的小包!誠然,發送大量的小包是降低了網絡利用率,但是,發送少量必須發送的小包也是對網絡利用率的降低,想徹底提高網絡利用率,為嘛不直接阻止小包發送呢?不管是大量小包還是少量小包,甚至一個小包也不讓發送,這才是提高網絡利用率的正解!是的,TCP_CORK就是做這個的。所以有人說,CORK選項是Nagle的增強,而實際上,它們是完全不同的兩回事,初衷不同。

  Nagle算法和CORK算法着眼點不一樣,Nagle算法主要避免網絡因為太多的小包(協議頭的比例非常之大)而擁塞,而CORK算法則是為了提高網絡的利用率,使得總體上協議頭占用的比例盡可能的小。如此看來這二者在避免發送小包上是一致的,在用戶控制的層面上,Nagle算法完全不受用戶socket的控制,你只能簡單的設置TCP_NODELAY而禁用它,CORK算法同樣也是通過設置或者清除TCP_CORK使能或者禁用之,然而Nagle算法關心的是網絡擁塞問題,只要所有的ACK回來則發包,而CORK算法卻可以關心內容,在前后數據包發送間隔很短的前提下(很重要,否則內核會幫你將分散的包發出),即使你是分散發送多個小數據包,你也可以通過使能CORK算法將這些內容拼接在一個包內,如果此時用Nagle算法的話,則可能做不到這一點。

    2.2 延遲ACK

        這里要說明一下,Nagle算法和延遲ACK作用在方向相反的數據包和針對該數據包的確認包上,因此它們的作用力會相悖,結果就是誰也不能發包。就像一根繩子上拴兩只青蛙一樣,被對方牽制誰也跑不了!關鍵點在於,小包的發送依賴於ACK,然而延遲ACK阻止了ACK的即時發送,形成了僵持狀態。本來只是為了減少網絡上小包的數量(再次強調Nagle算法以及延遲ACK的目的,注意,糊塗窗口綜合症只是網絡上小包泛濫的原因之一!),卻人為引入了大量的延遲!

 

參考文獻

再次談談TCP的Nagle算法與TCP_CORK選項

《延遲問題實測》

《tcp/ip詳解卷2》  24章 TCP 傳輸控制協議

連續發送多份小數據時40ms延遲問題

 《so_linger選項關閉timewait》http://www.xuebuyuan.com/1783143.html

  《nginx實現的應用層linger選項》http://blog.csdn.net/wangpengqi/article/details/17245889 以及http://blog.csdn.net/fangru/article/details/9024759

 

 
 

 

1. Nagle算法:

是為了減少廣域網的小分組數目,從而減小網絡擁塞的出現;

該算法要求一個tcp連接上最多只能有一個未被確認的未完成的小分組,在該分組ack到達之前不能發送其他的小分組,tcp需要收集這些少量的分組,並在ack到來時以一個分組的方式發送出去;其中小分組的定義是小於MSS的任何分組;

該算法的優越之處在於它是自適應的,確認到達的越快,數據也就發哦送的越快;而在希望減少微小分組數目的低速廣域網上,則會發送更少的分組;

 

2. 延遲ACK:

如果tcp對每個數據包都發送一個ack確認,那么只是一個單獨的數據包為了發送一個ack代價比較高,所以tcp會延遲一段時間,如果這段時間內有數據發送到對端,則捎帶發送ack,如果在延遲ack定時器觸發時候,發現ack尚未發送,則立即單獨發送;

延遲ACK好處:

(1) 避免糊塗窗口綜合症;

(2) 發送數據的時候將ack捎帶發送,不必單獨發送ack;

(3) 如果延遲時間內有多個數據段到達,那么允許協議棧發送一個ack確認多個報文段;

 

3. 當Nagle遇上延遲ACK:

試想如下典型操作,寫-寫-讀,即通過多個寫小片數據向對端發送單個邏輯的操作,兩次寫數據長度小於MSS,當第一次寫數據到達對端后,對端延遲ack,不發送ack,而本端因為要發送的數據長度小於MSS,所以nagle算法起作用,數據並不會立即發送,而是等待對端發送的第一次數據確認ack;這樣的情況下,需要等待對端超時發送ack,然后本段才能發送第二次寫的數據,從而造成延遲;

 

4. 關閉Nagle算法:

使用TCP套接字選項TCP_NODELAY可以關閉套接字選項;

如下場景考慮關閉Nagle算法:

(1) 對端不向本端發送數據,並且對延時比較敏感的操作;這種操作沒法捎帶ack;

(2) 如上寫-寫-讀操作;對於此種情況,優先使用其他方式,而不是關閉Nagle算法:

--使用writev,而不是兩次調用write,單個writev調用會使tcp輸出一次而不是兩次,只產生一個tcp分節,這是首選方法;

--把兩次寫操作的數據復制到單個緩沖區,然后對緩沖區調用一次write;

--關閉Nagle算法,調用write兩次;有損於網絡,通常不考慮;

 

5. 禁止Nagle和開啟Nagle算法發送數據與確認示意圖:

 

 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM