天天看點

select,poll,epoll

1. Epoll是何方神聖?

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

其實在Linux下設計并發網絡程式,向來不缺少方法,比如典型的Apache模型(Process Per Connection,簡稱PPC),TPC(Thread 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缺點的2和3它都沒有改掉。

3. Epoll的提升

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

3.1. Epoll沒有最大并發連接配接的限制,上限是最大可以打開檔案的數目,這個數字一般遠大于2048, 一般來說這個數目和系統記憶體關系很大,具體數目可以cat /proc/sys/fs/file-max察看。

3.2. 效率提升,Epoll最大的優點就在于它隻管你“活躍”的連接配接,而跟連接配接總數無關,是以在實際的網絡環境中,Epoll的效率就會遠遠高于select和poll。

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檔案描述符上的事件:注冊、修改、删除。其中參數epfd是epoll_create()建立Epoll專用的檔案描述符。相對于select模型中的FD_SET和FD_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.當事件多路分離單線程無法滿足并發需求時,将事件多路分離的線程擴充成線程池  

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

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

select每次都需要重新設定所要監控的fd_set(因為調用之後會改變其内容),這增加了程式開銷。

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效率問題

因為每次調用時都會對連接配接進行線性周遊,是以随着FD的增加會造成周遊速度慢的“線性下降性能問題”。

同上

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

3、 消息傳遞方式

核心需要将消息傳遞到使用者空間,都需要核心拷貝動作

epoll通過核心和使用者空間共享一塊記憶體來實作的。

總結:

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

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

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

本文轉自   Taxing祥   51CTO部落格,原文連結:http://blog.51cto.com/12118369/1964181