select和poll的特点是:用户给内核一张 需要查看的文件描述符表,内核必须处理表中的每一个文件描述符,当这个表变大时,程序性能降低。
epoll将事件监视器的注册和事件监视工作分离,以避开这个问题。
// @size : 用于提示内核要监控的文件描述符数目,并非最大数目,用于提升性能 int epoll_create(int size); int epoll_create1(int flags); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *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 */ }; int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
#define MAX_EVENTS 10 struct epoll_event ev, events[MAX_EVENTS]; int listen_sock, conn_sock, nfds, epollfd; /* Code to set up listening socket, 'listen_sock', (socket(), bind(), listen()) omitted */ epollfd = epoll_create1(0); if (epollfd == -1) { perror("epoll_create1"); 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_wait"); exit(EXIT_FAILURE); } for (n = 0; n < nfds; ++n) { if (events[n].data.fd == listen_sock) { conn_sock = accept(listen_sock, (struct sockaddr *) &addr, &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); } } }
若将epoll_ctl 的 event.events 设置为 EPOLLET,则使用边缘触发。
比如,如下事件
若使用水平触发,则在调用2立即返回,表示读管道不会阻塞。
若使用边缘触发,则在调用1发生返回,说明 即使 管道已经可读,但epoll_wait也不会返回,除非再次发生写管道事件。
水平触发是默认行为,边缘触发需要使用不同的程序设计方法,通常用于非阻挡IO并且仔细检测EAGAIN.