TCP傳輸小數(shù)據(jù)包效率問題(譯自MSDN)
http://www.ftpff.com/blog/?q=node/16
摘要:當(dāng)使用TCP傳輸小型數(shù)據(jù)包時,程序的設(shè)計是相當(dāng)重要的。如果在設(shè)計方案中不對TCP數(shù)據(jù)包的延遲應(yīng)答,Nagle算法,Winsock緩沖作用引起重視,將會嚴(yán)重影響程序的性能。這篇文章討論了這些問題,列舉了兩個案例,給出了一些傳輸小數(shù)據(jù)包的優(yōu)化設(shè)計方案。
背景:當(dāng)Microsoft TCP棧接收到一個數(shù)據(jù)包時,會啟動一個200毫秒的計時器。當(dāng)ACK確認(rèn)數(shù)據(jù)包發(fā)出之后,計時器會復(fù)位,接收到下一個數(shù)據(jù)包時,會再次啟動200毫秒的計時器。為了提升應(yīng)用程序在內(nèi)部網(wǎng)和Internet上的傳輸性能,Microsoft TCP棧使用了下面的策略來決定在接收到數(shù)據(jù)包后什么時候發(fā)送ACK確認(rèn)數(shù)據(jù)包:
1、如果在200毫秒的計時器超時之前,接收到下一個數(shù)據(jù)包,則立即發(fā)送ACK確認(rèn)數(shù)據(jù)包。
2、如果當(dāng)前恰好有數(shù)據(jù)包需要發(fā)給ACK確認(rèn)信息的接收端,則把ACK確認(rèn)信息附帶在數(shù)據(jù)包上立即發(fā)送。
3、當(dāng)計時器超時,ACK確認(rèn)信息立即發(fā)送。
為了避免小數(shù)據(jù)包擁塞網(wǎng)絡(luò),Microsoft TCP棧默認(rèn)啟用了Nagle算法,這個算法能夠?qū)?yīng)用程序多次調(diào)用Send發(fā)送的數(shù)據(jù)拼接起來,當(dāng)收到前一個數(shù)據(jù)包的ACK確認(rèn)信息時,一起發(fā)送出去。下面是Nagle算法的例外情況:
1、如果Microsoft TCP棧拼接起來的數(shù)據(jù)包超過了MTU值,這個數(shù)據(jù)會立即發(fā)送,而不等待前一個數(shù)據(jù)包的ACK確認(rèn)信息。在以太網(wǎng)中,TCP的MTU(Maximum Transmission Unit)值是1460字節(jié)。
2、如果設(shè)置了TCP_NODELAY選項,就會禁用Nagle算法,應(yīng)用程序調(diào)用Send發(fā)送的數(shù)據(jù)包會立即被投遞到網(wǎng)絡(luò),而沒有延遲。
為了在應(yīng)用層優(yōu)化性能,Winsock把應(yīng)用程序調(diào)用Send發(fā)送的數(shù)據(jù)從應(yīng)用程序的緩沖區(qū)復(fù)制到Winsock內(nèi)核緩沖區(qū)。Microsoft TCP棧利用類似Nagle算法的方法,決定什么時候才實際地把數(shù)據(jù)投遞到網(wǎng)絡(luò)。內(nèi)核緩沖區(qū)的默認(rèn)大小是8K,使用SO_SNDBUF選項,可以改變Winsock內(nèi)核緩沖區(qū)的大小。如果有必要的話,Winsock能緩沖大于SO_SNDBUF緩沖區(qū)大小的數(shù)據(jù)。在絕大多數(shù)情況下,應(yīng)用程序完成Send調(diào)用僅僅表明數(shù)據(jù)被復(fù)制到了Winsock內(nèi)核緩沖區(qū),并不能說明數(shù)據(jù)就實際地被投遞到了網(wǎng)絡(luò)上。唯一一種例外的情況是:通過設(shè)置SO_SNDBUT為0禁用了Winsock內(nèi)核緩沖區(qū)。
Winsock使用下面的規(guī)則來向應(yīng)用程序表明一個Send調(diào)用的完成:
1、如果socket仍然在SO_SNDBUF限額內(nèi),Winsock復(fù)制應(yīng)用程序要發(fā)送的數(shù)據(jù)到內(nèi)核緩沖區(qū),完成Send調(diào)用。
2、如果Socket超過了SO_SNDBUF限額并且先前只有一個被緩沖的發(fā)送數(shù)據(jù)在內(nèi)核緩沖區(qū),Winsock復(fù)制要發(fā)送的數(shù)據(jù)到內(nèi)核緩沖區(qū),完成Send調(diào)用。
3、如果Socket超過了SO_SNDBUF限額并且內(nèi)核緩沖區(qū)有不只一個被緩沖的發(fā)送數(shù)據(jù),Winsock復(fù)制要發(fā)送的數(shù)據(jù)到內(nèi)核緩沖區(qū),然后投遞數(shù)據(jù)到網(wǎng)絡(luò),直到Socket降到SO_SNDBUF限額內(nèi)或者只剩余一個要發(fā)送的數(shù)據(jù),才完成Send調(diào)用。
案例1
一個Winsock TCP客戶端需要發(fā)送10000個記錄到Winsock TCP服務(wù)端,保存到數(shù)據(jù)庫。記錄大小從20字節(jié)到100字節(jié)不等。對于簡單的應(yīng)用程序邏輯,可能的設(shè)計方案如下:
1、客戶端以阻塞方式發(fā)送,服務(wù)端以阻塞方式接收。
2、客戶端設(shè)置SO_SNDBUF為0,禁用Nagle算法,讓每個數(shù)據(jù)包單獨的發(fā)送。
3、服務(wù)端在一個循環(huán)中調(diào)用Recv接收數(shù)據(jù)包。給Recv傳遞200字節(jié)的緩沖區(qū)以便讓每個記錄在一次Recv調(diào)用中被獲取到。
性能:
在測試中發(fā)現(xiàn),客戶端每秒只能發(fā)送5條數(shù)據(jù)到服務(wù)段,總共10000條記錄,976K字節(jié)左右,用了半個多小時才全部傳到服務(wù)器。
分析:
因為客戶端沒有設(shè)置TCP_NODELAY選項,Nagle算法強制TCP棧在發(fā)送數(shù)據(jù)包之前等待前一個數(shù)據(jù)包的ACK確認(rèn)信息。然而,客戶端設(shè)置SO_SNDBUF為0,禁用了內(nèi)核緩沖區(qū)。因此,10000個Send調(diào)用只能一個數(shù)據(jù)包一個數(shù)據(jù)包的發(fā)送和確認(rèn),由于下列原因,每個ACK確認(rèn)信息被延遲200毫秒:
1、當(dāng)服務(wù)器獲取到一個數(shù)據(jù)包,啟動一個200毫秒的計時器。
2、服務(wù)端不需要向客戶端發(fā)送任何數(shù)據(jù),所以,ACK確認(rèn)信息不能被發(fā)回的數(shù)據(jù)包順路攜帶。
3、客戶端在沒有收到前一個數(shù)據(jù)包的確認(rèn)信息前,不能發(fā)送數(shù)據(jù)包。
4、服務(wù)端的計時器超時后,ACK確認(rèn)信息被發(fā)送到客戶端。
如何提高性能:
在這個設(shè)計中存在兩個問題。第一,存在延時問題。客戶端需要能夠在200毫秒內(nèi)發(fā)送兩個數(shù)據(jù)包到服務(wù)端。因為客戶端默認(rèn)情況下使用Nagle算法,應(yīng)該使用默認(rèn)的內(nèi)核緩沖區(qū),不應(yīng)該設(shè)置SO_SNDBUF為0。一旦TCP棧拼接起來的數(shù)據(jù)包超過MTU值,這個數(shù)據(jù)包會立即被發(fā)送,不用等待前一個ACK確認(rèn)信息。第二,這個設(shè)計方案對每一個如此小的的數(shù)據(jù)包都調(diào)用一次Send。發(fā)送這么小的數(shù)據(jù)包是不很有效率的。在這種情況下,應(yīng)該把每個記錄補充到100字節(jié)并且每次調(diào)用Send發(fā)送80個記錄。為了讓服務(wù)端知道一次總共發(fā)送了多少個記錄,客戶端可以在記錄前面帶一個頭信息。
案例二:
一個Winsock TCP客戶端程序打開兩個連接和一個提供股票報價服務(wù)的Winsock TCP服務(wù)端通信。第一個連接作為命令通道用來傳輸股票編號到服務(wù)端。第二個連接作為數(shù)據(jù)通道用來接收股票報價。兩個連接被建立后,客戶端通過命令通道發(fā)送股票編號到服務(wù)端,然后在數(shù)據(jù)通道上等待返回的股票報價信息。客戶端在接收到第一個股票報價信息后發(fā)送下一個股票編號請求到服務(wù)端。客戶端和服務(wù)端都沒有設(shè)置SO_SNDBUF和TCP_NODELAY選項。
性能:
測試中發(fā)現(xiàn),客戶端每秒只能獲取到5條報價信息。
分析:
這個設(shè)計方案一次只允許獲取一條股票信息。第一個股票編號信息通過命令通道發(fā)送到服務(wù)端,立即接收到服務(wù)端通過數(shù)據(jù)通道返回的股票報價信息。然后,客戶端立即發(fā)送第二條請求信息,send調(diào)用立即返回,發(fā)送的數(shù)據(jù)被復(fù)制到內(nèi)核緩沖區(qū)。然而,TCP棧不能立即投遞這個數(shù)據(jù)包到網(wǎng)絡(luò),因為沒有收到前一個數(shù)據(jù)包的ACK確認(rèn)信息。200毫秒后,服務(wù)端的計時器超時,第一個請求數(shù)據(jù)包的ACK確認(rèn)信息被發(fā)送回客戶端,客戶端的第二個請求包才被投遞到網(wǎng)絡(luò)。第二個請求的報價信息立即從數(shù)據(jù)通道返回到客戶端,因為此時,客戶端的計時器已經(jīng)超時,第一個報價信息的ACK確認(rèn)信息已經(jīng)被發(fā)送到服務(wù)端。這個過程循環(huán)發(fā)生。
如何提高性能:
在這里,兩個連接的設(shè)計是沒有必要的。如果使用一個連接來請求和接收報價信息,股票請求的ACK確認(rèn)信息會被返回的報價信息立即順路攜帶回來。要進(jìn)一步的提高性能,客戶端應(yīng)該一次調(diào)用Send發(fā)送多個股票請求,服務(wù)端一次返回多個報價信息。如果由于某些特殊原因必須要使用兩個單向的連接,客戶端和服務(wù)端都應(yīng)該設(shè)置TCP_NODELAY選項,讓小數(shù)據(jù)包立即發(fā)送而不用等待前一個數(shù)據(jù)包的ACK確認(rèn)信息。
提高性能的建議:
上面兩個案例說明了一些最壞的情況。當(dāng)設(shè)計一個方案解決大量的小數(shù)據(jù)包發(fā)送和接收時,應(yīng)該遵循以下的建議:
1、如果數(shù)據(jù)片段不需要緊急傳輸?shù)脑挘瑧?yīng)用程序應(yīng)該將他們拼接成更大的數(shù)據(jù)塊,再調(diào)用Send。因為發(fā)送緩沖區(qū)很可能被復(fù)制到內(nèi)核緩沖區(qū),所以緩沖區(qū)不應(yīng)該太大,通常比8K小一點點是很有效率的。只要Winsock內(nèi)核緩沖區(qū)得到一個大于MTU值的數(shù)據(jù)塊,就會發(fā)送若干個數(shù)據(jù)包,剩下最后一個數(shù)據(jù)包。發(fā)送方除了最后一個數(shù)據(jù)包,都不會被200毫秒的計時器觸發(fā)。
2、如果可能的話,避免單向的Socket數(shù)據(jù)流接連。
3、不要設(shè)置SO_SNDBUF為0,除非想確保數(shù)據(jù)包在調(diào)用Send完成之后立即被投遞到網(wǎng)絡(luò)。事實上,8K的緩沖區(qū)適合大多數(shù)情況,不需要重新改變,除非新設(shè)置的緩沖區(qū)經(jīng)過測試的確比默認(rèn)大小更高效。
4、如果數(shù)據(jù)傳輸不用保證可靠性,使用UDP。