linux下的各种I/O多路复用详解---select/poll/epoll 多路复用技术是一种通过共享物理信道实现多路信号传输的通信技术其核心原理是利用复用器将多路信号合成单一信号传输接收端通过分用器分离还原。该技术主要包括频分复用FDM、时分复用TDM、波分复用WDM、码分多址CDMA和空分多址SDMA等类型其中FDM通过划分频段实现ADSL数据传输TDM采用时分片机制支持T1载波传输WDM通过不同波长光信号在光纤中实现双向通信。该部分来源于百度百科select的概念和原理select是一种事件通知机制通常用于阻塞式 I/O 操作中它允许程序在多个文件描述符上进行监视一旦某个文件描述符变得可读、可写或出现异常select会通知程序进行相应的处理。select会在多个文件描述符上设置监视例如读、写、异常状态然后它会阻塞程序直到以下事件之一发生读事件某个文件描述符准备好读取数据如套接字接收到数据。写事件某个文件描述符准备好写入数据如套接字可以发送数据。异常事件文件描述符处于异常状态如套接字连接关闭。select会返回发生事件的文件描述符列表程序可以通过这些文件描述符来进一步处理数据。select原型int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);nfds最大文件描述符值加1。通常可以传入所有需要监视的文件描述符的最大值加1。readfds用于监视哪些文件描述符可以读取即数据已准备好读取。writefds用于监视哪些文件描述符可以写入。exceptfds用于监视哪些文件描述符发生了异常。timeout指定等待事件的最长时间。如果为NULL则select会阻塞直到有事件发生如果设置为0则为非阻塞模式否则设置为一个timeval结构体表示的超时时间。select的使用struct timeval timeout; timeout.tv_sec 5; // 等待最多5秒 timeout.tv_usec 0; int ready select(socket_fd 1, readfds, NULL, NULL, timeout); if (ready 0) { perror(select error); } else if (ready 0) { printf(timeout\n); } else { if (FD_ISSET(socket_fd, readfds)) { // 如果 socket_fd 可读进行读取操作 char buffer[1024]; int len read(socket_fd, buffer, sizeof(buffer)); if (len 0) { printf(Received data: %s\n, buffer); } } }select的不足文件描述符数量限制select的最大文件描述符数目是由fd_set中的位数限制的通常在 1024 或 4096 左右。超出这个数量select将无法处理。因此在高并发的应用场景下select可能不够高效。性能瓶颈当需要监视大量的文件描述符时select会不断地遍历所有文件描述符检查哪些描述符有事件发生。这可能导致性能瓶颈特别是在大规模的并发连接情况下。无法支持优先级select不支持文件描述符的优先级调度因此它处理多个文件描述符时是公平的不会根据优先级处理某些请求。poll的概念和原理poll是select的一个改进版本常用于多路复用I/O多路复用的场景它允许程序监控多个文件描述符的状态如是否可以读写。poll原型#include poll.h int poll(struct pollfd *fds, nfds_t nfds, int timeout);fds一个pollfd结构体数组表示要监视的文件描述符及其关注的事件。nfds要监视的文件描述符的数量即fds数组的大小。timeout指定poll阻塞等待的时间单位是毫秒。如果为负数则阻塞直到有事件发生如果为 0则立即返回如果为正数则等待指定的时间后返回。poll的使用#include stdio.h #include stdlib.h #include unistd.h #include string.h #include sys/socket.h #include arpa/inet.h #include poll.h #define PORT 8080 int main() { int server_fd, new_socket; struct sockaddr_in address; int addr_len sizeof(address); // 创建服务器套接字 if ((server_fd socket(AF_INET, SOCK_STREAM, 0)) 0) { perror(socket failed); exit(EXIT_FAILURE); } address.sin_family AF_INET; address.sin_addr.s_addr INADDR_ANY; address.sin_port htons(PORT); // 绑定套接字 if (bind(server_fd, (struct sockaddr *)address, sizeof(address)) 0) { perror(bind failed); exit(EXIT_FAILURE); } // 监听客户端连接 if (listen(server_fd, 3) 0) { perror(listen failed); exit(EXIT_FAILURE); } // 创建 pollfd 结构体 struct pollfd fds[1]; fds[0].fd server_fd; fds[0].events POLLIN; // 关注可读事件 printf(Server is listening on port %d...\n, PORT); while (1) { // 调用 poll等待事件 int ret poll(fds, 1, -1); // -1 表示无限阻塞 if (ret 0) { perror(poll failed); exit(EXIT_FAILURE); } // 检查是否有可读事件 if (fds[0].revents POLLIN) { new_socket accept(server_fd, (struct sockaddr *)address, (socklen_t*)addr_len); if (new_socket 0) { perror(accept failed); continue; } printf(New connection established\n); // 读取数据 char buffer[1024] {0}; int valread read(new_socket, buffer, sizeof(buffer)); if (valread 0) { printf(Received message: %s\n, buffer); } close(new_socket); } } return 0; }poll的缺点和优点优点没有文件描述符数量的限制不像select限制了文件描述符数量poll可以处理更大规模的文件描述符。灵活的事件选择通过pollfd可以灵活地指定感兴趣的事件并且能够处理多种类型的事件。缺点性能瓶颈在大量文件描述符的情况下poll仍然会遍历所有文件描述符因此它在大规模并发连接时的性能较低。没有优先级poll处理事件的方式是均等的没有优先级调度功能。epoll的概念和原理epoll是 Linux 下提供的高效的 I/O 多路复用机制特别适用于处理大量并发连接。与select和poll相比epoll提供了更高效的方式来管理大量文件描述符。epoll相关函数epoll_create创建一个 epoll 实例返回一个文件描述符。 int epoll_create(int size); size 参数已被废弃但它仍然被要求传递。通常传入一个大于零的数。 epoll_ctl用于添加、删除或修改事件。 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); epfdepoll_create 返回的 epoll 文件描述符。 op操作类型EPOLL_CTL_ADD、EPOLL_CTL_DEL、EPOLL_CTL_MOD。 fd待操作的文件描述符。 event待监控的事件结构。 epoll_wait等待事件发生并返回就绪的文件描述符。 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); epfdepoll_create 返回的 epoll 文件描述符。 events用于存放就绪事件的数组。 maxevents数组大小。 timeout等待时间单位毫秒可以设置为 -1永久阻塞0非阻塞或正数指定超时时间。epoll的使用#include stdio.h #include stdlib.h #include unistd.h #include string.h #include sys/socket.h #include arpa/inet.h #include sys/epoll.h #define PORT 8080 #define MAX_EVENTS 10 int main() { int server_fd, client_fd, epfd, n; struct sockaddr_in address; struct epoll_event ev, events[MAX_EVENTS]; int addr_len sizeof(address); // 创建服务器套接字 if ((server_fd socket(AF_INET, SOCK_STREAM, 0)) 0) { perror(socket failed); exit(EXIT_FAILURE); } address.sin_family AF_INET; address.sin_addr.s_addr INADDR_ANY; address.sin_port htons(PORT); // 绑定套接字 if (bind(server_fd, (struct sockaddr *)address, sizeof(address)) 0) { perror(bind failed); exit(EXIT_FAILURE); } // 监听客户端连接 if (listen(server_fd, 3) 0) { perror(listen failed); exit(EXIT_FAILURE); } // 创建 epoll 实例 epfd epoll_create1(0); if (epfd -1) { perror(epoll_create failed); exit(EXIT_FAILURE); } // 将 server_fd 添加到 epoll 监控列表中 ev.events EPOLLIN; ev.data.fd server_fd; if (epoll_ctl(epfd, EPOLL_CTL_ADD, server_fd, ev) -1) { perror(epoll_ctl failed); exit(EXIT_FAILURE); } printf(Server is listening on port %d...\n, PORT); while (1) { // 等待事件发生 n epoll_wait(epfd, events, MAX_EVENTS, -1); if (n -1) { perror(epoll_wait failed); exit(EXIT_FAILURE); } // 处理所有就绪事件 for (int i 0; i n; i) { if (events[i].data.fd server_fd) { // 新连接到达 client_fd accept(server_fd, (struct sockaddr *)address, (socklen_t*)addr_len); if (client_fd -1) { perror(accept failed); continue; } // 将 client_fd 添加到 epoll 监控列表中 ev.events EPOLLIN; ev.data.fd client_fd; if (epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, ev) -1) { perror(epoll_ctl failed); exit(EXIT_FAILURE); } printf(New connection established\n); } else if (events[i].events EPOLLIN) { // 有数据可读 char buffer[1024] {0}; int len read(events[i].data.fd, buffer, sizeof(buffer)); if (len 0) { printf(Received: %s\n, buffer); } else { // 客户端关闭连接 printf(Closing connection\n); close(events[i].data.fd); } } } } close(server_fd); close(epfd); return 0; }epoll的优点高效性epoll使用事件驱动模式当有文件描述符就绪时才通知应用程序因此避免了poll和select中每次都要遍历所有文件描述符的性能问题。支持大规模并发在大量并发连接的情况下epoll处理效率更高尤其是当活跃的文件描述符很少时它只会通知那些就绪的文件描述符避免了不必要的检查。支持边缘触发和水平触发epoll提供了两种触发模式可以根据需要选择。总结特性selectpollepoll最大文件描述符限制通常 1024没有最大文件描述符限制没有文件描述符数量的限制效率随着文件描述符增加性能下降随着文件描述符增加性能下降高效性能不随文件描述符数量变化API设计每次调用时需要重设文件描述符使用结构体数组使用事件驱动灵活且高效操作方式阻塞或非阻塞阻塞或非阻塞阻塞、非阻塞、事件驱动适用场景文件描述符数量较少简单应用文件描述符数量适中但不高高并发场景如大规模网络服务优点简单易用较灵活解决了 select 限制极高的性能无限制的并发处理