針對伺服器處理網路連線的幾種方式,unix網路程式設計裡給出了9種方案,並且對伺服器程序/執行緒的開銷做了乙個量化的比較。從個人經驗出發,覺得以下幾種方式是比較實用的:
1.最簡單的是堵塞accept,收到連線後fork程序(unix)或建立thread.原程序/執行緒繼續堵塞accept,創出來的程序執行緒只處理新連線上的客戶請求。如果忽略建立程序/執行緒的開銷,以及每個連線必須對應乙個程序/執行緒的話,做成這樣已經可以滿足絕大部分簡單的應用伺服器需要了。qmail的tcpserver用的也是這種模式。要注意的是,如果子程序不監聽連線,最好關掉繼承自父程序的原來listen的socket.由於fork的開銷比較大,如果對伺服器的響應非常苛求,這種方式還是不實用的。另外,fork乙個單執行緒的程序,會比fork乙個多執行緒的程序要快的多。
2.引入程序/執行緒池概念,也就是先建立一堆程序/執行緒,讓這些程序/執行緒來處理連線事件和客戶端業務。在多執行緒支援的系統裡,實現執行緒池還是很容易的,只讓乙個執行緒在accept上堵塞,收到連線後,喚醒乙個本來堵塞在某個互斥量上的執行緒來處理之或將其放入佇列等待處理。這樣處理客戶端連線的執行緒可以無限多(系統容量範圍內)。但在程序模式裡,因為除非使用fork讓子程序繼承連線控制代碼,否則跨程序處理accept返回的連線。(某些unix可以實現程序間傳遞控制代碼,win32也有程序複製控制代碼的做法,但這跟作業系統結合太緊,而且不是所有作業系統都支援,忽略之)
vcbear.mblogger.cn
3.解決上述程序問題就是讓所有程序都能監聽同乙個埠並accept連線,可以通過乙個程序建立了listen socket,並fork同樣進行listen/accept的子程序來實現。這裡要考慮驚群效應,也就是說有連線到來,所有堵塞在監聽socket上的程序可能都會被系統喚醒,但只有乙個程序能夠從tcp協議棧裡獲得連線。驚群效應引入了程序排程開銷,解決辦法是在accept上加鎖。同時只有乙個程序在呼叫accept.在獲得連線後,該程序不再呼叫accept,而是處理客戶端業務,直到客戶端退出,才重新回歸呼叫accept。 apache1.3.x使用的是類似的模式,可以從其http_main.c裡看到。
典型**如下
while(true) }
4.以上都只考慮了乙個程序/執行緒對應乙個連線的情況,當有大量的連線時,就可能會產生大量的執行緒。使用select可以讓乙個執行緒/程序處理多個連線。如下**
if( select( .... ) ) > 0 ) /*>0表示select集合裡有事件發生*/
else}}
}結合3的模式,比如建立10個都能監聽的程序,每個程序最多處理10個客戶端連線,那麼程序/連線數比就降低了10倍。但這種情況下就不能對監聽socket加鎖,無法避免驚群問題。可以看到,在select後的for,同樣可能占用不少cpu,比起系統的程序排程可能是有過之而無不及。在必須用單程序處理多個連線的case裡,是可以考慮這樣實現的。ps:unix網路程式設計裡提到,如果有多個程序需要堵塞在同乙個socket上,那麼堵塞在accept上比堵塞在select上要好。
5:說到select,在合適的地方使用select還是不錯的,尤其是讀socket,使用select可以有效的實現可超時的堵塞方式,而不是永久性堵塞。在網路程式設計裡冒把程序/執行緒堵塞致死的風險是很不應該的。所以最好把socket設定成非堵塞的,這樣讀函式可以立刻返回,讀到資料或產生錯誤,錯誤碼eagain/eintr/einval表示連線應該沒有斷開,可以繼續使用。
6:在windows上可以採用iocp的做法(參見n年前翻譯的文件:用單程序處理多個連線,讓作業系統去操心網路上的事件,並挑出來是哪個連線上產生了io。這樣把任務排程的細節放到了作業系統核心,避免了在應用層上的程序/執行緒開銷。據說linux/unix上有類似的epoll,懷疑apache2.0採用了這個技術,還沒有來得及研究。但如果通用**方案已經可以滿足要求,我覺得應該盡量避免使用和作業系統極度相關的**,比如win32的iocp。
7:考慮伺服器上的程序排程,程序限制,資訊共享是比較精細的事情。一般的實現就是做個共享記憶體公告板,程序競爭的讀寫公告板上的資訊,報告自己的狀態或獲取其他資訊。apache在這一塊是做的很漂亮的,它可以根據連線的忙閒程度,由乙個主程序來建立更多處理程序或控制空閒程序退出。過多的程序/執行緒存在還是會影響系統效率的(量化計算參考unix網路程式設計卷一的第27章)
8:一點注意:伺服器程式設計一定要設定 linger,否則客戶端主動socket關閉的時候,伺服器會持續2*time_wait的時間才真正斷開,造成大量的廢連線負擔。
rlinger.l_onoff = 1; // 開啟linegr開關
rlinger.l_linger = 0; // 設定延遲時間為 0 秒, 注意 tcpip立即關閉,但是有可能出現化身
setsockopt(nsockfd, sol_socket, so_linger, (char *)&rlinger, sizeof(rlinger)))
另外乙個選項so_reuseaddr也是server socket必須設定的。
無論採用什麼模式來編寫伺服器,需要關注的問題除了網路事件響應,程序/執行緒排程之外,還有伺服器本身的容量問題。最短的木板其實並不在於處理網路連線的模型本身,而是伺服器上的資料處理能力或網路頻寬,如果一台伺服器及其網路頻寬受理100個客戶端的業務就已經很吃力了(比如ftp,mud,p2p,其他應用服務...),那麼其socket伺服器就算能輕鬆處理10000個連線也沒有意義了。
關於網路程式設計(服務端)的一些筆記
針對伺服器處理網路連線的幾種方式,unix網路程式設計裡給出了 種方案,並且對伺服器程序 執行緒的開銷做了乙個量化的比較。從個人經驗出發,覺得以下幾種方式是比較實用的 1.最簡單的是堵塞accept,收到連線後fork程序 unix 或建立thread.原程序 執行緒繼續堵塞accept,創出來的程...
C 網路程式設計(服務端程式)
include include 載入靜態lib檔案或者載入動態dll檔案 pragma comment lib,ws2 32.lib void main if lobyte wsadata.wversion 1 hibyte wsadata.wversion 1 建立用於監聽的套接字 socket ...
linux網路程式設計 廣播服務端
解釋都在 裡 廣播接收服務端 include include include include include include include include define dbgprint printf define print printf define ip found ip found ip發...