Linux下併發伺服器的實現
實現併發伺服器的方式有多種,下面說一下我瞭解到的幾種解決方案。
方案一:多程序併發伺服器
主程序監聽、accept()連線,子程序負責處理業務邏輯和流的讀取。
缺點:程序需要佔用系統資源,存在硬體資源瓶頸,且排程,管理資源等系統開銷較大。
方案二:多執行緒併發伺服器
主執行緒監聽、accept()連線,子執行緒負責處理業務邏輯和流的讀取。
缺點是:
- 會頻繁地建立、銷燬執行緒,這對系統也是個不小的開銷。這個問題可以用執行緒池來解決。執行緒池是預先建立一部分執行緒,由執行緒池管理器來負責排程執行緒,達到執行緒複用的效果,避免了反覆建立執行緒帶來的效能開銷,節省了系統的資源。
- 要處理同步的問題,當多個執行緒請求同一個資源時,需要用鎖之類的手段來保證執行緒安全。同步處理不好會影響資料的安全性,也會拉低效能。
- 一個執行緒的崩潰會導致整個程序的崩潰。
多執行緒的適用場景是:提高響應速度,讓IO和計算相互重疊,降低延時。雖然多執行緒不能提高絕對效能,但是可以提高平均響應效能。
(方案一、二均不適宜高併發)
方案三:IO複用+多執行緒
利用epoll(或select、poll)監聽socket,每當有請求接入時,建立一個執行緒accept()連線請求,並在其獨立執行緒中與對應客戶端通訊。
缺點:同純粹的多執行緒一致,只是將主執行緒的阻塞等待連線,換成了事件輪詢,對單一程序來說並無區別。
方案四:單程序+多路IO複用(事件輪詢)模型(select、poll、epoll)
首先通過 socket() 來建立一個 sock 檔案描述符用來監聽客戶端的連線,然後將已連線的socket描述符加入epoll事件監聽池。
缺點:當監聽池中的多個事件同時觸發時,各個事件的處理並非是真正的併發進行的,而是按先後順序迴圈執行的。但是epoll相對於select、poll來說,這一點已經有很大優勢了,後兩者當檢測到事件時還需要迴圈檢測事件描述符集,需要消耗更多的時間。
方案五:利用事件驅動庫(libevent、libev、libuv等)來實現高併發
常見的事件驅動庫有 libevent 庫,還有作為 libevent 替代者的 libev 庫。這些庫會根據作業系統的特點選擇最合適的事件探測介面,並且加入了訊號 (signal) 等技術以支援非同步響應,這使得這些庫成為構建事件驅動模型的不二選擇。Linux下可使用 libev 庫替換 select 或 epoll 介面,實現高效穩定的伺服器模型。
Libev是一個基於Reactor模式的事件庫,效率較高、程式碼精簡(4.15版本8000多行,c語言編寫),是一個值得學習的輕量級事件驅動庫。 它的官網(http://libev.schmorp.de/)在國內已經沒法訪問了。但是我們仍然可以從github上下載其原始碼(https://github.com/enki/libev)。