[本文轉載來源為:http://www.tongyi.net/article/20020424/200204243275.shtml]
在前一篇文章裡,我們討論了以下問題︰如何採用sendfile()系統函數降低從磁片到網路的數據傳輸負載。接下來我們繼續討論涉及網路連接控制的另一問題,同時希望透過對這一問題的討論能有助於在實際環境下把sendfile()的功能最大化,這就是如何設定TCP/IP選項來控制套接字的行為。
TCP/IP數據傳輸
TCP/IP網路的數據傳輸通常建立在數據塊的基礎之上。從程式員的觀點來看,發送數據意味著發出(或者提交)一系列“發送數據塊”的請求。在系統級,發送單個數據塊可以透過調用系統函數write() 或者sendfile() 來完成。在網路級可以看到更多的數據塊,通常把它們叫做幀,幀再被包裝上一定位元組長度的報頭然後透過線路在網路上傳輸。幀及其報頭內部的訊息是由若干協議層定義的,從OSI參考模型的物理層到應用層都可能會牽扯到。
因為在網路連接中是由程式員來選擇最適當的應用協議,所以網路包的長度和順序都在程式員的控制之下。同樣的,程式員還必須選擇這個協議在軟體中得以實現的模式。TCP/IP協議自身已經有了多種可互操作的實現,所以在雙方通信時,每一方都有它自身的低級行為,這也是程式員所應該知道的情況。
通常情況下,程式員不必關心作業系統和網路協議棧發送和接收網路數據的方法。系統內置算法定義了低級的數據組織和傳輸模式;然而,影響這些算法的行為以及對網路連接施加更大強度控制能力的方法也是有的。例如,如果某個應用協議使用了超時和重發機製,程式員就可以採取一定措施設定或者獲取超時參數。他或她還可能需要增加發送和接收緩沖區的大小來保證網路上的訊息流動不會中斷。改變TCP/IP協議棧行為的一般的方法是採用所謂的TCP/IP選項。下面就讓我們來看一看你該如何使用這些選項來優化數據傳輸。
TCP/IP選項
有好幾種選項都能改變TCP/IP協議棧的行為。使用這些選擇能對在同一計算機上營運的其他應用程式產生不利的影響,因此普通用戶通常是不能使用這些選項的(除了root用戶以外)。我們在這裡主要討論能改變單個連接操作(用TCP/IP的術語來說就是套接字)的選項。
ioctl風格的getsockopt()和setsockopt()系統函數都提供了控制套接字行為的模式。比方說,為了在Linux上設定TCP_NODELAY選項,你可以如下編寫代碼︰
intfd, on = 1;
…
/* 此處是創建套接字等操作,出於篇幅的考慮省略*/
…
setsockopt (fd, SOL_TCP, TCP_NODELAY, &on, sizeof (on));
儘管有許多TCP選項可供程式員操作,而我們卻最關注如何處置其中的兩個選項,它們是TCP_NODELAY 和 TCP_CORK,這兩個選項都對網路連接的行為具有重要的作用。許多UNIX系統都實現了TCP_NODELAY選項,但是,TCP_CORK則是Linux系統所獨有的而且相對較新;它首先在內核版本2.4上得以實現。此外,其他UNIX系統版本也有功能類似的選項,值得注意的是,在某種由BSD派生的系統上的TCP_NOPUSH選項其實就是TCP_CORK的一部分具體實現。
TCP_NODELAY和TCP_CORK基本上控制了包的“Nagle化”,Nagle化在這裡的含義是採用Nagle算法把較小的包組裝為更大的幀。John Nagle是Nagle算法的發明人,後者就是用他的名字來命名的,他在1984年首次用這種方法來嘗試解決福特汽車公司的網路擁塞問題(欲了解詳情請參看IETF RFC 896)。他解決的問題就是所謂的silly window syndrome ,中文稱“愚蠢視窗症候群”,具體含義是,因為普遍終端應用程式每產生一次擊鍵操作就會發送一個包,而典型情況下一個包會擁有一個位元組的數據載荷以及40個位元組長的包頭,於是產生4000%的過載,很輕易地就能令網路發生擁塞,。 Nagle化後來成了一種標準並且立即在網際網路上得以實現。它現下已經成為缺省配置了,但在我們看來,有些場合下把這一選項關掉也是合乎需要的。
現下讓我們假設某個應用程式發出了一個請求,希望發送小塊數據。我們可以選擇立即發送數據或者等待產生更多的數據然後再一次發送兩種策略。如果我們馬上發送數據,那麼交互性的以及客戶/伺服器型的應用程式將極大地受益。例如,當我們正在發送一個較短的請求並且等候較大的附應時,相關過載與傳輸的數據總量相比就會比較低,而且,如果請求立即發出那麼附應時間也會快一些。以上操作可以透過設定套接字的TCP_NODELAY選項來完成,這樣就禁用了Nagle算法。
另外一種情況則需要我們等到數據量達到最大時才透過網路一次發送全部數據,這種數據傳輸模式有益於大量數據的通信性能,典型的應用就是檔案伺服器。應用Nagle算法在這種情況下就會產生問題。但是,如果你正在發送大量數據,你可以設定TCP_CORK選項禁用Nagle化,其模式正好同TCP_NODELAY相反(TCP_CORK 和 TCP_NODELAY 是互相排斥的)。下面就讓我們仔細分析下其工作原理。
假設應用程式使用sendfile()函數來轉移大量數據。應用協議通常要求發送某些訊息來預先解釋數據,這些訊息其實就是報頭內容。典型情況下報頭很小,而且套接字上設定了TCP_NODELAY。有報頭的包將被立即傳輸,在某些情況下(取決於內部的包計數器),因為這個包成功地被對方收到後需要請求對方確認。這樣,大量數據的傳輸就會被延遲而且產生了不必要的網路流量交換。
但是,如果我們在套接字上設定了TCP_CORK(可以比喻為在管道上插入“塞子”)選項,具有報頭的包就會填補大量的數據,所有的數據都根據大小自動地透過包傳輸出去。當數據傳輸完成時,最好取消TCP_CORK 選項設定給連接“拔去塞子”以便任一部分的幀都能發送出去。這同“塞住”網路連接同等重要。
總而言之,如果你肯定能一起發送多個數據集合(例如HTTP附應的頭和正文),那麼我們建議你設定TCP_CORK選項,這樣在這些數據之間不存在延遲。能極大地有益於WWW、FTP以及檔案伺服器的性能,同時也簡化了你的工作。示例代碼如下︰
intfd, on = 1;
…
/* 此處是創建套接字等操作,出於篇幅的考慮省略*/
…
setsockopt (fd, SOL_TCP, TCP_CORK, &on, sizeof (on)); /* cork */
write (fd, …);
fprintf (fd, …);
sendfile (fd, …);
write (fd, …);
sendfile (fd, …);
…
on = 0;
setsockopt (fd, SOL_TCP, TCP_CORK, &on, sizeof (on)); /* 拔去塞子 */
不幸的是,許多常用的程式並沒有考慮到以上問題。例如,Eric Allman編寫的sendmail就沒有對其套接字設定任何選項。
Apache HTTPD是網際網路上最流行的Web伺服器,它的所有套接字就都設定了TCP_NODELAY選項,而且其性能也深受大多數用戶的滿意。這是為什麼呢?答案就在於實現的差別之上。由BSD衍生的TCP/IP協議棧(值得注意的是FreeBSD)在這種狀況下的操作就不同。當在TCP_NODELAY 模式下提交大量小數據塊傳輸時,大量訊息將按照一次write()函數調用發送一塊數據的模式發送出去。然而,因為負責請求交付確認的記數器是面向位元組而非面向包(在Linux上)的,所以引入延遲的機率就降低了很多。結果僅僅和全部數據的大小有關係。而 Linux 在第一包到達之後就要求確認,FreeBSD則在進行如此操作之前會等待好幾百個包。
在Linux系統上,TCP_NODELAY的效果同習慣於BSD TCP/IP協議棧的開發者所期望的效果有很大不同,而且在Linux上的Apache性能表現也會更差些。其他在Linux上頻繁採用TCP_NODELAY的應用程式也有同樣的問題。
相得益彰
你的數據傳輸並不需要總是準確地遵守某一選項或者其它選擇。在那種情況下,你可能想要採取更為靈活的措施來控制網路連接︰在發送一系列當作單一消息的數據之前設定TCP_CORK,而且在發送應立即發出的短消息之前設定TCP_NODELAY。
把零拷貝和sendfile() 系統函數結合起來(前文有述)可以顯著地提升系統整體效率並且降低CPU負載。我們採用這一技術為Swsoft’s Virtuozzo公司開發了基於名稱的主機托管子系統,實踐經驗表明,該技術可以在裝備350-MHz Pentium II CPU的PC上實現每秒9000個HTTP請求,這一成績在以前幾乎是不可能實現的。性能上的提升顯而易見。
沒有留言:
張貼留言