epoll是Linux下多路復用IO接口select/poll的增強版本,它能顯著提高程序在大量并發連接中只有少量活躍的情況下的系統CPU利用率,因為它會復用文件描述符集合來傳遞結果而不用迫使開發者每次等待事件之前都必須重新準備要被偵聽的文件描述符集合,另一點原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那些被內核IO事件異步喚醒而加入Ready隊列的描述符集合就行了。
目前epell是linux大規模并發網絡程序中的熱門首選模型。
epoll除了提供select/poll那種IO事件的電平觸發(Level Triggered)外,還提供了邊沿觸發(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態,減少epoll_wait/epoll_pwait的調用,提高應用程序效率。
可以使用cat命令查看一個進程可以打開的socket描述符上限。
cat /proc/sys/fs/file-max
如有需要,可以通過修改配置文件的方式修改該上限值。
sudo vi /etc/security/limits.conf在文件尾部寫入以下配置,soft軟限制,hard硬限制。如下圖所示。* soft nofile 65536* hard nofile 100000
基礎API
- 創建一個epoll句柄,參數size用來告訴內核監聽的文件描述符的個數,跟內存大小有關。
#include <sys/epoll.h>int epoll_create(int size) size:監聽數目
- 控制某個epoll監控的文件描述符上的事件:注冊、修改、刪除。
#include <sys/epoll.h>int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)epfd: 為epoll_creat的句柄op: 表示動作,用3個宏來表示:EPOLL_CTL_ADD (注冊新的fd到epfd),EPOLL_CTL_MOD (修改已經注冊的fd的監聽事件),EPOLL_CTL_DEL (從epfd刪除一個fd);event: 告訴內核需要監聽的事件struct epoll_event {__uint32_t events; /* Epoll events */epoll_data_t data; /* User data variable */};typedef union epoll_data {void *ptr;int fd;uint32_t u32;uint64_t u64;} epoll_data_t;EPOLLIN : 表示對應的文件描述符可以讀(包括對端SOCKET正常關閉)EPOLLOUT: 表示對應的文件描述符可以寫EPOLLPRI: 表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來)EPOLLERR: 表示對應的文件描述符發生錯誤EPOLLHUP: 表示對應的文件描述符被掛斷;EPOLLET: 將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對于水平觸發(Level Triggered)而言的EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里
- 等待所監控文件描述符上有事件的產生,類似于select()調用。
#include <sys/epoll.h>int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)events: 用來存內核得到事件的集合,maxevents: 告之內核這個events有多大,這個maxevents的值不能大于創建epoll_create()時的size,timeout: 是超時時間-1: 阻塞0: 立即返回,非阻塞>0: 指定毫秒返回值: 成功返回有多少文件描述符就緒,時間到時返回0,出錯返回-1
?
server
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h> #include <errno.h> #include "wrap.h"#define MAXLINE 80 #define SERV_PORT 6666 #define OPEN_MAX 1024int main(int argc, char *argv[]) {int i, j, maxi, listenfd, connfd, sockfd;int nready, efd, res;ssize_t n;char buf[MAXLINE], str[INET_ADDRSTRLEN];socklen_t clilen;int client[OPEN_MAX];struct sockaddr_in cliaddr, servaddr;struct epoll_event tep, ep[OPEN_MAX];listenfd = Socket(AF_INET, SOCK_STREAM, 0);bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(SERV_PORT);Bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));Listen(listenfd, 20);for (i = 0; i < OPEN_MAX; i++)client[i] = -1;maxi = -1;efd = epoll_create(OPEN_MAX);if (efd == -1)perr_exit("epoll_create");tep.events = EPOLLIN; tep.data.fd = listenfd;res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);if (res == -1)perr_exit("epoll_ctl");while (1) {nready = epoll_wait(efd, ep, OPEN_MAX, -1); /* 阻塞監聽 */if (nready == -1)perr_exit("epoll_wait");for (i = 0; i < nready; i++) {if (!(ep[i].events & EPOLLIN))continue;if (ep[i].data.fd == listenfd) {clilen = sizeof(cliaddr);connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));for (j = 0; j < OPEN_MAX; j++) {if (client[j] < 0) {client[j] = connfd; /* save descriptor */break;}}if (j == OPEN_MAX)perr_exit("too many clients");if (j > maxi)maxi = j; /* max index in client[] array */tep.events = EPOLLIN; tep.data.fd = connfd;res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);if (res == -1)perr_exit("epoll_ctl");} else {sockfd = ep[i].data.fd;n = Read(sockfd, buf, MAXLINE);if (n == 0) {for (j = 0; j <= maxi; j++) {if (client[j] == sockfd) {client[j] = -1;break;}}res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);if (res == -1)perr_exit("epoll_ctl");Close(sockfd);printf("client[%d] closed connection\n", j);} else {for (j = 0; j < n; j++)buf[j] = toupper(buf[j]);Writen(sockfd, buf, n);}}}}close(listenfd);close(efd);return 0; }
client
/* client.c */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include "wrap.h"#define MAXLINE 80 #define SERV_PORT 6666int main(int argc, char *argv[]) {struct sockaddr_in servaddr;char buf[MAXLINE];int sockfd, n;sockfd = Socket(AF_INET, SOCK_STREAM, 0);bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);servaddr.sin_port = htons(SERV_PORT);Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));while (fgets(buf, MAXLINE, stdin) != NULL) {Write(sockfd, buf, strlen(buf));n = Read(sockfd, buf, MAXLINE);if (n == 0)printf("the other side has been closed.\n");elseWrite(STDOUT_FILENO, buf, n);}Close(sockfd);return 0; }
?