因爲poll()和select()的侷限,2.6內核引入了event poll(epoll)機制。雖然稍微複雜,可是epoll解決了它們共有的基本性能問題,並增長了一些新的特性。c++
poll()和select()每次調用都須要全部被監聽的文件描述符。內核必須遍歷全部被監視的文件描述符。當這個表變得很大時,成千上百的文件描述符,每次調用時的遍歷就成爲了明顯的瓶頸。編程
使用epoll_create()或者epoll_cerate1()建立一個epoll上下文。這裏epoll_cerate1()是epoll_cerate()的擴展版本。數組
#include <sys/epoll.h> int epoll_create (int size)
調用成功後,epoll_create()建立一個epoll實例,返回與該實例關聯的文件描述符。這個文件描述符和真正的文件沒有關係,僅僅是爲了後續調用使用epoll而建立的。size參數告訴內核須要監聽的文件描述符數目,但不是最大值。傳遞一個適當的近似值會帶來性能的提高,但不須要給出確切的數字。出錯時,返回-1,設置errno爲下列值之一:socket
EINVAL size不是正數函數
ENFILE 系統達到打開文件數的上限性能
ENOMEN 沒有足夠內存完成該次操做。pwa
標準調用以下:進程
int epfd; epfd = epoll_create (100); if (epfd <0 ) perror("epoll_create");
epoll_create返回的文件描述符須要用close()關閉。事件
epoll_ctl 能夠向指定的epoll上下文加入或刪除文件描述符:內存
#include <sys/epoll.h> int epoll_ctl (int epfd, int op, int fd, struct epoll_event *event);
頭文件<sys/epoll.h>中定義了epoll event結構體
struct epoll_event { _u32 events; union { void * ptr; int fd; _u32 u32; _u64 u64; }data; };
epoll_ctl()成功調用將關聯epoll實例和epfd。參數op指定對fd要進行的操做。event參數描述epoll更具體的行爲
如下是參數op的有效值:
EPOLL_CTL_ADD 把fd指定的文件添加到epfd指定的epoll實例監聽集中,監聽event中定義的事件。
EPOLL_CTL_DEL 把fd指定的文件從epfd指定的epoll監聽集中刪除。
EPOLL_CTL_MOD 使用event改變在已有fd上的監聽行爲。
epoll_event結構體中的event參數列出了在給定文件描述符上監聽的事件。多個事件可使用位或運算同時指定。如下是有效值:
EPOLLERR 文件出錯。即便不設置這個標誌,這個事件也是被監聽的。
EPOLLET 使用邊沿觸發。默認是水平觸發。
EPOLLHUP 文件被掛起。即便不設置這個標誌,這個事件也是被監聽的。
EPOLLIN 文件未阻塞,可讀。
EPOLLONESHOT 在一次事件產生被處理以後,文件不在被監聽。必須再也不被監聽。必須使用EPOLL_CTL_MOD指定新的事件,以便從新監聽文件。
EPOLLOUT 文件未阻塞,可寫。
EPOLLPRI 高優先級的帶外數據可讀。
event_poll中的data字段由用戶使用。確認監聽事件後,data會被返回給用戶。一般將event.data.fd設定爲fd,這樣就能夠知道那個文件描述符觸發事件。
成功後,epoll_ctl()返回0.失敗返回-1,並設置errno爲下列值:
EBADF epfd不是一個有效的epoll實例,或者fd不是有效文件描述符。
EEXIST op爲EPOLL_CTL_ADD,可是fd已經與epfd關聯。
EINVAL epfd不是一個epoll實例,epfd和fd相同,或者op無效。
ENOENT op是EPOLL_CTL_MOD或者是EPOLL_CTL_DEL,可是fd沒有與epfd關聯。
ENOMEN 沒有足夠內存完成進程的請求。
EPERM fd不支持epoll。
在epfd實例中加入一個fd指定的監聽文件,使用以下代碼:
struct epoll_event event; int ret; event.data.fd = fd;/*return the fd to us later*/ event.events = EPOLLIN|EPOLLOUT ; ret = epoll_ctl (epfd,EPOLL_CTL_MOD,fd,&event); if (ret) perror ("epoll_ctl");
修改epfd實例中的fd上的一個監聽事件,可使用以下代碼:
struct epoll_event event; int ret; event.data.fd = fd;/*return the fd to us later*/ event.events = EPOLLIN ; ret = epoll_ctl (epfd,EPOLL_CTL_MOD,fd,&event); if (ret) perror ("epoll_ctl");
刪除一個fd監聽事件,可使用以下代碼:
struct epoll_event event; int ret; event.data.fd = fd;/*return the fd to us later*/ event.events = EPOLLIN ; ret = epoll_ctl (epfd,EPOLL_CTL_DEL,fd,&event); if (ret) perror ("epoll_ctl");
epoll_wait()等待給定epoll實例關聯的文件描述符上的事件:
#include <sys/epoll.h> int epoll_wait (int epfd, struct epoll_event * * events, int maxevents, int timeout);
對epoll_wait()的調用等待epoll實例epfd中的文件fd上的事件,時限爲timeout毫秒。成功返回,struct epoll_event *events指向包含epoll_event結構體(該結構體描述了每一個事件)的內存,且最多能夠有maxevents
個事件。這樣能夠根據events結構體來肯定哪些fd觸發了事件,從而作出相應的處理。返回值是事件數,出錯返回-1,並將errno設置爲如下值
EBADF epfd是無效文件描述符
EFAULT 進程對events指向的內存無寫權限
EINTR 系統調用在完成前被信號中斷
EINVAL epfd不是有效的epoll實例,或者maxevents小於等於0
若是timeout 爲0.即便沒有事件發生,調用也當即發生,此時調用返回0.若是timeout爲-1,調用將一直等待到有事件發生。
當調用epoll_wait()返回,epoll_event結構體中的events數組描述了一次等待發生的事件,最多返回maxevents個事件。data字段包含了用戶在調用epoll_ctl前的設置,
如文件的句柄,用來區分那個文件所發生的事件。
一個完整的epoll_wait()例子以下:
#define MAX_EVENTS 64 struct epoll_event * events = NULL; int nr_events, i, epfd; events = malloc (sizeof(struct epoll_event) * MAX_EVENTS); if (! events ){ perror("malloc"); exit(-1); } nr_events = epoll_wait (epfd,events,MAX_EVENTS,-1); if (nr_events < 0){ perror("epoll_wait"); free(events); exit (-1); } for (int i=0; i<nr_eventsl i++) printf("event = %d on fd = %d \n", events[i].events,events[i].data.fd);
EPOLL事件有兩種模型 Level Triggered (LT) 和 Edge Triggered (ET):
LT(level triggered,水平觸發模式)是缺省的工做方式,而且同時支持 block 和 non-block socket。在這種作法中,內核告訴你一個文件描述符是否就緒了,而後你能夠對這個就緒的fd進行IO操做。若是你不做任何操做,內核仍是會繼續通知你的,因此,這種模式編程出錯誤可能性要小一點。
ET(edge-triggered,邊緣觸發模式)是高速工做方式,只支持no-block socket。在這種模式下,當描述符從未就緒變爲就緒時,內核經過epoll告訴你。而後它會假設你知道文件描述符已經就緒,而且不會再爲那個文件描述符發送更多的就緒通知,等到下次有新的數據進來的時候纔會再次出發就緒事件。
setnonblocking()函數將socket文件設置爲非阻塞,由於使用的是ET模式。do_use_fd()是對此文件作出必定的處理,如讀寫等。
#define MAX_EVENTS 10 struct epoll_event ev, events[MAX_EVENTS]; int listen_sock, conn_sock, nfds, epollfd; /* Set up listening socket, 'listen_sock' (socket(), bind(), listen()) */ epollfd = epoll_create(10); if (epollfd == -1) { perror("epoll_create"); exit(EXIT_FAILURE); } ev.events = EPOLLIN; ev.data.fd = listen_sock; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) { perror("epoll_ctl: listen_sock"); exit(EXIT_FAILURE); } for (;;) { nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_pwait"); exit(EXIT_FAILURE); } for (n = 0; n < nfds; ++n) { if (events[n].data.fd == listen_sock) { conn_sock = accept(listen_sock, (struct sockaddr *) &local, &addrlen); if (conn_sock == -1) { perror("accept"); exit(EXIT_FAILURE); } setnonblocking(conn_sock); ev.events = EPOLLIN | EPOLLET; ev.data.fd = conn_sock; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, &ev) == -1) { perror("epoll_ctl: conn_sock"); exit(EXIT_FAILURE); } } else { do_use_fd(events[n].data.fd); } } }