利用TCP/IP選項優化數據傳輸
TCP_DEFER_ACCEPT
我們首先考慮的第1個選項是TCP_DEFER_ACCEPT(這是Linux系統上的叫法,其他一些操作系統上也有同樣的選項但使用不同的名字)。為了理解TCP_DEFER_ACCEPT選項的具體思想,我們有必要大致闡述一下典型的HTTP客戶/服務器交互過程。請回想下TCP是如何與傳輸數據的目標建立連接的。在網絡上,在分離的單元之間傳輸的信息稱為IP包(或IP 數據報)。一個包總有一個攜帶服務信息的包頭,包頭用于內部協議的處理,并且它也可以攜帶數據負載。服務信息的典型例子就是一套所謂的標志,它把包標記代表TCP/IP協議棧內的特殊含義,例如收到包的成功確認等等。通常,在經過“標記”的包里攜帶負載是完全可能的,但有時,內部邏輯迫使TCP/IP協議棧發出只有包頭的IP包。這些包經常會引發討厭的網絡延遲而且還增加了系統的負載,結果導致網絡性能在整體上降低。
現在服務器創建了一個套接字同時等待連接。TCP/IP式的連接過程就是所謂“3次握手”。首先,客戶程序發送一個設置SYN標志而且不帶數據負載的TCP包(一個SYN包)。服務器則以發出帶SYN/ACK標志的數據包(一個SYN/ACK包)作為剛才收到包的確認響應。客戶隨后發送一個ACK包確認收到了第2個包從而結束連接過程。在收到客戶發來的這個ACK包之后,服務器會喚醒一個接收進程等待數據到達。當3次握手完成后,客戶程序即開始把“有用的”的數據發送給服務器。通常,一個HTTP請求的量是很小的而且完全可以裝到一個包里。但是,在以上的情況下,至少有4個包將用來進行雙向傳輸,這樣就增加了可觀的延遲時間。此外,你還得注意到,在“有用的”數據被發送之前,接收方已經開始在等待信息了。
為了減輕這些問題所帶來的影響,Linux(以及其他的一些操作系統)在其TCP實現中包括了TCP_DEFER_ACCEPT選項。它們設置在偵聽套接字的服務器方,該選項命令內核不等待最后的ACK包而且在第1個真正有數據的包到達才初始化偵聽進程。在發送SYN/ACK包之后,服務器就會等待客戶程序發送含數據的IP包。現在,只需要在網絡上傳送3個包了,而且還顯著降低了連接建立的延遲,對HTTP通信而言尤其如此。這一選項在好些操作系統上都有相應的對等物。例如,在FreeBSD上,同樣的行為可以用以下代碼實現:
/* 為明晰起見,此處略去無關代碼 */
struct accept_filter_arg af = { dataready, };
setsockopt(s, SOL_SOCKET, SO_ACCEPTFILTER, af, sizeof(af));
這個特征在FreeBSD上叫做“接受過濾器”,而且具有多種用法。不過,在幾乎所有的情況下其效果與TCP_DEFER_ACCEPT是一樣的:服務器不等待最后的ACK包而僅僅等待攜帶數據負載的包。要了解該選項及其對高性能Web服務器的重要意義的更多信息請參考Apache文檔上的有關內容。
就HTTP客戶/服務器交互而言,有可能需要改變客戶程序的行為。客戶程序為什么要發送這種“無用的”ACK包呢?這是因為,TCP協議棧無法知道ACK包的狀態。如果采用FTP而非HTTP,那么客戶程序直到接收了FTP服務器提示的數據包之后才發送數據。在這種情況下,延遲的ACK將導致客戶/服務器交互出現延遲。為了確定ACK是否必要,客戶程序必須知道應用程序協議及其當前狀態。這樣,修改客戶行為就成為必要了。
對Linux客戶程序來說,我們還可以采用另一個選項,它也被叫做TCP_DEFER_ACCEPT。我們知道,套接字分成兩種類型,偵聽套接字和連接套接字,所以它們也各自具有相應的TCP選項集合。因此,經常同時采用的這兩類選項卻具有同樣的名字也是完全可能的。在連接套接字上設置該選項以后,客戶在收到一個
SYN/ACK包之后就不再發送ACK包,而是等待用戶程序的下一個發送數據請求;因此,服務器發送的包也就相應減少了。
TCP_QUICKACK
阻止因發送無用包而引發延遲的另一個方法是使用TCP_QUICKACK選項。這一選項與 CP_DEFER_ACCEPT不同,它不但能用作管理連接建立過程而且在正常數據傳輸過程期間也可以使用。另外,它能在客戶/服務器連接的任何一方設置。如果知道數據不久即將發送,那么推遲ACK包的發送就會派上用場,而且最好在那個攜帶數據的數據包上設置ACK 標志以便把網絡負載減到最小。當發送方肯定數據將被立即發送(多個包)時,TCP_QUICKACK選項可以設置為0。對處于“連接”狀態下的套接字該選項的缺省值是1,首次使用以后內核將把該選項立即復位為1(這是個一次性的選項)。
在某些情形下,發出ACK包則非常有用。ACK包將確認數據塊的接收,而且,當下一塊被處理時不至于引入延遲。這種數據傳輸模式對交互過程是相當典型的,因為此類情況下用戶的輸入時刻無法預測。在Linux系統上這就是缺省的套接字行為。在上述情況下,客戶程序在向服務器發送HTTP請求,而預先就知
道請求包很短所以在連接建立之后就應該立即發送,這可謂HTTP的典型工作方式。既然沒有必要發送一個純粹的ACK包,所以設置TCP_QUICKACK為0以提高性能是完全可能的。在服務器方,這兩種選項都只能在偵聽套接字上設置一次。所有的套接字,也就是被接受呼叫間接創建的套接字則會繼承原有套接字的所有選項。
通過TCP_CORK、TCP_DEFER_ACCEPT和TCP_QUICKACK選項的組合,參與每一HTTP交互的數據包數量將被降低到最小的可接受水平(根據TCP協議的要求和安全方面的考慮)。結果不僅是獲得更快的數據傳輸和請求處理速度而且還使客戶/服務器雙向延遲實現了最小化。
小結
網絡程序的性能優化顯然是一項復雜的任務。優化技術包括:盡可能使用零拷貝、用TCP_CORK及其等價選項組裝適當的數據包、把傳輸數據包的數量最小化以及延遲優化等。為了提升網絡、系統的性能和可伸縮性,有必要在程序代碼中聯合一致地采用以上各種可用方法。當然,清楚了解TCP/IP協議棧和操作系統的內部工作原理也是必要的
發布者:博子
評論