關於網絡編程中MTU、TCP、UDP優化配置的一些總結


首先要看TCP/IP協議,涉及到四層:鏈路層,網絡層,傳輸層,應用層。   
其中以太網(Ethernet)的數據幀在鏈路層   
IP包在網絡層   
TCP或UDP包在傳輸層   
TCP或UDP中的數據(Data)在應用層   
它們的關系是 數據幀{IP包{TCP或UDP包{Data}}}   
---------------------------------------------------------------------------------
在應用程序中我們用到的Data的長度最大是多少,直接取決於底層的限制。   
我們從下到上分析一下:   
1.在鏈路層,由以太網的物理特性決定了數據幀的長度為(46+18)-(1500+18),其中的18是數據幀的頭和尾,也就是說數據幀的內容最大為1500(不包括幀頭和幀尾),即MTU(Maximum Transmission Unit)為1500;  
2.在網絡層,因為IP包的首部要占用20字節,所以這的MTU為1500-20=1480; 
3.在傳輸層,對於UDP包的首部要占用8字節,所以這的MTU為1480-8=1472;   
所以,在應用層,你的Data最大長度為1472。 (當我們的UDP包中的數據多於MTU(1472)時,發送方的IP層需要分片fragmentation進行傳輸,而在接收方IP層則需要進行數據報重組,由於UDP是不可靠的傳輸協議,如果分片丟失導致重組失敗,將導致UDP數據包被丟棄)。   
從上面的分析來看,在普通的局域網環境下,UDP的數據最大為1472字節最好(避免分片重組)。   
但在網絡編程中,Internet中的路由器可能有設置成不同的值(小於默認值),Internet上的標准MTU值為576,所以Internet的UDP編程時數據長度最好在576-20-8=548字節以內。
---------------------------------------------------------------------------------  
MTU對我們的UDP編程很重要,那如何查看路由的MTU值呢?   
對於windows OS: ping -f -l   如:ping -f -l 1472 192.168.0.1   
如果提示:Packets needs to be fragmented but DF set.   則表明MTU小於1500,不斷改小data_length值,可以最終測算出gateway的MTU值;   
對於linux OS: ping -c -M do -s   如: ping -c 1 -M do -s 1472 192.168.0.1   
如果提示 Frag needed and DF set……   則表明MTU小於1500,可以再測以推算gateway的MTU。

 

原理:ping程序使用ICMP報文,ICMP報文首部占8字節,IP數據報首部占20字節,因此在數據大小基礎上加上28字節為MTU值。

--------------------------------------------------------------------------------- 

IP數據包的最大長度是64K字節(65535),因為在IP包頭中用2個字節描述報文長度,2個字節所能表達的最大數字就是65535。  
    
由於IP協議提供為上層協議分割和重組報文的功能,因此傳輸層協議的數據包長度原則上來說沒有限制。實際上限制還是有的,因為IP包的標識字段終究不可能無限長,按照IPv4,好像上限應該是4G(64K*64K)。依靠這種機制,TCP包頭中就沒有“包長度”字段,而完全依靠IP層去處理分幀。這就是為什么TCP常常被稱作一種“流協議”的原因,開發者在使用TCP服務的時候,不必去關心數據包的大小,只需講SOCKET看作一條數據流的入口,往里面放數據就是了,TCP協議本身會進行擁塞/流量控制。  
    
UDP則與TCP不同,UDP包頭內有總長度字段,同樣為兩個字節,因此UDP數據包的總長度被限制為65535,這樣恰好可以放進一個IP包內,使得UDP/IP協議棧的實現非常簡單和高效。65535再減去UDP頭本身所占據的8個字節,UDP服務中的最大有效載荷長度僅為65527。這個值也就是你在調用getsockopt()時指定SO_MAX_MSG_SIZE所得到返回值,任何使用SOCK_DGRAM屬性的socket,一次send的數據都不能超過這個值,否則必然得到一個錯誤。  
    
那么,IP包提交給下層協議時將會得到怎樣的處理呢?這就取決於數據鏈路層協議了,一般的數據鏈路層協議都會負責將IP包分割成更小的幀,然后在目的端重組它。在EtherNet上,數據鏈路幀的大小如以上幾位大俠所言。而如果是IP   over   ATM,則IP包將被切分成一個一個的ATM   Cell,大小為53字節。

 

一些典型的MTU值: 

網絡:                                    MTU字節
超通道                                  65535
16Mb/s信息令牌環(IBM)               17914
4Mb/s令牌環(IEEE802.5)              4464
FDDI                                   4352
以太網                                  1500
IEEE802.3/802.2                         1492
X.25                                    576
點對點(低時延)                         296

    路徑MTU:如果兩台主機之間的通信要通過多個網絡,那么每個網絡的鏈路層就可能有不同的MTU。重要的不是兩台主機所在網絡的MTU的值,重要的是兩台通信主機路徑中的最小MTU。它被稱作路徑MTU。

Tcp傳輸中的nagle算法

  TCP/IP協議中,無論發送多少數據,總是要在數據前面加上協議頭,同時,對方接收到數據,也需要發送ACK表示確認。為了盡可能的利用網絡帶寬,TCP總是希望盡可能的發送足夠大的數據。(一個連接會設置MSS參數,因此,TCP/IP希望每次都能夠以MSS尺寸的數據塊來發送數據)。Nagle算法就是為了盡可能發送大塊數據,避免網絡中充斥着許多小數據塊。

      Nagle算法的基本定義是任意時刻,最多只能有一個未被確認的小段。 所謂“小段”,指的是小於MSS尺寸的數據塊,所謂“未被確認”,是指一個數據塊發送出去后,沒有收到對方發送的ACK確認該數據已收到。

1. Nagle算法的規則:

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

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

      (3)設置了TCP_NODELAY選項,則允許發送;

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

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

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

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

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

     舉個例子,一開始client端調用socket的write操作將一個int型數據(稱為A塊)寫入到網絡中,由於此時連接是空閑的(也就是說還沒有未被確認的小段),因此這個int型數據會被馬上發送到server端,接着,client端又調用write操作寫入‘\r\n’(簡稱B塊),這個時候,A塊的ACK沒有返回,所以可以認為已經存在了一個未被確認的小段,所以B塊沒有立即被發送,一直等待A塊的ACK收到(大概40ms之后),B塊才被發送。整個過程如圖所示:

      這里還隱藏了一個問題,就是A塊數據的ACK為什么40ms之后才收到?這是因為TCP/IP中不僅僅有nagle算法,還有一個TCP確認延遲機制 。當Server端收到數據之后,它並不會馬上向client端發送ACK,而是會將ACK的發送延遲一段時間(假設為t),它希望在t時間內server端會向client端發送應答數據,這樣ACK就能夠和應答數據一起發送,就像是應答數據捎帶着ACK過去。在我之前的時間中,t大概就是40ms。這就解釋了為什么'\r\n'(B塊)總是在A塊之后40ms才發出。

       當然,TCP確認延遲40ms並不是一直不變的,TCP連接的延遲確認時間一般初始化為最小值40ms,隨后根據連接的重傳超時時間(RTO)、上次收到數據包與本次接收數據包的時間間隔等參數進行不斷調整。另外可以通過設置TCP_QUICKACK選項來取消確認延遲。

      關於TCP確認延遲的詳細介紹可參考:http://blog.csdn.net/turkeyzhou/article/details/6764389

2. TCP_NODELAY 選項

      默認情況下,發送數據采用Negale 算法。這樣雖然提高了網絡吞吐量,但是實時性卻降低了,在一些交互性很強的應用程序來說是不允許的,使用TCP_NODELAY選項可以禁止Negale 算法。

      此時,應用程序向內核遞交的每個數據包都會立即發送出去。需要注意的是,雖然禁止了Negale 算法,但網絡的傳輸仍然受到TCP確認延遲機制的影響。

3. TCP_CORK 選項

     所謂的CORK就是塞子的意思,形象地理解就是用CORK將連接塞住,使得數據先不發出去,等到拔去塞子后再發出去。設置該選項后,內核會盡力把小數據包拼接成一個大的數據包(一個MTU)再發送出去,當然若一定時間后(一般為200ms,該值尚待確認),內核仍然沒有組合成一個MTU時也必須發送現有的數據(不可能讓數據一直等待吧)。

      然而,TCP_CORK的實現可能並不像你想象的那么完美,CORK並不會將連接完全塞住。內核其實並不知道應用層到底什么時候會發送第二批數據用於和第一批數據拼接以達到MTU的大小,因此內核會給出一個時間限制,在該時間內沒有拼接成一個大包(努力接近MTU)的話,內核就會無條件發送。也就是說若應用層程序發送小包數據的間隔不夠短時,TCP_CORK就沒有一點作用,反而失去了數據的實時性(每個小包數據都會延時一定時間再發送)。

4. Nagle算法與CORK算法區別

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

    實際上Nagle算法並不是很復雜,他的主要職責是數據的累積,實際上有兩個門檻:一個就是緩 沖區中的字節數達到了一定量,另一個就是等待了一定的時間(一般的Nagle算法都是等待200ms);這兩個門檻的任何一個達到都必須發送數據了。一般 情況下,如果數據流量很大,第二個條件是永遠不會起作用的,但當發送小的數據包時,第二個門檻就發揮作用了,防止數據被無限的緩存在緩沖區不是好事情哦。 了解了TCP的Nagle算法的原理之后我們可以自己動手來實現一個類似的算法了,在動手之前我們還要記住一個重要的事情,也是我們動手實現Nagle算 法的主要動機就是我想要緊急發送數據的時候就要發送了,所以對於上面的兩個門檻之外還的增加一個門檻就是緊急數據發送。

    對於我現在每秒鍾10次數據發送,每次數據發送量固定在85~100字節的應用而言,如果采用默認的開啟Nagle算法,我在發送端,固定每幀數據85個,間隔100ms發送一次,我在接受端(阻塞方式使用)接受的數據是43 138交替出現,可能就是這個算法的時間閾值問題,如果關閉Nagle算法,在接收端就可以保證數據每次接收到的都是85幀。

    Nagle算法適用於小包、高延遲的場合,而對於要求交互速度的b/s或c/s就不合適了。socket在創建的時候,默認都是使用Nagle算法的,這會導致交互速度嚴重下降,所以需要setsockopt函數來設置TCP_NODELAY為1.不過取消了Nagle算法,就會導致TCP碎片增多,效率可能會降低。

關閉nagle算法,以免影響性能,因為控制時控制端要發送很多數據量很小的數據包,需要馬上發送。

 const char chOpt = 1;

int nErr = setsockopt(pContext->m_Socket, IPPROTO_TCP, TCP_NODELAY, &chOpt, sizeof(char));

if (nErr == -1)

{

    TRACE(_T("setsockopt() error\n"),WSAGetLastError());

    return;

}

setsockopt(sockfd, SOL_TCP, TCP_CORK, &on, sizeof(on)); //set TCP_CORK

 

TCP傳輸小數據包效率問題

摘要:當使用TCP傳輸小型數據包時,程序的設計是相當重要的。如果在設計方案中不對TCP數據包的
延遲應答,Nagle算法,Winsock緩沖作用引起重視,將會嚴重影響程序的性能。這篇文章討論了這些
問題,列舉了兩個案例,給出了一些傳輸小數據包的優化設計方案。

背景:當Microsoft TCP棧接收到一個數據包時,會啟動一個200毫秒的計時器。當ACK確認數據包
發出之后,計時器會復位,接收到下一個數據包時,會再次啟動200毫秒的計時器。為了提升應用程序
在內部網和Internet上的傳輸性能,Microsoft TCP棧使用了下面的策略來決定在接收到數據包后
什么時候發送ACK確認數據包:
1、如果在200毫秒的計時器超時之前,接收到下一個數據包,則立即發送ACK確認數據包。
2、如果當前恰好有數據包需要發給ACK確認信息的接收端,則把ACK確認信息附帶在數據包上立即發送。
3、當計時器超時,ACK確認信息立即發送。
為了避免小數據包擁塞網絡,Microsoft TCP棧默認啟用了Nagle算法,這個算法能夠將應用程序多次
調用Send發送的數據拼接起來,當收到前一個數據包的ACK確認信息時,一起發送出去。下面是Nagle
算法的例外情況:
1、如果Microsoft TCP棧拼接起來的數據包超過了MTU值,這個數據會立即發送,而不等待前一個數據
包的ACK確認信息。在以太網中,TCP的MTU(Maximum Transmission Unit)值是1460字節。
2、如果設置了TCP_NODELAY選項,就會禁用Nagle算法,應用程序調用Send發送的數據包會立即被
投遞到網絡,而沒有延遲。
為了在應用層優化性能,Winsock把應用程序調用Send發送的數據從應用程序的緩沖區復制到Winsock
內核緩沖區。Microsoft TCP棧利用類似Nagle算法的方法,決定什么時候才實際地把數據投遞到網絡。
內核緩沖區的默認大小是8K,使用SO_SNDBUF選項,可以改變Winsock內核緩沖區的大小。如果有必要的話,
Winsock能緩沖大於SO_SNDBUF緩沖區大小的數據。在絕大多數情況下,應用程序完成Send調用僅僅表明數據
被復制到了Winsock內核緩沖區,並不能說明數據就實際地被投遞到了網絡上。唯一一種例外的情況是:
通過設置SO_SNDBUT為0禁用了Winsock內核緩沖區。

Winsock使用下面的規則來向應用程序表明一個Send調用的完成:
1、如果socket仍然在SO_SNDBUF限額內,Winsock復制應用程序要發送的數據到內核緩沖區,完成Send調用。
2、如果Socket超過了SO_SNDBUF限額並且先前只有一個被緩沖的發送數據在內核緩沖區,Winsock復制要發送
的數據到內核緩沖區,完成Send調用。
3、如果Socket超過了SO_SNDBUF限額並且內核緩沖區有不只一個被緩沖的發送數據,Winsock復制要發送的數據
到內核緩沖區,然后投遞數據到網絡,直到Socket降到SO_SNDBUF限額內或者只剩余一個要發送的數據,才
完成Send調用。

案例1
一個Winsock TCP客戶端需要發送10000個記錄到Winsock TCP服務端,保存到數據庫。記錄大小從20字節到100
字節不等。對於簡單的應用程序邏輯,可能的設計方案如下:
1、客戶端以阻塞方式發送,服務端以阻塞方式接收。
2、客戶端設置SO_SNDBUF為0,禁用Nagle算法,讓每個數據包單獨的發送。
3、服務端在一個循環中調用Recv接收數據包。給Recv傳遞200字節的緩沖區以便讓每個記錄在一次Recv調用中
被獲取到。

性能:
在測試中發現,客戶端每秒只能發送5條數據到服務段,總共10000條記錄,976K字節左右,用了半個多小時
才全部傳到服務器。

分析:
因為客戶端沒有設置TCP_NODELAY選項,Nagle算法強制TCP棧在發送數據包之前等待前一個數據包的ACK確認
信息。然而,客戶端設置SO_SNDBUF為0,禁用了內核緩沖區。因此,10000個Send調用只能一個數據包一個數據
包的發送和確認,由於下列原因,每個ACK確認信息被延遲200毫秒:
1、當服務器獲取到一個數據包,啟動一個200毫秒的計時器。
2、服務端不需要向客戶端發送任何數據,所以,ACK確認信息不能被發回的數據包順路攜帶。
3、客戶端在沒有收到前一個數據包的確認信息前,不能發送數據包。
4、服務端的計時器超時后,ACK確認信息被發送到客戶端。

如何提高性能:
在這個設計中存在兩個問題。第一,存在延時問題。客戶端需要能夠在200毫秒內發送兩個數據包到服務端。
因為客戶端默認情況下使用Nagle算法,應該使用默認的內核緩沖區,不應該設置SO_SNDBUF為0。一旦TCP
棧拼接起來的數據包超過MTU值,這個數據包會立即被發送,不用等待前一個ACK確認信息。第二,這個設計
方案對每一個如此小的的數據包都調用一次Send。發送這么小的數據包是不很有效率的。在這種情況下,應該
把每個記錄補充到100字節並且每次調用Send發送80個記錄。為了讓服務端知道一次總共發送了多少個記錄,
客戶端可以在記錄前面帶一個頭信息。

案例二:
一個Winsock TCP客戶端程序打開兩個連接和一個提供股票報價服務的Winsock TCP服務端通信。第一個連接
作為命令通道用來傳輸股票編號到服務端。第二個連接作為數據通道用來接收股票報價。兩個連接被建立后,
客戶端通過命令通道發送股票編號到服務端,然后在數據通道上等待返回的股票報價信息。客戶端在接收到第一
個股票報價信息后發送下一個股票編號請求到服務端。客戶端和服務端都沒有設置SO_SNDBUF和TCP_NODELAY
選項。

性能:
測試中發現,客戶端每秒只能獲取到5條報價信息。

分析:

這個設計方案一次只允許獲取一條股票信息。第一個股票編號信息通過命令通道發送到服務端,立即接收到
服務端通過數據通道返回的股票報價信息。然后,客戶端立即發送第二條請求信息,send調用立即返回,
發送的數據被復制到內核緩沖區。然而,TCP棧不能立即投遞這個數據包到網絡,因為沒有收到前一個數據包的
ACK確認信息。200毫秒后,服務端的計時器超時,第一個請求數據包的ACK確認信息被發送回客戶端,客戶端
的第二個請求包才被投遞到網絡。第二個請求的報價信息立即從數據通道返回到客戶端,因為此時,客戶端的
計時器已經超時,第一個報價信息的ACK確認信息已經被發送到服務端。這個過程循環發生。

如何提高性能:
在這里,兩個連接的設計是沒有必要的。如果使用一個連接來請求和接收報價信息,股票請求的ACK確認信息會
被返回的報價信息立即順路攜帶回來。要進一步的提高性能,客戶端應該一次調用Send發送多個股票請求,服務端
一次返回多個報價信息。如果由於某些特殊原因必須要使用兩個單向的連接,客戶端和服務端都應該設置TCP_NODELAY
選項,讓小數據包立即發送而不用等待前一個數據包的ACK確認信息。

提高性能的建議:
上面兩個案例說明了一些最壞的情況。當設計一個方案解決大量的小數據包發送和接收時,應該遵循以下的建議:
1、如果數據片段不需要緊急傳輸的話,應用程序應該將他們拼接成更大的數據塊,再調用Send。因為發送緩沖區
很可能被復制到內核緩沖區,所以緩沖區不應該太大,通常比8K小一點點是很有效率的。只要Winsock內核緩沖區
得到一個大於MTU值的數據塊,就會發送若干個數據包,剩下最后一個數據包。發送方除了最后一個數據包,都不會
被200毫秒的計時器觸發。
2、如果可能的話,避免單向的Socket數據流接連。
3、不要設置SO_SNDBUF為0,除非想確保數據包在調用Send完成之后立即被投遞到網絡。事實上,8K的緩沖區適合大多數
情況,不需要重新改變,除非新設置的緩沖區經過測試的確比默認大小更高效。
4、如果數據傳輸不用保證可靠性,使用UDP。


免責聲明!

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



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