由於poll()和select()的局限,2.6內核引入了event poll(epoll)機制。雖然稍微復雜,但是epoll解決了它們共有的基本性能問題,並增加了一些新的特性。
poll()和select()每次調用都需要所有被監聽的文件描述符。內核必須遍歷所有被監視的文件描述符。當這個表變得很大時,成千上百的文件描述符,每次調用時的遍歷就成為了明顯的瓶頸。
1、創建一個新的epoll實例
使用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為下列值之一:
EINVAL size不是正數
ENFILE 系統達到打開文件數的上限
ENOMEN 沒有足夠內存完成該次操作。
標准調用如下:
int epfd; epfd = epoll_create (100); if (epfd <0 ) perror("epoll_create");
epoll_create返回的文件描述符需要用close()關閉。
2、控制 epoll
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");
3、等待Epoll事件
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);
4、邊沿觸發時間和水平觸發事件
EPOLL事件有兩種模型 Level Triggered (LT) 和 Edge Triggered (ET):
LT(level triggered,水平觸發模式)是缺省的工作方式,並且同時支持 block 和 non-block socket。在這種做法中,內核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的fd進行IO操作。如果你不作任何操作,內核還是會繼續通知你的,所以,這種模式編程出錯誤可能性要小一點。
ET(edge-triggered,邊緣觸發模式)是高速工作方式,只支持no-block socket。在這種模式下,當描述符從未就緒變為就緒時,內核通過epoll告訴你。然后它會假設你知道文件描述符已經就緒,並且不會再為那個文件描述符發送更多的就緒通知,等到下次有新的數據進來的時候才會再次出發就緒事件。
5、man 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); } } }