Linux——epoll

最好的參考資料:

1.師從互聯網。

2.man 7 epoll

3.http://bbs.chinaunix.net/thread-1740209-1-1.html

4.http://hi.baidu.com/firobd/blog/item/dcb4f251530d341d0cf3e3ee.html

5.http://www.cnblogs.com/dubingsky/archive/2009/07/22/1528695.html

6.http://bbs.chinaunix.net/thread-1740209-2-1.html

7.http://www.cppblog.com/converse/archive/2008/04/29/48482.html

 

第一條:概述

簡單說來 epoll 就是Linux內核解決大量的用戶併發地連接到服務器上,而爲程序員提供的開發大規模併發網絡程序的一種I/O 多路複用技術。epoll是Linux下多路複用IO接口select/poll的增強版本,本質上仍是I/O多路複用技術,所以他沒什麼好怕的,但是epoll能力卻非常強悍的。你會見到如此少的代碼卻能輕鬆的搞定如何接受大量用戶連接的問題,非常厲害。 

 

最早你可你在Kernel2.5.44中見到epoll的系統調用,2.6內核時被正式引進到glibc2.3.2中。epoll是linux特有的機制 類似與BSD的Kqueue和Solaris的/dev/poll。

第二條:Linux併發網絡編程模型

(0)Apache 模型,簡稱 PPC ( Process Per Connection ,):爲每個連接分配一個進程。主機分配給每個連接的時間和空間上代價較大,並且隨着連接的增多,大量進程間切換開銷也增長了。很難應對大量的客戶併發連接。

 

(1) TPC 模型( Thread Per Connection ):每個連接一個線程。和PCC類似。

 

(2) select 模型:I/O多路複用技術。

(2.1)每個連接對應一個描述。select模型受限於 FD_SETSIZE即進程最大打開的描述符數linux2.6.35爲1024,實際上linux每個進程所能打開描數字的個數僅受限於內存大小,然而在設計select的系統調用時,卻是參考FD_SETSIZE的值。可通過重新編譯內核更改此值,但不能根治此問題,對於百萬級的用戶連接請求  即便增加相應 進程數, 仍顯得杯水車薪呀。

 

(2.2)select每次都會掃描一個文件描述符的集合,這個集合的大小是作爲select第一個參數傳入的值。但是每個進程所能打開文件描述符若是增加了 ,掃描的效率也將減小。

(2.3)內核到用戶空間,採用內存複製傳遞文件描述上發生的信息。

 

(3)poll 模型:I/O多路複用技術。poll模型將不會受限於FD_SETSIZE,因爲內核所掃描的文件 描述符集合的大小是由用戶指定的,即poll的第二個參數。但仍有掃描效率和內存拷貝問題。

 

(4)pselect模型:I/O多路複用技術。同select。

 

(5)epoll模型:

(5.1)無文件描述字大小限制僅與內存大小相關

(5.2)epoll返回時已經明確的知道哪個socket fd發生了什麼事件,不用像select那樣再一個個比對。

(5.3)內核到用戶空間採用共享內存方式,傳遞消息。

第三條:epoll API

#include <sys/epoll.h>//epoll機制相關的所需的API和數據類型都在這個頭文件中

(0)int epoll_create (int  size) ;

(0.1)函數返回一個epoll專用的描述符epfd,epfd引用了一個新的epoll機制例程(instance.)。

(0.2)參數size是這個epoll專用描述符epfd所關聯的socketfd的最大個數。man手冊指出:The  size  is  not  the maximum size of the backing store but just a hint to the kernel about how to dimension internal structures.  (Nowadays, size  is ignored; see NOTES below.)從2,6.8內核就不使用這個參數,而是內核動態分配所需的數據結構。

(0.3)當我們不再需要epfd時,一定要調用close關閉他。當epfd被關閉時,kernel銷燬所引用的instance和釋放相關資源。

(1) int epoll_create1(int flags);

(1.1)正如man手冊所指出的epoll_create的參數size,其實已經被拋棄了,毫無用處。epoll_create1和epoll_create功效一樣,而且還添加了一個flags參數,其值如下:

enum  {

    EPOLL_CLOEXEC = 02000000,//在新建的epfd上設置FD_CLOEXEC。

    EPOLL_NONBLOCK = 04000   //新建的epfd設置爲非阻塞。

  };

(2)int epoll_ctl (int __epfd, int __op, int __fd, struct epoll_event *__event)  ;//設置epfd關聯的一個socketfd

(2.1)參數epfd的值設置爲epoll_create返回的epoll專用描述符。

(2.2)參數op值如下:

#define EPOLL_CTL_ADD 1 /* Add a file decriptor to the interface.  */關聯一個socketfd到epfd

#define EPOLL_CTL_DEL 2 /* Remove a file decriptor from the interface.  */刪除一個epfd已經關聯的socketfd

#define EPOLL_CTL_MOD 3 /* Change file decriptor epoll_event structure.  */更新一個epfd已經關聯的socketfd

(2.3)參數fd設置爲我們要操作的socketfd

(2.4)  參數event具體如何設置socketfd,其值如下:

 

typedef union epoll_data {//注意這是union結構~~~

               void        *ptr;

               int          fd;//一般都用這個成員

               uint32_t     u32;

               uint64_t     u64;

           } epoll_data_t;

struct epoll_event {//呵呵,這個數據結構就是epoll爲什麼如此高效的原因。

               uint32_t     events;      /* Epoll events *///對應的時間

               epoll_data_t data;        /* User data variable *///此值一般是關心的socketfd

           };

成員events對應的值如下,他表示相應的描述符發生的事件或狀態:

enum EPOLL_EVENTS

  {

    EPOLLIN = 0x001,//可讀

    EPOLLPRI = 0x002,//有緊急數據可讀,比如帶外數據

    EPOLLOUT = 0x004,//可寫

    EPOLLRDNORM = 0x040,

    EPOLLRDBAND = 0x080,

    EPOLLWRNORM = 0x100,

    EPOLLWRBAND = 0x200,

    EPOLLMSG = 0x400,

    EPOLLERR = 0x008,//出錯

    EPOLLHUP = 0x010,//掛斷

    EPOLLRDHUP = 0x2000,//連接斷開,或處於半關閉狀態(前提是對應的流socket,就是支持連接的socket)。man手冊中的說明:(since Linux 2.6.17)  Stream socket peer closed connection, or shut down writing  half  of connection.  (This flag is especially useful for writing simple code to detect peer shutdown when using Edge Triggered monitoring.)

 

    EPOLLONESHOT = (1 << 30),/*默認監聽一個socketfd之後並不把它從epfd關聯的socketfd集合中刪除,只清空socketfd對應的事件成員的值一次事件。EPOLLONESHOT表示設置socketfd爲監聽一次事件。當監聽完這次事件之後,從epfd關聯的socketfd集合中刪除監聽的socketfd,如果還需要繼續監聽這個socketfd的話,需要再次把這個socketfd加入到epfd關聯的socketfd集合(隊列)裏 */

 

    EPOLLET = (1 << 31)// 將epfd設爲邊緣觸發(Edge Triggered)模式,默認epfd是電平觸發(Level Triggered)。 下文細述。

  }; 

(3)int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

int epoll_pwait(int epfd, struct epoll_event *events,  int maxevents, int timeout, const sigset_t *sigmask);//想想pselect  

(3.1)參數events指向一個struct epoll_event類型的數組,內核用他來裝載發生的事件傳送給用戶。

(3.2)參數maxevents指明數組的大小。

(3.3)參數timeout:超時時間。

(3.4)參數sigmask:信號屏蔽集。epoll_pwait等價如下:

            sigset_t origmask;

           sigprocmask(SIG_SETMASK, &sigmask, &origmask);

           ready = epoll_wait(epfd, &events, maxevents, timeout);

           sigprocmask(SIG_SETMASK, &origmask, NULL);

epoll_wait等待epfd關聯的socketfd上發生事件,如果發生了內核把發生事件的socketfd和事件類型放到events數組當中(就是這個小小的細節就決定了epoll強悍的性能,因爲他不需要像select再輪詢所有的socketfd集合,去確定哪個socketfd 要去處理。同時內核也會將epfd關聯的socketfd的struct evpoll_event結構的事件類型成員(events)清空(不是epoll_wait函數的第二個參數events,要嚴重區分),所以如果下次你還要關注這個socketfd就需要用epoll_ctl的EPOLL_CTL_MOD(不是EPOLL_CTL_ADD,socketfd並未清空,只是事件類型清空)命令來重新設置我們關心socketfd的事件類型。重新設置這一步非常重要!!!

第四條:epoll工作模式

epoll有兩種工作方式

ET:Edge Triggered,邊緣觸發。僅當狀態發生變化時纔會通知,epoll_wait返回。換句話,就是對於一個事件,只通知一次。且只支持非阻塞的socket。

LT:Level Triggered,電平觸發(默認工作方式)。類似select/poll,只要還有沒有處理的事件就會一直通知,以LT方式調用epoll接口的時候,它就相當於一個速度比較快的poll.支持阻塞和不阻塞的socket。

第五條:FAQ  

來自互聯網,鏈接見上面 

1、單個epoll並不能解決所有問題,特別是你的每個操作都比較費時的時候,因爲epoll是串行處理的。 所以你有還是必要建立線程池來發揮更大的效能。 

2、如果fd被註冊到兩個epoll中時,如果有時間發生則兩個epoll都會觸發事件。

3、如果註冊到epoll中的fd被關閉,則其會自動被清除出epoll監聽列表。
4、如果多個事件同時觸發epoll,則多個事件會被聯合在一起返回。
5、epoll_wait會一直監聽epollhup事件發生,所以其不需要添加到events中。
6、爲了避免大數據量io時,et模式下只處理一個fd,其他fd被餓死的情況發生。linux建議可以在fd聯繫到的結構中增加ready位,然後epoll_wait觸發事件之後僅將其置位爲ready模式,然後在下邊輪詢ready fd列表。

 

 

 

 

 

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