一、概述
epoll是Linux内核提供的一种高效的I/O多路复用机制,是使用Linux进行网络编程的必备技能之一。相比于select和poll,epoll具有更高的性能和更强的扩展性,能够支持更多的并发连接,并且不会因为连接数的增加而导致性能下降。
它的运作原理是通过一个文件描述符,将多个socket文件描述符上的I/O事件集中到一个地方进行处理,而不像select和poll那样需要每个socket文件描述符都进行轮询并处理。
二、epoll的基本使用
使用epoll的步骤比较简单,大致可以分为以下几步:
1. 创建一个epoll实例,并监听文件描述符
int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
其中,epoll_create用于创建一个epoll实例,size指定这个实例中所能包含的最大文件描述符数。而epoll_ctl用于向这个epoll实例中添加或删除文件描述符,op参数指定添加或删除,fd是需要添加或删除的文件描述符,event结构体则用于指定这个文件描述符所关注的事件类型,如可读事件、可写事件等。
2. 调用epoll_wait进行等待
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
epoll_wait会阻塞直到有一个或多个文件描述符上发生了关注的事件,或者超时,或者被信号中断。它将等待的事件填充到events数组中,并返回实际触发事件的文件描述符数目。
三、epoll的高级应用
1. 边缘触发模式
epoll可以选择边缘触发和水平触发两种模式,边缘触发模式是指只有当有新的状态变化时才会触发,而水平触发则是一直触发,直到该事件被处理。边缘触发可以减少不必要的事件处理,提高效率,但是需要注意边缘触发的处理方式,需要一次性处理完所有的事件。
2. ET与LT模式
epoll的工作模式可以分为ET和LT两种模式,ET模式是指只有当I/O事件的状态发生变化的时候,epoll才会通知用户应用程序来读取或者写入数据。而LT模式则是在需要就绪的时候会每次通知一次用户应用程序来处理,也就是无论是否读写完成都会通知用户一次。使用ET模式需要一次性读完所有数据,否则不能再次触发事件。
3. 优化epoll性能
在使用epoll时,需要注意一些可以优化性能的地方。比如使用epoll的时候建议使用EPOLLONESHOT,可以保证同一时刻只有一个线程处理同一事件。
四、代码示例
下面是一个使用epoll的简单示例:
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<errno.h> #include<string.h> #include<sys/epoll.h> #include<sys/socket.h> #include<arpa/inet.h> #define MAX_EVENTS 1024 #define PORT 8888 int main(int argc, char **argv) { int listenfd,connfd,epfd,nfds,i,n; ssize_t nready; char buf[BUFSIZ]; struct sockaddr_in servaddr,cliaddr; socklen_t cliaddrlen; struct epoll_event ev, events[MAX_EVENTS]; // 创建socket并绑定监听 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(PORT); bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); listen(listenfd, 20); // 创建epoll实例并添加监听描述符 epfd = epoll_create(MAX_EVENTS); ev.data.fd = listenfd; ev.events = EPOLLIN; epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); // 循环等待,直到有事件触发 while (1) { nfds = epoll_wait(epfd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_wait"); continue; } // 循环处理所有事件 for (n = 0; n < nfds; ++n) { // 如果事件是listenfd,则处理新连接 if (events[n].data.fd == listenfd) { cliaddrlen = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddrlen); if (connfd < 0) { perror("accept"); continue; } ev.data.fd = connfd; ev.events = EPOLLIN; epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev); printf("new connection from %s:%d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port)); } // 否则处理已连接的客户端 else { if ((nready = read(events[n].data.fd, buf, BUFSIZ)) < 0) { if (errno == ECONNRESET) { close(events[n].data.fd); epoll_ctl(epfd, EPOLL_CTL_DEL, events[n].data.fd, NULL); } else { perror("read error"); } } else if (nready == 0) { close(events[n].data.fd); epoll_ctl(epfd, EPOLL_CTL_DEL, events[n].data.fd, NULL); printf("connection closed\n"); } else { write(STDOUT_FILENO, buf, nready); write(events[n].data.fd, buf, nready); } } } } close(listenfd); return 0; }