学习笔记——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平台下性能最优,广泛应用于各种高性能服务器中。掌握这些技术对于开发高并发网络应用至关重要。

相关推荐
比奇堡派星星2 小时前
Linux Hotplug 机制详解
linux·开发语言·驱动开发
忙里偷闲学python2 小时前
docker
运维·docker·容器
云飞云共享云桌面2 小时前
河北某机器人工厂8个研发设计共享一台SolidWorks云主机
运维·服务器·网络·数据库·算法·性能优化·机器人
m0_485614672 小时前
Linux-容器基础2
linux·运维·服务器
于齐龙2 小时前
2025年12月19日 - 操作系统
运维·服务器
QT 小鲜肉3 小时前
【Linux命令大全】001.文件管理之mattrib命令(实操篇)
linux·运维·服务器·chrome·笔记
杨了个杨89823 小时前
日志服务部署
运维·服务器
FixPng3 小时前
【数据库】MySQL基于MyCAT分库分表
数据库·mysql
华舞灵瞳3 小时前
学习FPGA(八)快速傅里叶变换
学习·fpga开发