学习笔记——IO多路复用技术

IO多路复用技术

一、核心概念

1.1 定义

IO多路复用(I/O Multiplexing)是单线程或单进程同时监测多个文件描述符是否可以执行IO操作的能力。

1.2 主要作用

  1. TCP服务器中处理多个客户端连接请求

  2. 对多个可能阻塞的设备进行IO操作,哪个设备数据先就绪就先处理哪个

  3. 解决传统阻塞IO模型并发能力差的问题

二、Linux IO模型分类

2.1 阻塞IO(默认)

  • 进程在等待IO操作时被挂起

  • 直到数据准备好或超时才恢复执行

2.2 非阻塞IO

  • 通过fcntl()设置O_NONBLOCK标志

  • 立即返回,不会阻塞进程

  • 需要循环检查(忙等待),消耗CPU资源

复制代码
int flag;
flag = fcntl(fd, F_GETFL, 0);      // 获取当前文件状态标志
flag = flag | O_NONBLOCK;          // 添加非阻塞标志
fcntl(fd, F_SETFL, flag);          // 设置新的文件状态标志

2.3 信号驱动IO(SIGIO)

  • 为文件描述符设置信号处理函数

  • 当数据就绪时内核发送SIGIO信号

  • 实际应用较少

2.4 并行模型

  • 使用多进程或多线程

  • 每个连接一个进程/线程

  • 资源消耗大,上下文切换开销高

2.5 IO多路复用(重点)

  • selectpollepoll

  • 单个线程监控多个文件描述符

  • 高效处理大量并发连接

三、select 模型详解

3.1 select 函数原型

复制代码
int select(int nfds, 
           fd_set *readfds, 
           fd_set *writefds,
           fd_set *exceptfds, 
           struct timeval *timeout);

3.2 参数说明

参数 说明
nfds 监控的文件描述符最大值+1
readfds 监控读就绪的文件描述符集合
writefds 监控写就绪的文件描述符集合
exceptfds 监控异常的文件描述符集合
timeout 超时时间,NULL表示无限等待

3.3 相关宏函数

复制代码
void FD_ZERO(fd_set *set);          // 清空集合
void FD_SET(int fd, fd_set *set);   // 添加fd到集合
void FD_CLR(int fd, fd_set *set);   // 从集合移除fd
int FD_ISSET(int fd, fd_set *set);  // 检查fd是否在集合中

3.4 select 工作流程

复制代码
1. 创建fd集合
2. 添加关心的fd到集合
3. 调用select()阻塞等待(内核轮询检查)
4. select返回后,遍历集合找到就绪的fd
5. 对就绪的fd进行读写操作
6. 清理标志位,准备下一次select

3.5 select 的缺点

  1. 文件描述符数量限制:默认1024

  2. 每次调用都需要重新设置fd集合

  3. 需要遍历所有fd检查就绪状态(O(n)复杂度)

  4. fd集合需要在用户态和内核态之间拷贝

四、epoll 模型详解

4.1 epoll 核心优势

  1. 无文件描述符数量限制

  2. 事件驱动,不随监听数增加而性能下降(O(1)复杂度)

  3. 共享内存,减少数据拷贝

  4. 只返回就绪的事件列表

4.2 epoll 相关函数

4.2.1 epoll_create()
复制代码
int epoll_create(int size);
  • 创建epoll实例

  • size:建议的监控数量(Linux 2.6.8后忽略,但需>0)

  • 返回:epoll文件描述符(成功),-1(失败)

4.2.2 epoll_ctl()
复制代码
int epoll_ctl(int epfd, 
              int op, 
              int fd, 
              struct epoll_event *event);
  • 管理epoll监控列表

op参数:

  • EPOLL_CTL_ADD:添加文件描述符

  • EPOLL_CTL_MOD:修改监控事件

  • EPOLL_CTL_DEL:删除文件描述符

epoll_event结构:

复制代码
struct epoll_event {
    uint32_t events;    // 监控的事件类型
    epoll_data_t data;  // 用户数据
};

typedef union epoll_data {
    void *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;

常用事件类型:

  • EPOLLIN:可读事件

  • EPOLLOUT:可写事件

  • EPOLLET:边缘触发模式

  • EPOLLONESHOT:单次事件

4.2.3 epoll_wait()
复制代码
int epoll_wait(int epfd, 
               struct epoll_event *events,
               int maxevents, 
               int timeout);
  • 等待事件发生

  • events:输出参数,存放就绪的事件

  • maxevents:events数组的大小

  • timeout:超时时间(毫秒),-1表示阻塞

4.3 epoll 工作模式

4.3.1 水平触发(LT,默认)
  • 只要文件描述符可读/可写,就会不断通知

  • 类似select/poll的工作方式

  • 编程简单,不易遗漏事件

4.3.2 边缘触发(ET)
  • 只有状态变化时才通知一次

  • 需要一次读取所有数据

  • 效率更高,但编程复杂

  • 必须使用非阻塞IO

4.4 epoll 工作流程

复制代码
1. 创建epoll实例 (epoll_create)
2. 添加关心的fd到epoll (epoll_ctl)
3. 等待事件发生 (epoll_wait)
4. 处理就绪的事件(直接遍历返回的events数组)
5. 继续等待下一次事件

五、select vs epoll 对比

特性 select epoll
最大连接数 1024(可调整但有限制) 无限制(系统可打开文件数)
时间复杂度 O(n),需要遍历所有fd O(1),只处理就绪的fd
工作方式 轮询所有fd 事件驱动,回调机制
数据拷贝 每次调用都要拷贝fd集合 内核和用户空间共享内存
触发模式 只支持水平触发 支持水平触发和边缘触发
编程复杂度 简单 相对复杂
适用场景 连接数少,跨平台 连接数多,Linux专用

六、实际应用建议

6.1 选择标准

  • select:连接数少(<1000),需要跨平台

  • poll:连接数中等,无平台限制

  • epoll:连接数多(>1000),Linux平台

6.2 性能优化建议

  1. 非阻塞IO:与epoll ET模式配合使用

  2. 事件循环:单线程处理所有连接

  3. 连接池:复用已建立的连接

  4. 缓冲区管理:合理设置缓冲区大小

6.3 常见应用

  1. Web服务器:Nginx, Apache

  2. 数据库连接池

  3. 聊天服务器

  4. 游戏服务器

  5. 代理服务器

七、示例代码框架

复制代码
// epoll 服务器框架示例
int main() {
    // 1. 创建socket,绑定,监听
    int listen_fd = socket(...);
    bind(listen_fd, ...);
    listen(listen_fd, ...);
    
    // 2. 创建epoll实例
    int epfd = epoll_create(1024);
    
    // 3. 添加监听socket到epoll
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = listen_fd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);
    
    struct epoll_event events[MAX_EVENTS];
    
    while (1) {
        // 4. 等待事件
        int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
        
        for (int i = 0; i < nfds; i++) {
            if (events[i].data.fd == listen_fd) {
                // 5. 接受新连接
                int conn_fd = accept(listen_fd, ...);
                
                // 6. 设置非阻塞
                fcntl(conn_fd, F_SETFL, O_NONBLOCK);
                
                // 7. 添加到epoll
                ev.events = EPOLLIN | EPOLLET;  // ET模式
                ev.data.fd = conn_fd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ev);
            } else {
                // 8. 处理客户端数据
                handle_client(events[i].data.fd);
            }
        }
    }
    
    close(epfd);
    close(listen_fd);
    return 0;
}

八、总结

IO多路复用是现代高性能网络编程的核心技术,通过select/poll/epoll等机制,实现了单线程处理大量并发连接的能力。其中epoll在Linux平台下性能最优,广泛应用于各种高性能服务器中。掌握这些技术对于开发高并发网络应用至关重要。

相关推荐
大模型玩家七七17 分钟前
梯度累积真的省显存吗?它换走的是什么成本
java·javascript·数据库·人工智能·深度学习
曾经的三心草19 分钟前
redis-9-哨兵
数据库·redis·bootstrap
明哥说编程23 分钟前
Dataverse自定义表查询优化:D365集成大数据量提速实战【索引配置】
数据库·查询优化·dataverse·dataverse自定义表·索引配置·d365集成·大数据量提速
珠海西格电力科技24 分钟前
微电网能量平衡理论的实现条件在不同场景下有哪些差异?
运维·服务器·网络·人工智能·云计算·智慧城市
xiaowu08033 分钟前
C# 拆解 “显式接口实现 + 子类强类型扩展” 的设计思想
数据库·oracle
A星空12336 分钟前
一、Linux嵌入式的I2C驱动开发
linux·c++·驱动开发·i2c
释怀不想释怀42 分钟前
Linux环境变量
linux·运维·服务器
zzzsde1 小时前
【Linux】进程(4):进程优先级&&调度队列
linux·运维·服务器
讯方洋哥1 小时前
HarmonyOS App开发——关系型数据库应用App开发
数据库·harmonyos
半壶清水1 小时前
[软考网规考点笔记]-软件开发、项目管理与知识产权核心知识与真题解析
网络·笔记·压力测试