1. 程式人生 > >關於網路程式設計中MTU、TCP、UDP優化配置的一些總結

關於網路程式設計中MTU、TCP、UDP優化配置的一些總結

轉載來自:http://www.cnblogs.com/maowang1991/archive/2013/04/15/3022955.html

首先要看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。