實作并發伺服器的方式有多種,下面說一下我了解到的幾種解決方案。
方案一:多程序并發伺服器
主程序監聽、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)。