如果您具有基於 unix® 的程式設計經驗,那麼您可能會在一定程度上苦惱於如何提高您的網路吞吐量。本文介紹了一些有價值的技術,使用本文中描述的這些方法,您可以最大程度地利用您的頻寬,並實現顯著的效能提公升。引言
任何具有 unix® 系統程式設計經驗的人都會苦惱於如何提高網路吞吐量以及磁碟 i/o(在某些情況下)。本文介紹了協議實現者所使用的一些高階程式設計技術,它們可以幫助您充分利用現有的頻寬。(本文並不打算介紹如何優化您的作業系統 (os)、配置核心、或者系統調整。)
儘管特定協議的可用頻寬受到 shannon 定律和其他一些因素(如網路使用模式)的限制,但大多數時候是因為低劣的程式設計或者未經過實驗的編碼導致網路資源的使用率無法達到最佳狀態。
效能增強不僅僅是一種科學,而且還是一門藝術。要獲得最佳的端到端吞吐量,您必須使用各種工具以度量效能、識別瓶頸,並消除它們,或者使得它們的影響最小化。通過一些簡單且直觀的科學方法,您可以快速地獲得顯著的效能提公升。
流水線和持久連線
流水線 是 cpu 所使用的乙個眾所周知的概念,它用於減少取指令-解碼-執行 週期中出現的延遲。在獲取每條指令期間會出現某種延遲,可以通過預取指令並對其進行儲存以用於後續的執行,從而避免延遲。可以將相同的概念應用於網路,正如本部分中所描述的。
可以提前通知伺服器在處理完當前請求後客戶所關心的是哪些內容,而不是使伺服器按老一套方式處理來自客戶的請求。伺服器維護乙個掛起請求佇列,依次地執行它們,而不是先執行乙個請求,然後再讀取下乙個請求,等等。這樣做提高了互動式應用程式的響應,並且與任何其他技術相比,可以更有效地減少延遲。
但是,並不總是能夠這樣做。即使在它可以起到作用的情況下,有時候伺服器佇列也可能耗盡。這種情況非常少見,並且大多數時候流水線都可以很好地工作。所有常見的協議,包括 http 1.1、nntp、x11 等,都通過某種方式使用了流水線。
持久的傳輸控制協議(tran**ission control protocol,tcp)連線
如果您打算為每個請求或者事務發起不同的 tcp 連線,那麼上述的技術很明顯會失效。您應該重用現有的連線,當然這並不是唯一的原因。在建立和拆除連線的過程中,tcp 握手可能會帶來巨大的開銷,當然最好能夠避免這個開銷。
如果不能正確地關閉 tcp 連線,那麼可能很快會給您的 unix 系統帶來各種各樣的麻煩。另乙個因素是新的連線中的協議開銷。您希望確保網路盡可能用於實際資料,而不是交換 header 以及其他控制資訊。圖 1
顯示了流水線和普通處理。
如何將這轉換為程式設計實現呢?使用現有的 tcp 連線非常容易,但實現流水線並不是那麼簡單。協議設計必須考慮跟蹤掛起的請求,並確定某個響應對應於哪個請求。
下乙個部分將說明能夠起到幫助作用的其他機制。
非阻塞的 i/o、select(2) 和 poll(2)
您應該熟悉這兩種程式設計模型:同步和非同步處理。本文介紹的流水線技術是使用非同步處理以提高效能的示例。同步程式設計將帶來簡單的設計、更簡單的**,但有時候效能比較糟糕,這是我們不希望看到的。為了避免這個問題,您必須使用其他的一些技巧,如非阻塞 i/o。
阻塞和非阻塞套接字大致上對應於同步和非同步處理,但並不是在網路級別,而是在作業系統級別。在使用阻塞套接字進行典型的socket write(2)
或者send(2)
操作時,使用者程序必須等待系統呼叫返回。核心負責將程序轉移到睡眠狀態、等待套接字做好寫入的準備、讀取 tcp 狀態**,等等。最後,將控制權返回給應用程式。
在使用非阻塞套接字的情況下,麻煩在於程式設計師必須確保該套接字是可寫入的,並且必須確保正確地寫入所有資料。這顯然給程式設計帶來了一些不便之處,並且必須了解一種新的習慣用法,但是在掌握了該方法之後,它將成為一種為所有網路**提供優秀效能的功能強大的工具。
當您的套接字變成非阻塞時,僅使用read(2)
和write(2)
,或recv(2)
或者send(2)
是不夠的。您必須使用附加的系統呼叫,如poll(2)
或者select(2)
,以便確定什麼時候可以對套接字進行寫入,或者從網路進行讀取操作。
其中一種選擇是使用poll(2)
來確定可寫入性(因為select(2)
無法完成這一項任務),並使用select(2)
來確定另一端的資料何時到達您的套接字。清單 1
詳細地介紹了乙個非阻塞 i/o 的示例。
清單 1. 乙個非阻塞 i/o 的示例
1 /******************************************2 * non blocking socket read with poll(2) *
3 * *
4 *****************************************/
55 void
6 poll_wait(int fd, int events)
7 20 }
2122 size_t
23 readall(int sock, char *buf, size_t n)
40 }
41 return (pos);
42 }
4344 size_t
45 readmore(int sock, char *buf, size_t n)
6061 return bytes;
62 }
6364 /******************************************
65 * non blocking socket write with poll(2) *
66 * *
67 *****************************************/
6869
70 void
71 poll_wait(int fd, int events)
72 85 }
8687
88 size_t
89 writenw(int fd, char *buf, size_t n)
90 107 }
108 return (pos);
109110 }
111112
internet 協議 (ip) 分片以及其他隨機的網路影響
sendfile(2)
是一種避免緩衝區複製開銷的技術,並且它直接將資料位從檔案系統轉移到網路。遺憾的是,這個系統呼叫在現代的 unix 系統中存在可移植性的問題;甚至在 openbsd 中沒有提供這個系統呼叫。出於可移植性的考慮,應該避免直接使用這個功能。
因為多餘的memcpy(2)
,所以sendfile(2)
可以減少延遲。與非阻塞 i/o 一樣,您可以使用這種技術在作業系統級別提高網路**的效能。
然而,在網路級別上可能存在一些影響因素。您可能聽說過 ip 分片。通常,它會對效能產生影響。分片和重組的代價非常高,並且儘管僅由中間路由器執行這項任務,但是它對吞吐量有很大的影響。
路徑最大傳輸單元(path maximum transfer unit,pmtu)發現技術可以幫助您避免對 ip 包進行分片。使用這種方法,您至少可以了解(或者猜測)在不對 ip 層進行分片的情況下可能通過的 tcp 報文段的大小。幸運的是,os tcp 層可以負責將協議資料劃分為避免 ip 分片的tcp 報文段。因為 tcp 是一種不存在任何訊息邊界的位元組流,所以這種方法非常有效,並且是最好的。但是請注意使用者資料報協議(user datagram protocol,udp),對它來說,這一點並不成立。
您還需要確保您的網路不會被一些無用的、並且有害的資料報所占用(將 windows 計算機隔離為乙個單獨的虛擬 lan)。在 unix 領域中,tcpdump、iftop 和頻寬監視工具(如 wmnet)都是非常有價值的。
總結本文提供了一些方法,可以幫助您充分地利用頻寬。組合使用各種不同的工具,可以提高效能。
最大程度地利用您的網路資源
如果您具有基於 unix 的程式設計經驗,那麼您可能會在一定程度上苦惱於如何提高您的網路吞吐量。本文介紹了一些有價值的技術,使用本文中描述的這些方法,您可以最大程度地利用您的頻寬,並實現顯著的效能提公升。引言 任何具有 unix 系統程式設計經驗的人都會苦惱於如何提高網路吞吐量以及磁碟 i o 在某...
高效能網路程式設計(五) IO復用與併發程式設計
對於伺服器的併發處理能力,我們需要的是 每一毫秒伺服器都能及時處理這一毫秒內收到的數百個不同tcp連線上的報文,與此同時,可能伺服器上還有數以十萬計的最近幾秒沒有收發任何報文的相對不活躍連線。同時處理多個並行發生事件的連線,簡稱為併發 同時處理萬計 十萬計的連線,則是高併發。伺服器的併發程式設計所追...
高效能網路程式設計MailList 熱點回顧 1
早在今年2月份,鑑於國內伺服器程式開發的同仁一直以來都沒有乙個固定的場所可以互相交流,我在google group上建了乙個有關 高效能網路程式設計的maillist 加入的方法見 這裡 現已經加入的600多名成員中,所屬領域較為廣泛 有從事網遊伺服器開發的,有從事im伺服器開發的,也有從事web伺...