一、poll系统调用poll是System V引入的I/O多路复用函数它克服了select的一些限制如文件描述符数量上限。poll通过一个结构体数组来监视多个文件描述符的事件。1. 函数原型#include poll.h int poll(struct pollfd *fds, nfds_t nfds, int timeout);参数fds指向struct pollfd数组的指针每个元素描述一个待监视的文件描述符及其感兴趣的事件。nfdsfds数组中的元素个数。timeout超时时间毫秒。-1阻塞直到有事件发生0立即返回不阻塞0等待指定的毫秒数。返回值成功返回就绪有事件发生的文件描述符个数。超时返回0。出错返回-1并设置errno。struct pollfd定义如下struct pollfd { int fd; /* 文件描述符 */ short events; /* 等待的事件掩码 */ short revents; /* 实际发生的事件掩码 */ };events和revents是由以下标志按位或组成的位掩码POLLIN有数据可读。POLLOUT可写数据。POLLERR发生错误仅输出。POLLHUP挂起仅输出。等等。2. 使用示例struct pollfd fds[2]; fds[0].fd sockfd; fds[0].events POLLIN; fds[1].fd STDIN_FILENO; fds[1].events POLLIN; int ret poll(fds, 2, 5000); // 等待5秒 if (ret 0) { for (int i 0; i 2; i) { if (fds[i].revents POLLIN) { // 处理该文件描述符的读事件 } } } else if (ret 0) { // 超时处理 } else { perror(poll); }3. poll的特点与局限性优点没有最大文件描述符数量的限制基于链表存储受系统内存约束。接口相对简单支持多种事件类型。缺点每次调用都需要将pollfd数组从用户态拷贝到内核态当监视大量文件描述符时开销较大。内核检测到事件后仍需遍历整个数组以查找哪些描述符就绪线性扫描时间复杂度O(n)。无法动态修改监视的描述符集合需要重新组织数组并调用poll。只能工作在水平触发模式Level-Triggered, LT即只要文件描述符处于就绪状态每次poll都会报告该事件。二、epollLinux特有的高性能I/O事件通知机制epoll是Linux内核为处理大批量文件描述符而引入的增强版I/O多路复用接口它解决了poll和select的性能瓶颈。epoll通过内核事件表、回调机制和内存映射等技术实现了高效的I/O事件通知。1. 核心函数epoll提供三个系统调用(1)epoll_create#include sys/epoll.h int epoll_create(int size);功能创建一个epoll实例返回一个指向内核事件表的文件描述符称为epfd。参数size提示内核事件表的大小Linux 2.6.8之后被忽略但必须大于0。返回值成功返回新的文件描述符失败返回-1。(2)epoll_ctlint epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);功能对内核事件表进行控制添加、修改或删除一个监视的文件描述符。参数epfdepoll实例的文件描述符。op操作类型可取EPOLL_CTL_ADD添加fd到事件表。EPOLL_CTL_MOD修改fd上已注册的事件。EPOLL_CTL_DEL从事件表中删除fd。fd要操作的文件描述符。event指向struct epoll_event的指针描述感兴趣的事件和用户数据。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可读。EPOLLOUT可写。EPOLLRDHUP流套接字对端关闭连接。EPOLLPRI有紧急数据可读。EPOLLERR发生错误自动设置无需手动注册。EPOLLHUP挂起自动设置。EPOLLET设置为边缘触发模式Edge-Triggered, ET。EPOLLONESHOT事件只触发一次触发后需要重新注册。(3)epoll_waitint epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);功能等待事件表中的文件描述符产生事件。参数epfdepoll实例的文件描述符。events用户提供的数组用于存放内核返回的就绪事件。maxeventsevents数组的大小即最多返回多少个事件。timeout超时时间毫秒语义与poll相同。返回值成功返回就绪事件个数超时返回0失败返回-1。2. 使用示例服务器监听socketint epfd epoll_create(1); struct epoll_event ev, events[10]; ev.events EPOLLIN; ev.data.fd listen_sock; epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, ev); while (1) { int nfds epoll_wait(epfd, events, 10, -1); for (int i 0; i nfds; i) { if (events[i].data.fd listen_sock) { // 处理新连接 int conn accept(listen_sock, ...); ev.events EPOLLIN | EPOLLET; // 边缘触发 ev.data.fd conn; epoll_ctl(epfd, EPOLL_CTL_ADD, conn, ev); } else { // 处理已连接套接字的读写 } } }3. epoll的工作模式水平触发与边缘触发水平触发LTLevel-Triggered默认模式。当文件描述符就绪时epoll_wait会返回该事件如果程序没有一次性处理完所有数据下一次调用epoll_wait会再次报告该事件直到数据被处理完。这种模式编程简单不容易遗漏事件但可能重复触发。边缘触发ETEdge-Triggered需要设置EPOLLET标志。当文件描述符从未就绪变为就绪时epoll_wait仅返回一次该事件。如果程序没有处理完所有数据后续不再通知除非描述符再次出现新的状态变化。ET模式要求程序员必须一次性将数据全部读取或写入通常使用非阻塞I/O循环处理否则可能造成数据丢失或饥饿。ET模式效率更高减少了epoll的重复触发次数但编程复杂度较高。4. epoll的优势无文件描述符数量上限epoll监视的描述符数量只受系统内存限制。事件驱动避免线性扫描内核通过回调机制将就绪的描述符加入就绪队列epoll_wait直接返回就绪队列时间复杂度O(1)仅返回就绪个数。内存映射减少拷贝epoll使用mmap在内核和用户空间共享事件表避免了用户态到内核态的数据拷贝事件注册时仍需拷贝但相比poll的每次全量拷贝要少。支持边缘触发在高并发场景下可进一步减少系统调用次数。可修改监视事件通过epoll_ctl动态添加、删除、修改监视的描述符无需重新构建整个集合。三、select、poll、epoll对比总结特性selectpollepoll底层数据结构位数组fd_setpollfd数组链表红黑树就绪链表最大连接数有限通常1024无上限受内存限制无上限受内存限制事件集合拷贝每次调用都从用户态拷贝到内核态每次调用都从用户态拷贝到内核态使用epoll_ctl注册通过mmap共享减少拷贝查找就绪描述符方式线性遍历所有fd线性遍历所有fd直接返回就绪队列无需遍历工作模式仅LT仅LT支持LT和ET修改监视集需要重新构造fd_set并重调select需要重新组织pollfd数组并重调poll使用epoll_ctl动态增删改无需重建时间复杂度获取就绪fdO(n)O(n)O(1)就绪个数可移植性广泛支持POSIX广泛支持POSIXLinux特有四、适用场景建议select适用于连接数较少1024且对可移植性要求高的场景代码简单。poll相比select没有最大连接数限制但仍有线性遍历开销适合中等规模的连接数几千以内。epoll高并发服务器如C10K问题的首选尤其是当连接数巨大且活动连接比例较低时ET模式能最大化性能。但需注意epoll是Linux专属跨平台需考虑替代方案如libevent、libuv等封装库。