news 2026/8/15 17:13:52

linux下的各种I/O多路复用详解---select/poll/epoll

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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的概念和原理

pollselect的一个改进版本,常用于多路复用(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 多路复用机制,特别适用于处理大量并发连接。与selectpoll相比,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); epfd:epoll_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); epfd:epoll_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使用事件驱动模式,当有文件描述符就绪时才通知应用程序,因此避免了pollselect中每次都要遍历所有文件描述符的性能问题。
  • 支持大规模并发:在大量并发连接的情况下,epoll处理效率更高,尤其是当活跃的文件描述符很少时,它只会通知那些就绪的文件描述符,避免了不必要的检查。
  • 支持边缘触发和水平触发epoll提供了两种触发模式,可以根据需要选择。

总结

特性selectpollepoll
最大文件描述符限制(通常 1024)没有最大文件描述符限制没有文件描述符数量的限制
效率随着文件描述符增加,性能下降随着文件描述符增加,性能下降高效,性能不随文件描述符数量变化
API设计每次调用时需要重设文件描述符使用结构体数组使用事件驱动,灵活且高效
操作方式阻塞或非阻塞阻塞或非阻塞阻塞、非阻塞、事件驱动
适用场景文件描述符数量较少,简单应用文件描述符数量适中,但不高高并发场景,如大规模网络服务
优点简单易用较灵活,解决了 select 限制极高的性能,无限制的并发处理
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/15 17:12:54

全网首发:OSD字幕时,字体数据、间距的正确处理

比如说&#xff1a;如果是巍宇&#xff0c;这字好处理&#xff1b;如果是一丨&#xff0c;字体数据只有一行&#xff0c;数据如何刷新处理&#xff1f;间距又如何处理&#xff1f; 别担心。我这里有现成的代码。 先看处理结果 https://blog.csdn.net/quantum7/article/detai…

作者头像 李华
网站建设 2026/8/15 17:10:45

5分钟上手UnrealPakViewer:让Pak文件分析变成一次可视化体检

5分钟上手UnrealPakViewer&#xff1a;让Pak文件分析变成一次可视化体检 【免费下载链接】UnrealPakViewer 查看 UE4 Pak 文件的图形化工具&#xff0c;支持 UE4 pak/ucas 文件 项目地址: https://gitcode.com/gh_mirrors/un/UnrealPakViewer 当项目包体超限、关卡加载变…

作者头像 李华