非阻塞IO-多路复用-epoll的使用

非阻塞IO-多路复用-epoll

epoll

epoll 相对于 select 与 poll 有较⼤的不同,主要是针对前⾯两种多路复⽤ IO 接⼝的不⾜

  • 与 select/poll ⽅案对⽐
    • select ⽅案使⽤数组存储⽂件描述符,最⼤⽀持 1024
    • select 每次调⽤都需要将描述符集合拷⻉到内核中, ⾮常消耗资源
    • poll ⽅案解决⽂件描述符存储数量限制问题,但其他问题没有得到解决
    • select / poll 底层使⽤轮询的⽅式检测⽂件描述符是否就绪,⽂件描述符越多,则效率越低
    • epoll 底层使⽤红⿊树,没有⽂件描述符数量的限制, 并且可以动态增加与删除节点,不⽤重复拷⻉
    • epoll 底层使⽤ callback 机制, 没有采⽤遍历所有描述符的⽅式,效率较⾼

在这里插入图片描述

epoll

epoll创建需要调用epoll_create()函数

创建一个epool实例,分配相关的数据结构空间

函数头文件:

#include <sys/epoll.h>

函数原型:

int epoll_create(int size);

参数:

  • size: 填写大于0的整数,从Linux 2.6.8开始,size参数已经被废弃,可以不填

返回值:

  • 成功: 返回一个大于0的整数,表示epoll实例的句柄
  • 失败: 返回-1,并设置errno
创建epoll实例
//todo 创建一个epoll实例
#include <stdio.h>
#include <stdlib.h>
#include <poll.h>
#include <sys/epoll.h>
int main() {

    // 创建一个epoll实例,获取文件描述符
    int epoll_fd = epoll_create(1);
    if (epoll_fd == -1) {
        perror("epoll_create error");
        exit(1);
    }

    printf("epoll_fd = %d\n", epoll_fd);

    return 0;
}

epoll_ctl 控制函数

epoll 控制函数主要⽤于⽂件描述符集合的管理,包括增加、修改、删除等操作, 具体需要调⽤epoll_ctl 函数

函数头文件:

#include <sys/epoll.h>

函数原型:

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

参数:

epfd: epoll实例的句柄

op: 操作类型, 
      EPOLL_CTL_ADD(增加),在epoll实例中添加新的文件描述符,相当于添加到红黑树,并将事件链接到fd
      EPOLL_CTL_MOD(修改), 更改与目标文件描述符fd相关联的事件
      EPOLL_CTL_DEL(删除),从epoll实例中删除目标文件描述符fd,事件参数被忽略
      在系统中定义如下:
                    #define EPOLL_CTL_ADD 1
                    #define EPOLL_CTL_DEL 2
                    #define EPOLL_CTL_MOD 3
                    
fd: 要操作的文件描述符

event: 指向epoll_event结构体的指针, 用于描述事件类型和事件掩码


struct epoll_event 结构体定义如下:
typedef union epoll_data {
        void        *ptr; // 指向用户空间的数据
        int          fd;// 文件描述符
        uint32_t     u32; // 32位无符号整数
        uint64_t     u64;// 64位无符号整数
} epoll_data_t;

struct epoll_event {
        uint32_t     events; // 事件类型掩码
        epoll_data_t data; // 事件数据
};

    epoll 事件,事件具体定义如下:
            EPOLLIN : 读事件有效
            EPOLLOUT : 写事件有效
            EPOLLET: 将 EPOLL 设为边缘触发 (Edge Triggered) 模式
            

epoll_wait 等待事件函数

epoll 等待文件描述符关联的事件发⽣ (关联的⽂件描述符就绪), 这⾥调⽤ epoll_wait 函数

函数头文件:

#include <sys/epoll.h>

函数原型:

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

参数:

epfd: epoll实例的句柄
events: 指向epoll_event结构体数组的指针, 用于接收就绪事件
maxevents: 最大就绪事件数, 即events数组的大小
timeout: 超时时间, 单位为毫秒, -1表示阻塞, 0表示不阻塞, >0表示超时时间

返回值:

  • 成功: 返回就绪事件数, 即events数组中填充的事件数
  • 超时: 返回0
  • 失败: 返回-1, 并设置errno

实例

//todo 创建一个epoll实例
#include <stdio.h>
#include <stdlib.h>
#include <poll.h>
#include <sys/epoll.h>
int main() {

    // 创建一个epoll实例,获取文件描述符
    int epoll_fd = epoll_create(1);
    if (epoll_fd == -1) {
        perror("epoll_create error");
        exit(1);
    }

    printf("epoll_fd = %d\n", epoll_fd);

    //定义事件结构体
    struct epoll_event event;//关联文件描述符和事件类型
    //设置事件类型为可读
    event.events = EPOLLIN;
    //设置文件描述符为0
    event.data.fd = 0;
    //注册事件
    // 注册事件,监控文件描述符0
    int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, 0, &event);
    if (ret == -1) {
        perror("epoll_ctl error");
        exit(1);
    }
    printf("epoll_ctl success\n");

    // 等待事件到来
    struct epoll_event events[10];
    while (1){
        int nfds = epoll_wait(epoll_fd, events, 10, 1000);
        if (nfds == -1) {
            perror("epoll_wait error");
            exit(1);
        }if (nfds == 0) {
            printf("超时\n");
            continue;
        }if (nfds > 0) { 
            printf("有事件发生\n");

            char buffer[1024]={0};
            //遍历文件描述符集合
            for (int i = 0; i < nfds; i++) {
                //判断事件类型是否为可读
                if(events[i].data.fd==0){
                    fgets(buffer,1024,stdin);
                    printf("收到消息:%s",buffer);
                }
            }


        }
    }





    return 0;
}

epoll 源码分析

在这里插入图片描述

上述框架中核⼼的数据结构 struct eventpoll , 具体定义如下:


struct eventpoll {
    spinlock_t lock;//自旋锁,用于保护对 struct eventpoll 结构的访问。
    wait_queue_head_t wq;//等待队列头,用于 epoll 本身的等待队列。
    wait_queue_head_t poll_wait;//等待队列头,用于 poll 回调的等待队列。
    struct list_head rdllist;//就绪文件描述符的链表头
    struct rb_root_cached rbr;//红黑树根节点,用于存储被监视的文件描述符结构。
    struct user_struct *user;//创建 eventpoll 描述符的用户结构
    struct list_head f_ep_links;//链表头,用于将此结构链接到 struct file 的 f_ep_links 链表。
    struct list_head f_tfile_llink;//链表头,用于将此结构链接到 struct file 的 f_tfile_llink 链表。
    int visited;// 用于优化循环检测检查
    struct list_head visited_list_link;//链表头,用于链接到已访问的链表。
#ifdef CONFIG_NET_RX_BUSY_POLL
    unsigned int napi_id;  //用于跟踪 busy poll 的 napi_id(仅在配置了 CONFIG_NET_RX_BUSY_POLL 时存在)。
#endif
};


红⿊树的节点对应的数据结构为 struct epitem , 具体定义如下:

struct epitem {
    struct rb_node rbn;       /* 红黑树节点 */
    struct list_head rdllink; /* 就绪列表链表节点 */
    struct epitem *next;      /* 用于 epoll 实例的哈希表 */
    struct epoll_filefd ffd;  /* 文件描述符和文件指针 */
    int nwait;                /* 等待队列的数量 */
    struct list_head pwqlist; /* 等待队列链表 */
    struct eventpoll *ep;     /* 指向所属的 epoll 实例 */
    struct list_head fllink;  /* 文件链表节点 */
    struct epoll_event event; /* 用户感兴趣的事件 */
};


文件指针 /
int nwait; /
等待队列的数量 /
struct list_head pwqlist; /
等待队列链表 */
struct eventpoll ep; / 指向所属的 epoll 实例 /
struct list_head fllink; /
文件链表节点 /
struct epoll_event event; /
用户感兴趣的事件 */
};

实现原理 (未完)

### Redis 中基于 Epoll非阻塞 IO 多路复用工作原理 Redis 是一种高性能的内存数据库,其核心之一在于通过高效的 IO 模型支持大量的并发连接。为了实现这一点,Redis 利用了 IO 多路复用技术中的 `epoll` 机制,在 Linux 平台上提供了高效的通知方式。 #### 1. **Epoll 基本概念** `epoll` 是 Linux 提供的一种高效的 IO 复用接口,相比传统的 `select` 和 `poll` 更加适合大规模文件描述符的场景。它通过三个主要操作来完成事件监听和通知的功能: - `epoll_create`: 创建一个 epoll 实例。 - `epoll_ctl`: 注册/修改/删除感兴趣的文件描述符及其对应的事件类型。 - `epoll_wait`: 等待注册的文件描述符上的事件发生并返回。 这些特性使得 `epoll` 能够有效地减少轮询开销,并且只关注那些真正发生了变化的文件描述符[^3]。 #### 2. **Redis 中的 Epoll 应用** 在 Redis 中,所有的客户端连接都被抽象成文件描述符 (FD),并通过 `epoll` 进行统一管理。以下是具体的工作流程: - **初始化阶段**: 当服务器启动时,会调用 `aeCreateFileEvent` 函数创建一个新的文件事件处理器,并将其绑定到指定的文件描述符上。此时,Redis 将该 FD 添加到内部维护的一个全局数组中以便后续跟踪。 - **事件循环**: 主线程进入无限循环模式 (`mainLoop`) 来持续监控所有已注册的文件描述符的状态变更情况。在这个过程中,主线程会定期调用 `epoll_wait` 方法等待任何可读或可写的条件满足后再继续执行下一步逻辑处理动作[^1]。 - **事件分发与回调**: 如果某个特定时间点上有新的数据到达或者可以发送更多消息出去,则相应的 handler function 就会被触发去实际完成具体的业务功能;如果没有匹配项则保持休眠状态直到下一个周期到来为止[^2]。 #### 3. **优势分析** 使用 `epoll` 可以为 Redis 带来以下几个方面的提升效果显著: - 性能优化: 对于大数量级的同时在线活跃链接数来说,相比于其他两种传统方法(select/poll),epoll 明显具备更低延迟率以及更高吞吐能力. - 资源节约: 它只需要消耗少量额外内存用于保存关心列表即可而无需像前者那样每次都需要重新构建整个集合结构体实例化对象. ```c // 示例代码展示如何设置 epoll 监听器 int fd = socket(AF_INET, SOCK_STREAM, 0); struct epoll_event event; event.events = EPOLLIN | EPOLLET; // 边缘触发模式 event.data.fd = fd; if(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event) == -1){ perror("epoll set insertion error"); } ``` --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

可能只会写BUG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值