1. Epoll是何方神聖?linux
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模型socket
1. 最大併發數限制,由於一個進程所打開的FD(文件描述符)是有限制的,由FD_SETSIZE設置,默認值是1024/2048,所以Select模型的最大併發數就被相應限制了。本身改改這個FD_SETSIZE?想法雖好,但是先看看下面吧…ide
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標誌位的數據結構來進行下一步處理。這樣所帶來的缺點是:
一、 單個進程可監視的fd數量被限制,即能監聽端口的大小有限。
通常來講這個數目和系統內存關係很大,具體數目能夠cat /proc/sys/fs/file-max察看。32位機默認是1024個。64位機默認是2048.
二、 對socket進行掃描時是線性掃描,即採用輪詢的方法,效率較低:
當套接字比較多的時候,每次select()都要經過遍歷FD_SETSIZE個Socket來完成調度,無論哪一個Socket是活躍的,都遍歷一遍。這會浪費不少CPU時間。若是能給套接字註冊某個回調函數,當他們活躍時,自動完成相關操做,那就避免了輪詢,這正是epoll與kqueue作的。
三、須要維護一個用來存放大量fd的數據結構,這樣會使得用戶空間和內核空間在傳遞該結構時複製開銷大
poll:
poll本質上和select沒有區別,它將用戶傳入的數組拷貝到內核空間,而後查詢每一個fd對應的設備狀態,若是設備就緒則在設備等待隊列中加入一項並繼續遍歷,若是遍歷完全部fd後沒有發現就緒設備,則掛起當前進程,直到設備就緒或者主動超時,被喚醒後它又要再次遍歷fd。這個過程經歷了屢次無謂的遍歷。
它沒有最大鏈接數的限制,緣由是它是基於鏈表來存儲的,可是一樣有一個缺點:
一、大量的fd的數組被總體複製於用戶態和內核地址空間之間,而無論這樣的複製是否是有意義。 二、poll還有一個特色是「水平觸發」,若是報告了fd後,沒有被處理,那麼下次poll時會再次報告該fd。
epoll:
epoll支持水平觸發和邊緣觸發,最大的特色在於邊緣觸發,它只告訴進程哪些fd剛剛變爲就需態,而且只會通知一次。還有一個特色是,epoll使用「事件」的就緒通知方式,經過epoll_ctl註冊fd,一旦該fd就緒,內核就會採用相似callback的回調機制來激活該fd,epoll_wait即可以收到通知
epoll的優勢:
一、沒有最大併發鏈接的限制,能打開的FD的上限遠大於1024(1G的內存上能監聽約10萬個端口);
二、效率提高,不是輪詢的方式,不會隨着FD數目的增長效率降低。只有活躍可用的FD纔會調用callback函數;
即Epoll最大的優勢就在於它只管你「活躍」的鏈接,而跟鏈接總數無關,所以在實際的網絡環境中,Epoll的效率就會遠遠高於select和poll。
三、 內存拷貝,利用mmap()文件映射內存加速與內核空間的消息傳遞;即epoll使用mmap減小複製開銷。
select、poll、epoll 區別總結:
一、支持一個進程所能打開的最大鏈接數
select |
單個進程所能打開的最大鏈接數有FD_SETSIZE宏定義,其大小是32個整數的大小(在32位的機器上,大小就是32*32,同理64位機器上FD_SETSIZE爲32*64),固然咱們能夠對進行修改,而後從新編譯內核,可是性能可能會受到影響,這須要進一步的測試。 |
poll |
poll本質上和select沒有區別,可是它沒有最大鏈接數的限制,緣由是它是基於鏈表來存儲的 |
epoll |
雖然鏈接數有上限,可是很大,1G內存的機器上能夠打開10萬左右的鏈接,2G內存的機器能夠打開20萬左右的鏈接 |
二、FD劇增後帶來的IO效率問題
select |
由於每次調用時都會對鏈接進行線性遍歷,因此隨着FD的增長會形成遍歷速度慢的「線性降低性能問題」。 |
poll |
同上 |
epoll |
由於epoll內核中實現是根據每一個fd上的callback函數來實現的,只有活躍的socket纔會主動調用callback,因此在活躍socket較少的狀況下,使用epoll沒有前面二者的線性降低的性能問題,可是全部socket都很活躍的狀況下,可能會有性能問題。 |
三、 消息傳遞方式
select |
內核須要將消息傳遞到用戶空間,都須要內核拷貝動做 |
poll |
同上 |
epoll |
epoll經過內核和用戶空間共享一塊內存來實現的。 |
總結:
綜上,在選擇select,poll,epoll時要根據具體的使用場合以及這三種方式的自身特色。
一、表面上看epoll的性能最好,可是在鏈接數少而且鏈接都十分活躍的狀況下,select和poll的性能可能比epoll好,畢竟epoll的通知機制須要不少函數回調。
二、select低效是由於每次它都須要輪詢。但低效也是相對的,視狀況而定,也可經過良好的設計改善