epoll是Linux下多路復用IO接口select/poll的增強版本,它能顯著減少程序在大量并發連接中只有少量活躍的情況下的系統CPU利用率。
?
?
一、epoll的優點
支持一個進程打開大數目的socket描述符。
IO效率不隨FD數目增加而線性下降。
?
二、epoll的使用
epoll有2種工作方式:LT和ET。?
LT(level?triggered,水平觸發)是缺省的工作方式,并且同時支持block和no-block?socket.在這種做法中,
內核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的fd進行IO操作。如果你不作任操作,
內核還是會繼續通知你的,所以,這種模式編程出錯誤可能性要小一點。傳統的select/poll都是這種模型
的代表。?
?
ET?(edge-triggered,邊緣觸發)是高速工作方式,只支持no-block?socket。在這種模式下,當描述符從未
就緒變為就緒時,內核通過epoll告訴你。然后它會假設你知道文件描述符已經就緒,并且不會再為那個文
件描述符發送更多的就緒通知,直到你做了某些操作導致那個文件描述符不再為就緒狀態了(比如,你在
發送,接收或者接收請求,或者發送接收的數據少于一定量時導致了一個EWOULDBLOCK?錯誤)。但是
請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),內核不會發送更多的通知
(only?once)。
?
epoll相關的系統調用有3個:epoll_create,?epoll_ctl和epoll_wait。在頭文件<sys/epoll.h>
?
1.?int?epoll_create(int?size);
創建一個epoll句柄,即圖中的epfd, 用來監聽事件,?size用來告訴內核這個監聽的數目一共有多大。
這個參數不同于select()中的第一個參數,給出最大監聽的fd+1的值。需要注意的是,當創建好epoll句柄后,它就是會占用一個fd值,所以在使用完epoll后,必須調用close()關閉,否則可能導致fd被耗盡。
?
2.?int?epoll_ctl(int?epfd,?int?op,?int?fd,?struct?epoll_event?*event);
參數op是操作類型, 使用這個方法完成3種操作:
EPOLL_CTL_ADD:注冊新的fd到epfd中;
EPOLL_CTL_MOD:修改已經注冊的fd的監聽事件;
EPOLL_CTL_DEL:從epfd中刪除一個fd;
(1) 注冊新事件
C代碼 ?
struct?epoll_event?ev; ??ev.data.fd?=?fd; ??ev.events?=?EPOLLIN; ??epoll_cntl(epfd,?EPOOL_CTL_ADD,?fd,?&ev);?? struct epoll_event ev; ev.data.fd = fd; ev.events = EPOLLIN; epoll_cntl(epfd, EPOOL_CTL_ADD, fd, &ev);?
?
?
(2) 修改監聽事件
?
C代碼 ?
struct?epoll_event?*a_event?=?get_a_event()? ??struct?epoll_event?ev; ??ev.data.fd?=?a_event->data.fd; ??ev.events?=?a_event->events?|?EPOLLOUT; ??epoll_cntl(epfd,?EPOOL_CTL_MOD,?a_event->data.fd,?&ev);?? struct epoll_event *a_event = get_a_event() struct epoll_event ev; ev.data.fd = a_event->data.fd; ev.events = a_event->events | EPOLLOUT; epoll_cntl(epfd, EPOOL_CTL_MOD, a_event->data.fd, &ev);?
?
(3) 刪除事件
?
C代碼 ?
struct?epoll_event?*a_event?=?get_a_event()? ??struct?epoll_event?ev; ??ev.data.fd?=?a_event->data.fd; ??epoll_cntl(epfd,?EPOOL_CTL_DEL,?a_event->data.fd,?&ev);?? struct epoll_event *a_event = get_a_event() struct epoll_event ev; ev.data.fd = a_event->data.fd; epoll_cntl(epfd, EPOOL_CTL_DEL, a_event->data.fd, &ev);?
?
3種操作都使用了一個ev變量, 這個變量用來關聯fd和它的監聽事件, 是臨時的,?可以反復使用.
ev是一個struct epoll_event結構體, 結構如下:
?
typedef?union?epoll_data?{?????void?*ptr;?????int?fd;?????__uint32_t?u32;?????__uint64_t?u64;??}?epoll_data_t;????struct?epoll_event?{?????__uint32_t?events;??????epoll_data_t?data;???};?? ?
events可以是以下幾個宏的集合:
EPOLLIN?:表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
EPOLLOUT:表示對應的文件描述符可以寫;
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來);
EPOLLERR:表示對應的文件描述符發生錯誤;
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET:?將EPOLL設為邊緣觸發(Edge?Triggered)模式,這是相對于水平觸發(Level?Triggered)來說的。
注意多個socket可以設置不同的觸發模式
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里
3.?int?epoll_wait(int?epfd,?struct?epoll_event?*?events,?int?maxevents,?int?timeout);
等待事件的產生, 把產生的事件存放到events數組里, 如圖中所示, 調用epoll_wait后,?
fd 1和 fd 3和fd k產生了事件, 把它們分別存放到events[0], events[1], events[2]
參數epfd:epoll_create()函數返回的epoll句柄。
參數events:struct?epoll_event結構指針,用來從內核得到事件的集合。
參數?maxevents:告訴內核這個events有多大
參數?timeout:?等待時的超時時間,以毫秒為單位。
返回值:成功時,返回需要處理的事件數目。調用失敗時,返回0,表示等待超時。
?
?
三 epoll實例 -- 模擬HTTP服務器?
?
?
C代碼 ?
#include?<sys/socket.h> ??#include?<sys/wait.h> ??#include?<netinet/in.h> ??#include?<netinet/tcp.h> ??#include?<sys/epoll.h> ??#include?<sys/sendfile.h> ??#include?<sys/stat.h> ??#include?<unistd.h> ??#include?<stdio.h> ??#include?<stdlib.h> ??#include?<string.h> ??#include?<strings.h> ??#include?<fcntl.h> ??#include?<errno.h>? ????#define?MAX_EVENTS?10 ??#define?PORT?8080 ??????void?setnonblocking(int?sockfd)?{ ??????int?opts; ????????opts?=?fcntl(sockfd,?F_GETFL); ??????if(opts?<?0)?{ ??????????perror("fcntl(F_GETFL)\n"); ??????????exit(1); ??????} ??????opts?=?(opts?|?O_NONBLOCK); ??????if(fcntl(sockfd,?F_SETFL,?opts)?<?0)?{ ??????????perror("fcntl(F_SETFL)\n"); ??????????exit(1); ??????} ??} ????int?main(){ ??????struct?epoll_event?ev,?events[MAX_EVENTS]; ??????int?addrlen,?listenfd,?conn_sock,?nfds,?epfd,?fd,?i,?nread,?n; ??????struct?sockaddr_in?local,?remote; ??????char?buf[BUFSIZ]; ??????????????if(?(listenfd?=?socket(AF_INET,?SOCK_STREAM,?0))?<?0)?{ ??????????perror("sockfd\n"); ??????????exit(1); ??????} ??????bzero(&local,?sizeof(local)); ??????local.sin_family?=?AF_INET; ??????local.sin_addr.s_addr?=?htonl(INADDR_ANY);; ??????local.sin_port?=?htons(PORT); ??????if(?bind(listenfd,?(struct?sockaddr?*)?&local,?sizeof(local))?<?0)?{ ??????????perror("bind\n"); ??????????exit(1); ??????} ??????listen(listenfd,?20); ????????epfd?=?epoll_create(MAX_EVENTS); ??????if?(epfd?==?-1)?{ ??????????perror("epoll_create"); ??????????exit(EXIT_FAILURE); ??????} ????????ev.events?=?EPOLLIN; ??????ev.data.fd?=?listenfd; ??????if?(epoll_ctl(epfd,?EPOLL_CTL_ADD,?listenfd,?&ev)?==?-1)?{ ??????????perror("epoll_ctl:?listen_sock"); ??????????exit(EXIT_FAILURE); ??????} ????????for?(;;)?{ ??????????nfds?=?epoll_wait(epfd,?events,?MAX_EVENTS,?-1); ??????????if?(nfds?==?-1)?{ ??????????????perror("epoll_pwait"); ??????????????exit(EXIT_FAILURE); ??????????} ????????????for?(i?=?0;?i?<?nfds;?++i)?{ ??????????????fd?=?events[i].data.fd; ??????????????if?(fd?==?listenfd)?{ ??????????????????conn_sock?=?accept(listenfd, ??????????????????????????(struct?sockaddr?*)?&remote,?&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(epfd,?EPOLL_CTL_ADD,?conn_sock, ??????????????????????????????&ev)?==?-1)?{ ??????????????????????perror("epoll_ctl:?add"); ??????????????????????exit(EXIT_FAILURE); ??????????????????} ??????????????????continue; ??????????????}?? ??????????????if?(events[i].events?&?EPOLLIN)?{ ??????????????????n?=?0; ??????????????????while?((nread?=?read(fd,?buf?+?n,?BUFSIZ-1))?>?0)?{ ??????????????????????n?+=?nread; ??????????????????} ??????????????????buf[n]?=?'\0'; ????????????????????ev.data.fd?=?fd; ??????????????????ev.events?=?events[i].events?|?EPOLLOUT; ??????????????????if?(epoll_ctl(epfd,?EPOLL_CTL_MOD,?fd,?&ev)?==?-1)?{ ??????????????????????perror("epoll_ctl:?mod"); ??????????????????} ??????????????} ??????????????if?(events[i].events?&?EPOLLOUT)?{ ??????????????????sprintf(buf,?"HTTP/1.1?200?OK\r\nContent-Length:?%d\r\n\r\nHello?World",?11); ??????????????????n?=?strlen(buf); ??????????????????if?(write(fd,?buf,?n)?<?n)?{ ??????????????????????perror("write"); ??????????????????} ??????????????????close(fd); ??????????????} ??????????} ??????} ????????return?0; ??}?? #include <sys/socket.h> #include <sys/wait.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <sys/epoll.h> #include <sys/sendfile.h> #include <sys/stat.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <fcntl.h> #include <errno.h> #define MAX_EVENTS 10 #define PORT 8080 //設置socket連接為非阻塞模式 void setnonblocking(int sockfd) { int opts; opts = fcntl(sockfd, F_GETFL); if(opts < 0) { perror("fcntl(F_GETFL)\n"); exit(1); } opts = (opts | O_NONBLOCK); if(fcntl(sockfd, F_SETFL, opts) < 0) { perror("fcntl(F_SETFL)\n"); exit(1); } } int main(){ struct epoll_event ev, events[MAX_EVENTS]; int addrlen, listenfd, conn_sock, nfds, epfd, fd, i, nread, n; struct sockaddr_in local, remote; char buf[BUFSIZ]; //創建listen socket if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("sockfd\n"); exit(1); } bzero(&local, sizeof(local)); local.sin_family = AF_INET; local.sin_addr.s_addr = htonl(INADDR_ANY);; local.sin_port = htons(PORT); if( bind(listenfd, (struct sockaddr *) &local, sizeof(local)) < 0) { perror("bind\n"); exit(1); } listen(listenfd, 20); epfd = epoll_create(MAX_EVENTS); if (epfd == -1) { perror("epoll_create"); exit(EXIT_FAILURE); } ev.events = EPOLLIN; ev.data.fd = listenfd; if (epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev) == -1) { perror("epoll_ctl: listen_sock"); exit(EXIT_FAILURE); } for (;;) { nfds = epoll_wait(epfd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_pwait"); exit(EXIT_FAILURE); } for (i = 0; i < nfds; ++i) { fd = events[i].data.fd; if (fd == listenfd) { conn_sock = accept(listenfd, (struct sockaddr *) &remote, &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(epfd, EPOLL_CTL_ADD, conn_sock, &ev) == -1) { perror("epoll_ctl: add"); exit(EXIT_FAILURE); } continue; } if (events[i].events & EPOLLIN) { n = 0; while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) { n += nread; } buf[n] = '\0'; ev.data.fd = fd; ev.events = events[i].events | EPOLLOUT; if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev) == -1) { perror("epoll_ctl: mod"); } } if (events[i].events & EPOLLOUT) { sprintf(buf, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\nHello World", 11); n = strlen(buf); if (write(fd, buf, n) < n) { perror("write"); } close(fd); } } } return 0; }?
轉載于:https://www.cnblogs.com/armlinux/archive/2011/11/26/2396774.html
總結
以上是生活随笔為你收集整理的学习使用epoll的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。