三种I/O复用方式——select、poll和epoll

本文介绍了Linux系统中用于I/O复用的三种方式:select、poll和epoll,详细讲解了它们的工作原理、使用方法以及应用场景。重点阐述了epoll的LT和ET两种模式,并指出epoll在性能和效率上的优势。

为什么要引入I/O复用:

TCP服务器在与客户端完成建立连接,并在完成整个交互过程之后再断开连接。在服务端的代码中,在收发数据时加上一个while循环,用于解决同一个客户端的多次收发数据请求。但当多个客户端同时向服务器发出请求时,当前的服务器无法满足要求,所以引入了I/O复用,可以使程序同时监听多个文件描述符,这样能使程序的性能提高。

I/O复用的场景

1.TCP服务器需要同时处理监听socket、连接socket
2.服务器要同时接听多个端口
3.客户端程序要同时处理用户输入和网络连接
4.程序要同时处理多个socket
5.服务器要同时处理TCP请求和UDP请求等多个请求
tips:I/O复用能够同时监听多个文件描述符,但是他的本身是阻塞的,如不采取其他的措施,就会串行执行这些文件描述符。想要实现并发,就需要用到多进程或者多线程、线程池来实现。

一、select—#include<sys/select.h>

#include <sys/select.h>  
{
    int select(int maxfd, fd_set* readfds, fd_set* writefds, fd_set* execptfds, 
    struct meval* timeout);
}

作用:在一段指定时间内,监听用户感兴趣的文件描述符上的可读、可写和异常等事件。

(1)maxfd:指定被监听的文件描述符的总数。它通常被设置为select监听的所有文件描述符的中的最大值加1,因为文件描述符是从0开始计数的。
(2)readfds:指向可读事件对应的文件描述符集合,
(3)writefds:指向可写事件对应的文件描述符集合
(4)exceptfds:指向异常事件对应的文件描述符集合
(5)timeout:用来设置select监听的超时时间。

### Linux 中 `select`、`poll` `epoll` 的区别及实际开发中的选择 #### 1. 基本概念 - **`select`**:最早的 I/O 多路复用机制,支持监听多个文件描述符(FD),并返回可读、可写或发生异常的 FD 列表[^1]。 - **`poll`**:改进版的 `select`,移除了文件描述符数量限制的问题,但仍然存在轮询机制的效率瓶颈[^1]。 - **`epoll`**:基于事件驱动的 I/O 多路复用机制,通过内核中注册回调函数的方式,仅通知活跃的 FD,显著提高了性能[^3]。 #### 2. 工作原理与性能特点 - **`select`**: - 使用一个 `fd_set` 结构体来管理需要监听的 FD 集合,每次调用时都需要将整个集合复制到内核空间。 - 效率随着 FD 数量增加而下降,因为每次都会对所有 FD 进行轮询检查。 - 最大 FD 数量受限于系统定义的 `FD_SETSIZE` 常量[^1]。 ```c fd_set readfds; FD_ZERO(&readfds); FD_SET(fd, &readfds); select(fd + 1, &readfds, NULL, NULL, NULL); ``` - **`poll`**: - 使用 `struct pollfd` 数组来管理 FD,理论上没有最大 FD 数量限制。 - 同样采用轮询方式检查每个 FD 的状态,因此在大量 FD 场景下性能较差[^1]。 ```c struct pollfd fds[1]; fds[0].fd = fd; fds[0].events = POLLIN; poll(fds, 1, -1); ``` - **`epoll`**: - 内核中维护了一个红黑树结构,用于高效管理活跃的 FD。 - 支持两种工作模式:水平触发(LT)边缘触发(ET)。ET 模式下,仅在状态变化时通知一次,需一次性处理所有数据[^4]。 - 使用 `mmap` 加速内核与用户空间的消息传递,避免不必要的内存拷贝[^2]。 ```c int epoll_fd = epoll_create1(0); struct epoll_event event; event.events = EPOLLIN | EPOLLET; // 边缘触发模式 event.data.fd = fd; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event); struct epoll_event events[MAX_EVENTS]; int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); ``` #### 3. 性能对比 - 在连接数较少且连接活跃的情况下,`select` `poll` 的性能可能优于 `epoll`,因为 `epoll` 的复杂设计引入了额外的开销。 - 当面对大量空闲连接(idle-connection)时,`epoll` 的效率远高于 `select` `poll`,因为它只关注活跃的 FD[^3]。 | 特性 | `select` | `poll` | `epoll` | |--------------------------|------------------------|------------------------|--------------------------| | 文件描述符限制 | 受限于 `FD_SETSIZE` | 理论上无限制 | 理论上无限制 | | 轮询机制 | 是 | 是 | 否 | | 内存拷贝优化 | 无 | 无 | 使用 `mmap` 优化 | | 适用场景 | 小规模 FD | 中等规模 FD | 大规模 FD 或高并发场景 | #### 4. 实际开发中的选择 - 如果应用程序涉及的 FD 数量较少且连接活跃度较高,可以选择 `select` 或 `poll`,因其简单易用且开销较小[^1]。 - 对于大规模并发场景或需要高效处理大量空闲连接的应用程序,应优先选择 `epoll`,以充分利用其事件驱动机制性能优势。 - 在使用 `epoll` 时,根据具体需求选择 LT 或 ET 模式。对于需要一次性处理所有数据的场景,推荐使用 ET 模式。 ### 示例代码对比 #### 使用 `select` ```c fd_set readfds; FD_ZERO(&readfds); FD_SET(fd, &readfds); struct timeval timeout = { .tv_sec = 5, .tv_usec = 0 }; select(fd + 1, &readfds, NULL, NULL, &timeout); if (FD_ISSET(fd, &readfds)) { // 处理读事件 } ``` #### 使用 `poll` ```c struct pollfd fds[1]; fds[0].fd = fd; fds[0].events = POLLIN; int ret = poll(fds, 1, 5000); if (ret > 0 && (fds[0].revents & POLLIN)) { // 处理读事件 } ``` #### 使用 `epoll` ```c int epoll_fd = epoll_create1(0); struct epoll_event event; event.events = EPOLLIN; event.data.fd = fd; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event); struct epoll_event events[MAX_EVENTS]; int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, 5000); for (int i = 0; i < nfds; ++i) { if (events[i].events & EPOLLIN) { // 处理读事件 } } ``` ###
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值