Linux IO 多路复用 epoll 机制

本文摘自写给应用开发的 Android Framework 教程 ,完整教程请查阅 yuandaimaahao.github.io/AndroidFram... 更为详细的视频教程与答疑服务,请联系微信 zzh0838

什么是 IO 多路复用

在 Linux 中:

  • IO 就是对文件的读写操作
  • 多路是指同时读写多个文件
  • 复用是指使用一个程序处理多个文件的同时读写

问题来了,为什么需要多路复用,为了快,要给每一个 fd 通道最快的感受,要让每一个 fd 觉得,你只在给他一个人跑腿。

为了更快的处理多路 IO,大体有两种方案:

  • 一种方案是:一个 IO 请求(比如 write )对应一个线程来处理,但是线程数多了,性能反倒会差。
  • 另外一种方案是: IO 多路复用

接下来,我们就来看看 IO 多路复用:

我不用任何其他系统调用,能否实现 IO 多路复用?

可以的,写个 for 循环,每次都尝试 IO 一下,读/写到了就处理,读/写不到就 sleep 下。

cpp 复制代码
while(true) 
{
    foreach fd数组 
    {
        read/write(fd, /* 参数 */)
    }

    sleep(1s)
}

默认情况下,我们没有加任何参数 create 出的 fd 是阻塞类型的。我们读数据的时候,如果数据还没准备好,是会需要等待的,当我们写数据的时候,如果还没准备好,默认也会卡住等待。所以,在上面伪代码中的 read/write 是可能被直接卡死,而导致整个线程都得到不到运行。只需要把 fd 都设置成非阻塞模式。这样 read/write 的时候,如果数据没准备好,返回 EAGIN 的错误即可,不会卡住线程,从而整个系统就运转起来了。

这个实现只是为了帮助我们理解 IO 多路复用,实际上,上面的实现在性能上有很大的缺陷。for 循环每次要定期 sleep 1s,这个会导致吞吐能力极差,因为很可能在刚好要 sleep 的时候,所有的 fd 都准备好 IO 数据,而这个时候却要硬生生的等待 1s。

IO 多路复用就是 1 个线程处理 多个 fd 的模式。我们的要求是:这个 "1" 就要尽可能的快,避免一切无效工作,要把所有的时间都用在处理句柄的 IO 上,不能有任何空转,sleep 的时间浪费。

为了实现上诉的功能,内核提供了 3 种系统调用 select,poll,epoll。

这 3 种系统调用都能够管理 fd 的可读可写事件,在所有 fd 不可读不可写无所事事的时候,可以阻塞线程,切走 cpu 。fd 可读写的时候,对应线程会被唤醒。

三者的差异主要是在性能上,epoll 的性能是强于 select 和 poll 的,我们接下来就来看看 epoll 的具体使用。

epoll 的使用

使用 epoll 需要以下三个系统调用:

cpp 复制代码
//头文件
#include <sys/epoll.h>

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  • epollcreate 负责创建一个池子,一个监控和管理句柄 fd 的池子;
  • epollctl 负责管理这个池子里的 fd 增、删、改;
  • epollwait 就是负责打盹的,让出 CPU 调度,但是只要有"事",立马会从这里唤醒;

接下来我们看个示例程序:

使用 epoll_create 创建一个管理 fd 的池子

cpp 复制代码
epollfd = epoll_create(1024);
if (epollfd == -1) {
    perror("epoll_create");
    exit(EXIT_FAILURE);
}

这个池子对我们来说是黑盒,这个黑盒是用来装 fd 的,我们暂不纠结其中细节。我们拿到了一个 epollfd ,这个 epollfd 就能唯一代表这个 epoll 池。

然后,我们就要往这个 epoll 池里放 fd 了,这就要用到 epoll_ctl 了

cpp 复制代码
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
    perror("epoll_ctl: listen_sock");
    exit(EXIT_FAILURE);
}

我们就把 fd 放到这个池子里了,EPOLL_CTL_ADD 表明操作是增加 fd,最后一个参数是 epoll_event 结构体:

cpp 复制代码
struct epoll_event {
  __uint32_t events;  /* Epoll 事件 */
  epoll_data_t data;  /* 用户数据 */
};

epoll_event 的第一个成员 events 用于指定我们监听的 fd 事件类型,常见的值有:

  • EPOLLIN:可读事件
  • EPOLLOUT:可写事件

多个值可以通过或操作同时生效:

cpp 复制代码
epoll_event event;
// 同时监听可读可写事件
event.events = EPOLLIN | EPOLLOUT;

最后,我们需要调用 epoll_wait 进入休眠状态,可读或可写事件到来时,醒休眠中的程序从 epoll_wait 处被唤醒。

其使用方法,通常如下:

cpp 复制代码
while (true)
{   
    // epollfd 是 epoll_create 的返回值
    // events 是一个 epoll_event 的数组,用于存储收到的多个事件
    // EPOLL_SIZE 用于设定最多监听多少个事件
    // 最后一个参数 -1 用于指定阻塞时间上限,-1:表示调用将一直阻塞
    int count = epoll_wait(epollfd, events, EPOLL_SIZE, -1);
    if (count < 0)
    {
        perror("epoll failed");
        break;
    }
    for (int i=0;i < count;i++)
    {
        //处理可读或可写事件
    }
}

参考资料

相关推荐
编程洪同学3 小时前
Spring Boot 中实现自定义注解记录接口日志功能
android·java·spring boot·后端
氤氲息5 小时前
Android 底部tab,使用recycleview实现
android
Clockwiseee5 小时前
PHP之伪协议
android·开发语言·php
小林爱6 小时前
【Compose multiplatform教程08】【组件】Text组件
android·java·前端·ui·前端框架·kotlin·android studio
小何开发7 小时前
Android Studio 安装教程
android·ide·android studio
开发者阿伟7 小时前
Android Jetpack LiveData源码解析
android·android jetpack
weixin_438150997 小时前
广州大彩串口屏安卓/linux触摸屏四路CVBS输入实现同时显示!
android·单片机
CheungChunChiu8 小时前
Android10 rk3399 以太网接入流程分析
android·framework·以太网·eth·net·netd
木头没有瓜8 小时前
ruoyi 请求参数类型不匹配,参数[giftId]要求类型为:‘java.lang.Long‘,但输入值为:‘orderGiftUnionList
android·java·okhttp
键盘侠0078 小时前
springboot 上传图片 转存成webp
android·spring boot·okhttp