Linux 下 IO 复用与 epoll 简介

一、阻塞 I/O

阻塞 I/O 模式下,一个线程只能处理一个流的 I/O 事件。如果想要同时处理多个流,要么多进程(fork),要么多线程(pthread_create),但是两种方法效率都不高

二、epoll

epoll 可以理解为 event poll,不同于忙轮询和无差别轮询,epoll 会把哪个流发生了怎样的 I/O 事件通知我们,此时我们对这些流的操作都是有意义的。在高并发下,epoll 相较于 select 和 poll 来说更高效
原因:

  • 它会复用文件描述符集合来传递结果,而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合
  • 获取事件的时候,无需遍历整个被侦听的描述符集合,只要遍历那些被内核 IO 事件异步唤醒的 Ready 队列的描述符集合就行了
2.1 什么是 epoll

Linux 2.6 内核正式引入 epoll,epoll 是 Linux 下多路复用 IO 接口 select/poll 的增强版本,能显著提高程序在大量并发连接中只有少量活跃的情况下的系统 CPU 利用率。epoll 除了默认的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered)

2.2 创建并管理 epoll 实例

1.int epoll_create(int size);
方法作用:创建一个新的 epoll 实例,对应会在内核中创建了一棵红黑树,返回的文件描述符指向红黑树的树根
方法参数:size,告诉内核创建多少个文件描述符,该值只是一个建议值,底层会根据该值给上层应用开辟内存空间
方法返回值:大于 0 表示 epoll 实例,-1 表示出错

2.int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
方法作用:将事件注册进 epoll 对象(控制某个 epoll 监控的文件描述符上的事件,在红黑树上对文件描述符进行相应操作)
方法参数:

  • epfd:epoll 对象的文件描述符,对应 epoll_create() 函数的返回值
  • op:对文件描述符的红黑树的数据结构进行的操作,有:EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL 三个宏,分别对应增加、修改、删除节点操作
  • fd:要监听的文件描述符
  • event:描述监听事件的详细信息的 epoll_event 结构体
typedef union epoll_data {
   void        *ptr;
   int          fd; // 通常把 fd 用来设置为需要监听的文件描述符,当然也可以设置成其他数据,这里的 fd 只是用于事件发生后的判断
   uint32_t     u32;
   uint64_t     u64;
} epoll_data_t;

struct epoll_event {
   uint32_t     events; // 事件掩码,指明需要监听的事件种类:EPOLLIN(可读)、EPOLLOUT(可写)、EPOLLERROR(描述符发生错误)、EPOLLHUP(描述符被挂起)等
   epoll_data_t data; // 自定义数据,当此事件发生时该数据将原封不动返回给使用者
};

方法返回:文件描述符上的发生的事件的个数

epoll_ctl 函数第三个参数 fd 代表要监听的文件描述符,是真正要注册监听的事件
epoll_ctl 函数第二个参数 op 代表要增加、删除或修改注册事件
// 增加:EPOLL_CTL_ADD,修改:EPOLL_CTL_MOD、删除:EPOLL_CTL_DEL
epoll_event.events:注册监听的事件发生时所感兴趣的操作
// EPOLLIN(可读)、EPOLLOUT(可写)、EPOLLERROR(描述符发生错误)、EPOLLHUP(描述符被挂起)等
epoll_event.data.fd:自定义数据,方便使用者用来对注册监听的事件发生后的代码判断

3.int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
方法作用:等待所监控的文件描述符上有事件发生,将就绪的文件描述符放到 events 数组中
方法参数:

  • epfd:文件描述符,即 epoll_create() 函数的返回值
  • events:数组,用来存储内核得到的事件集合,成员类型为 epoll_event 结构体
  • maxevents:events 数组的大小
  • timeout:超时时间,-1:阻塞,0:立即返回,>0:超时的毫秒数

方法返回:返回文件描述符就绪的个数,-1 表示出错

三、用法示例(使用 Linux 测试)

① 打开 Linux 环境
如果想安装 Linux 环境,可以参考
② 选择任意目录,编写 epoll_hello.cpp
gedit epoll_hello.cpp

#include <stdio.h>
#include <unistd.h>
#include <sys/epoll.h>

int main(void)
{
    int epfd, nfds;
    struct epoll_event ev,events[5]; // ev 表示注册事件,数组 events 用于返回要处理的事件(数组成员为 ev 对应的结构体类型)
    // 1. 创建 epoll 对象
    epfd = epoll_create(1);
    // 2. 填充 epoll_event 结构体
    ev.data.fd = STDIN_FILENO; // 指定事件所从属的目标文件描述符,设置 fd 为 STDIN_FILENO(标准输入事件)
    ev.events = EPOLLIN||EPOLLET; // 监听读状态,并设置触发模式为边缘触发(效果是监听到读事件,就去读一次数据,能否一次把缓冲区中的数据读完是不确定的),默认是水平触发(效果是监听到读事件,就会一直去读缓冲区中的数据,直到读完为止)
    // 3. 注册 epoll 事件,监听 STDIN_FILENO(标准输入事件)
    epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev);
    for(;;)
    {
    	// 4. 等待事件发生(注册的 STDIN_FILENO 事件发生后才返回,并携带发生的事件的相关信息)
        nfds = epoll_wait(epfd, events, 5, -1);
        // 5. 对 epoll_wait() 返回的 events 结构体数组进行处理
        for(int i = 0; i < nfds; i++)
        {
            // 获取的文件描述符等于指定事件所从属的目标文件描述符
            // STDIN_FILENO:标准输入设备文的件描述符
            if(events[i].data.fd==STDIN_FILENO)
                printf("welcome to epoll's word!\n");
        }
    }
}

③ gcc epoll_hello.cpp -o epoll_hello
④ ./epoll_hello
⑤ 然后输入任意字符,如:HelloWorld,回车即可
./epoll_hello

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值