select,poll,epoll

1. Epoll是何方神聖?

Epoll可是當前在Linux下開發大規模併發網絡程序的熱門人選,Epoll Linux2.6內核中正式引入,和select相似,其實都I/O多路複用技術而已,並沒有什麼神祕的。

 

其實在Linux下設計併發網絡程序,向來不缺少方法,比如典型的Apache模型(Process Per Connection,簡稱PPC),TPCThread PerConnection)模型,以及select模型和poll模型,那爲何還要再引入Epoll這個東東呢?那還是有得說說的

2. 常用模型的缺點

如果不擺出來其他模型的缺點,怎麼能對比出Epoll的優點呢。

2.1 PPC/TPC模型

這兩種模型思想類似,就是讓每一個到來的連接一邊自己做事去,別再來煩我。只是PPC是爲它開了一個進程,而TPC開了一個線程。可是別煩我是有代價的,它要時間和空間啊,連接多了之後,那麼多的進程/線程切換,這開銷就上來了;因此這類模型能接受的最大連接數都不會高,一般在幾百個左右。

2.2 select模型

1. 最大併發數限制,因爲一個進程所打開的FD(文件描述符)是有限制的,由FD_SETSIZE設置,默認值是1024/2048,因此Select模型的最大併發數就被相應限制了。自己改改這個FD_SETSIZE?想法雖好,可是先看看下面吧

2. 效率問題,select每次調用都會線性掃描全部的FD集合,這樣效率就會呈現線性下降,把FD_SETSIZE改大的後果就是,大家都慢慢來,什麼?都超時了??!!

3. 內核/用戶空間內存拷貝問題,如何讓內核把FD消息通知給用戶空間呢?在這個問題上select採取了內存拷貝方法,每次調用select,都需要把fd集合從用戶態拷貝到內核態。

2.3 poll模型

基本上效率和select是相同的,select缺點的23它都沒有改掉。

3. Epoll的提升

把其他模型逐個批判了一下,再來看看Epoll的改進之處吧,其實把select的缺點反過來那就是Epoll的優點了。

3.1. Epoll沒有最大併發連接的限制,上限是最大可以打開文件的數目,這個數字一般遠大於2048, 一般來說這個數目和系統內存關係很大,具體數目可以cat /proc/sys/fs/file-max察看。

3.2. 效率提升,Epoll最大的優點就在於它只管你“活躍”的連接,而跟連接總數無關,因此在實際的網絡環境中,Epoll的效率就會遠遠高於selectpoll

3.3. 內存拷貝,Epoll在這點上使用了“共享內存”,這個內存拷貝也省略了。

 

4. Epoll爲什麼高效

Epoll的高效和其數據結構的設計是密不可分的,這個下面就會提到。

首先回憶一下select模型,當有I/O事件到來時,select通知應用程序有事件到了快去處理,而應用程序必須輪詢所有的FD集合,測試每個FD是否有事件發生,並處理事件

Epoll不僅會告訴應用程序有I/0事件到來,還會告訴應用程序相關的信息,這些信息是應用程序填充的,因此根據這些信息應用程序就能直接定位到事件,而不必遍歷整個FD集合。


6. 使用Epoll

既然Epoll相比select這麼好,那麼用起來如何呢?會不會很繁瑣啊先看看下面的三個函數吧,就知道Epoll的易用了。

 

intepoll_create(int size);

生成一個Epoll專用的文件描述符,其實是申請一個內核空間,用來存放你想關注的socket fd上是否發生以及發生了什麼事件。size就是你在這個Epoll fd上能關注的最大socket fd數,大小自定,只要內存足夠。

intepoll_ctl(int epfd, intop, int fd, structepoll_event *event);

控制某個Epoll文件描述符上的事件:註冊、修改、刪除。其中參數epfdepoll_create()創建Epoll專用的文件描述符。相對於select模型中的FD_SETFD_CLR宏。

intepoll_wait(int epfd,structepoll_event * events,int maxevents,int timeout);

等待I/O事件的發生;參數說明:

epfd:epoll_create() 生成的Epoll專用的文件描述符;

epoll_event:用於回傳代處理事件的數組;

maxevents:每次能處理的事件數;

timeout:等待I/O事件發生的超時值;

返回發生事件數。

相對於select模型中的select函數。


 

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Epoll模型主要負責對大量併發用戶的請求進行及時處理,完成服務器與客戶端的數據交互。其具體的實現步驟如下:
(a) 使用epoll_create()函數創建文件描述,設定將可管理的最大socket描述符數目。
(b) 創建與epoll關聯的接收線程,應用程序可以創建多個接收線程來處理epoll上的讀通知事件,線程的數量依賴於程序的具體需要。
(c) 創建一個偵聽socket描述符ListenSock;將該描述符設定爲非阻塞模式,調用Listen()函數在套接字上偵聽有無新的連接請求,在 epoll_event結構中設置要處理的事件類型EPOLLIN,工作方式爲 epoll_ET,以提高工作效率,同時使用epoll_ctl()註冊事件,最後啓動網絡監視線程。
(d) 網絡監視線程啓動循環,epoll_wait()等待epoll事件發生。
(e) 如果epoll事件表明有新的連接請求,則調用accept()函數,將用戶socket描述符添加到epoll_data聯合體,同時設定該描述符爲非阻塞,並在epoll_event結構中設置要處理的事件類型爲讀和寫,工作方式爲epoll_ET.
(f) 如果epoll事件表明socket描述符上有數據可讀,則將該socket描述符加入可讀隊列,通知接收線程讀入數據,並將接收到的數據放入到接收數據的鏈表中,經邏輯處理後,將反饋的數據包放入到發送數據鏈表中,等待由發送線程發送。

 

1.不要採用一個連接一個線程的方式,而是儘量利用操作系統的事件多路分離機制
如:UNIX下的 select  linux下的epoll BSD下的kqueue
或者使用這些機制的高層API (boost.asio&&ACE Reactor)
2.儘量使用異步I/O,而不是同步
3.當事件多路分離單線程無法滿足併發需求時,將事件多路分離的線程擴展成線程池  

兩種方式的區別主要體現在以下幾個方面:

  1. select所能控制的I/O數有限,這主要是因爲fd_set數據結構是一個有大小的,相當與一個定長所數組。

  2. select每次都需要重新設置所要監控的fd_set(因爲調用之後會改變其內容),這增加了程序開銷。

  3. select的性能要比epoll差,具體原因會在後續內容中詳細說明。

嗯,說道這個爲什麼select要差,那就要從這個select API說起了。這個傳進去一個數組,內部實現也不知道那個有哪個沒有,所以要遍歷一遍。假設說我只監控一個文件描述符,但是他是1000。那麼select需要遍歷前999個之後再來poll這個1000的文件描述符,而epoll則不需要,因爲在之前epoll_ctl的調用過程中,已經維護了一個隊列,所以直接等待事件到來就可以了。

epoll跟select都能提供多路I/O複用的解決方案。在現在的Linux內核裏有都能夠支持,其中epoll是Linux所特有,而select則應該是POSIX所規定,一般操作系統均有實現

select:

select本質上是通過設置或者檢查存放fd標誌位的數據結構來進行下一步處理。這樣所帶來的缺點是:

1、 單個進程可監視的fd數量被限制,即能監聽端口的大小有限。

      一般來說這個數目和系統內存關係很大,具體數目可以cat /proc/sys/fs/file-max察看。32位機默認是1024個。64位機默認是2048.

2、 對socket進行掃描時是線性掃描,即採用輪詢的方法,效率較低:

       當套接字比較多的時候,每次select()都要通過遍歷FD_SETSIZE個Socket來完成調度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間。如果能給套接字註冊某個回調函數,當他們活躍時,自動完成相關操作,那就避免了輪詢,這正是epoll與kqueue做的。

3、需要維護一個用來存放大量fd的數據結構,這樣會使得用戶空間和內核空間在傳遞該結構時複製開銷大

poll:

poll本質上和select沒有區別,它將用戶傳入的數組拷貝到內核空間,然後查詢每個fd對應的設備狀態,如果設備就緒則在設備等待隊列中加入一項並繼續遍歷,如果遍歷完所有fd後沒有發現就緒設備,則掛起當前進程,直到設備就緒或者主動超時,被喚醒後它又要再次遍歷fd。這個過程經歷了多次無謂的遍歷。

它沒有最大連接數的限制,原因是它是基於鏈表來存儲的,但是同樣有一個缺點:

1、大量的fd的數組被整體複製於用戶態和內核地址空間之間,而不管這樣的複製是不是有意義。                                                                                                                                      2、poll還有一個特點是“水平觸發”,如果報告了fd後,沒有被處理,那麼下次poll時會再次報告該fd。

epoll:

epoll支持水平觸發和邊緣觸發,最大的特點在於邊緣觸發,它只告訴進程哪些fd剛剛變爲就需態,並且只會通知一次。還有一個特點是,epoll使用“事件”的就緒通知方式,通過epoll_ctl註冊fd,一旦該fd就緒,內核就會採用類似callback的回調機制來激活該fd,epoll_wait便可以收到通知

epoll的優點:


1、沒有最大併發連接的限制,能打開的FD的上限遠大於1024(1G的內存上能監聽約10萬個端口);
2、效率提升,不是輪詢的方式,不會隨着FD數目的增加效率下降。只有活躍可用的FD纔會調用callback函數;
      即Epoll最大的優點就在於它只管你“活躍”的連接,而跟連接總數無關,因此在實際的網絡環境中,Epoll的效率就會遠遠高於select和poll。

3、 內存拷貝,利用mmap()文件映射內存加速與內核空間的消息傳遞;即epoll使用mmap減少複製開銷。

select、poll、epoll 區別總結:


1、支持一個進程所能打開的最大連接數

select

單個進程所能打開的最大連接數有FD_SETSIZE宏定義,其大小是32個整數的大小(在32位的機器上,大小就是32*32,同理64位機器上FD_SETSIZE爲32*64),當然我們可以對進行修改,然後重新編譯內核,但是性能可能會受到影響,這需要進一步的測試。

poll

poll本質上和select沒有區別,但是它沒有最大連接數的限制,原因是它是基於鏈表來存儲的

epoll

雖然連接數有上限,但是很大,1G內存的機器上可以打開10萬左右的連接,2G內存的機器可以打開20萬左右的連接

2、FD劇增後帶來的IO效率問題

select

因爲每次調用時都會對連接進行線性遍歷,所以隨着FD的增加會造成遍歷速度慢的“線性下降性能問題”。

poll

同上

epoll

因爲epoll內核中實現是根據每個fd上的callback函數來實現的,只有活躍的socket纔會主動調用callback,所以在活躍socket較少的情況下,使用epoll沒有前面兩者的線性下降的性能問題,但是所有socket都很活躍的情況下,可能會有性能問題。

3、 消息傳遞方式

select

內核需要將消息傳遞到用戶空間,都需要內核拷貝動作

poll

同上

epoll

epoll通過內核和用戶空間共享一塊內存來實現的。

總結:

綜上,在選擇select,poll,epoll時要根據具體的使用場合以及這三種方式的自身特點。

1、表面上看epoll的性能最好,但是在連接數少並且連接都十分活躍的情況下,select和poll的性能可能比epoll好,畢竟epoll的通知機制需要很多函數回調。

2、select低效是因爲每次它都需要輪詢。但低效也是相對的,視情況而定,也可通過良好的設計改善



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章