首先要看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。linux
--------------------------------------------------------------------------------- 算法
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
點對點(低時延) 296windows
路徑MTU:若是兩臺主機之間的通訊要經過多個網絡,那麼每一個網絡的鏈路層就可能有不一樣的MTU。重要的不是兩臺主機所在網絡的MTU的值,重要的是兩臺通訊主機路徑中的最小MTU。它被稱做路徑MTU。緩存
Tcp傳輸中的nagle算法服務器
TCP/IP協議中,不管發送多少數據,老是要在數據前面加上協議頭,同時,對方接收到數據,也須要發送ACK表示確認。爲了儘量的利用網絡帶寬,TCP老是但願儘量的發送足夠大的數據。(一個鏈接會設置MSS參數,所以,TCP/IP但願每次都可以以MSS尺寸的數據塊來發送數據)。Nagle算法就是爲了儘量發送大塊數據,避免網絡中充斥着許多小數據塊。網絡
Nagle算法的基本定義是任意時刻,最多隻能有一個未被確認的小段。 所謂「小段」,指的是小於MSS尺寸的數據塊,所謂「未被確認」,是指一個數據塊發送出去後,沒有收到對方發送的ACK確認該數據已收到。socket
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確認數據包:
一、若是在200毫秒的計時器超時以前,接收到下一個數據包,則當即發送ACK確認數據包。
二、若是當前剛好有數據包須要發給ACK確認信息的接收端,則把ACK確認信息附帶在數據包上當即發送。
三、當計時器超時,ACK確認信息當即發送。
爲了不小數據包擁塞網絡,Microsoft TCP棧默認啓用了Nagle算法,這個算法可以將應用程序屢次
調用Send發送的數據拼接起來,當收到前一個數據包的ACK確認信息時,一塊兒發送出去。下面是Nagle
算法的例外狀況:
一、若是Microsoft TCP棧拼接起來的數據包超過了MTU值,這個數據會當即發送,而不等待前一個數據
包的ACK確認信息。在以太網中,TCP的MTU(Maximum Transmission Unit)值是1460字節。
二、若是設置了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調用的完成:
一、若是socket仍然在SO_SNDBUF限額內,Winsock複製應用程序要發送的數據到內核緩衝區,完成Send調用。
二、若是Socket超過了SO_SNDBUF限額而且先前只有一個被緩衝的發送數據在內核緩衝區,Winsock複製要發送
的數據到內核緩衝區,完成Send調用。
三、若是Socket超過了SO_SNDBUF限額而且內核緩衝區有不僅一個被緩衝的發送數據,Winsock複製要發送的數據
到內核緩衝區,而後投遞數據到網絡,直到Socket降到SO_SNDBUF限額內或者只剩餘一個要發送的數據,才
完成Send調用。
案例1
一個Winsock TCP客戶端須要發送10000個記錄到Winsock TCP服務端,保存到數據庫。記錄大小從20字節到100
字節不等。對於簡單的應用程序邏輯,可能的設計方案以下:
一、客戶端以阻塞方式發送,服務端以阻塞方式接收。
二、客戶端設置SO_SNDBUF爲0,禁用Nagle算法,讓每一個數據包單獨的發送。
三、服務端在一個循環中調用Recv接收數據包。給Recv傳遞200字節的緩衝區以便讓每一個記錄在一次Recv調用中
被獲取到。
性能:
在測試中發現,客戶端每秒只能發送5條數據到服務段,總共10000條記錄,976K字節左右,用了半個多小時
才所有傳到服務器。
分析:
由於客戶端沒有設置TCP_NODELAY選項,Nagle算法強制TCP棧在發送數據包以前等待前一個數據包的ACK確認
信息。然而,客戶端設置SO_SNDBUF爲0,禁用了內核緩衝區。所以,10000個Send調用只能一個數據包一個數據
包的發送和確認,因爲下列緣由,每一個ACK確認信息被延遲200毫秒:
一、當服務器獲取到一個數據包,啓動一個200毫秒的計時器。
二、服務端不須要向客戶端發送任何數據,因此,ACK確認信息不能被髮回的數據包順路攜帶。
三、客戶端在沒有收到前一個數據包的確認信息前,不能發送數據包。
四、服務端的計時器超時後,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確認信息。
提升性能的建議:上面兩個案例說明了一些最壞的狀況。當設計一個方案解決大量的小數據包發送和接收時,應該遵循如下的建議:一、若是數據片斷不須要緊急傳輸的話,應用程序應該將他們拼接成更大的數據塊,再調用Send。由於發送緩衝區極可能被複制到內核緩衝區,因此緩衝區不該該太大,一般比8K小一點點是頗有效率的。只要Winsock內核緩衝區獲得一個大於MTU值的數據塊,就會發送若干個數據包,剩下最後一個數據包。發送方除了最後一個數據包,都不會被200毫秒的計時器觸發。二、若是可能的話,避免單向的Socket數據流接連。三、不要設置SO_SNDBUF爲0,除非想確保數據包在調用Send完成以後當即被投遞到網絡。事實上,8K的緩衝區適合大多數狀況,不須要從新改變,除非新設置的緩衝區通過測試的確比默認大小更高效。四、若是數據傳輸不用保證可靠性,使用UDP。