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) 註冊新事件
struct epoll_event ev; ev.data.fd = fd; ev.events = EPOLLIN; epoll_cntl(epfd, EPOOL_CTL_ADD, fd, &ev);
(2) 修改監聽事件
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) 刪除事件
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 events */
- epoll_data_t data; /* User data variable */
- };
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服務器
#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; }
運行程序後, 打開瀏覽器: