I/O多路复用(select、poll、epoll)

2023-10-26

基本思想

1、先构造一张有关文件描述符的表,然后使用我们的select、poll、epoll函数

2、我们的应用程序会将这张表复制给内核。

3、内核层初始化表中的需要检测的描述符。

4、当检测到有文件操作时,则立即将文件描述符作为标志并返回给应用程序。

5、应用程序根据内核返回的表来进行相应的I/O操作。

函数原型

select

 int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

void FD_CLR(int fd, fd_set *set);

int  FD_ISSET(int fd, fd_set *set);

void FD_SET(int fd, fd_set *set);

void FD_ZERO(fd_set *set);

struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };


参数说明:

                nfds: 程序中最大的文件描述符值 + 1(注意:一般我们自定义的文件描述符是从3开始,0,1,2被系统占用为stderr,stdout,stdin)
                readfds:   读操作文件描述符的集合.
                writefds:  写操作文件描述符的集合.(在线程池当中可能用到)
                exceptfds: 异常文件描述符集合.(基本不会使用)
                timeout:   函数超时检测. 
               
                如果为 NULL 则一直等待,直到有可操作的文件描述符
                比如:2秒的话,表示2秒后如果没有文件描述符可操作则立即返回
                
                返回值:错误 -1 . 
                正确返回select函数检测到的能够执行操作的文件描述符个数.

void FD_CLR(int fd, fd_set *set);
         清除集合表当中的指定文件描述符fd.
int  FD_ISSET(int fd, fd_set *set);
          判断文件描述符是否在表内为1.
void FD_SET(int fd, fd_set *set);
          把文件描述符加入集合
void FD_ZERO(fd_set *set); 
          把集合表中所有文件描符清空

示例:

#include <strings.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{ 
    int fd = open("/dev/input/mice", O_RDONLY);//打开我们Linux下的鼠标文件
    if(fd == -1)
    {
        perror("open");
        return -1;
    }
    char buf[32] = {0};

    fd_set rfds, t_fds;//创建两张表,一张用于操作,一张用于维持原来的状态
    FD_ZERO(&rfds);   //把集合中的所有文件描述清空
    FD_SET(0, &rfds);//最开始我们将所有的状态都置为0
    FD_SET(fd, &rfds);//将我们的鼠标文件描述加入到文件描述表中
    int max_fd = fd + 1;
    int i = 0;
    int ret = 0;

    struct timeval t_val = {3, 0};//初始化时间

    while(1)
    {
        t_fds = rfds;
        t_val.tv_sec = 3;
        t_val.tv_usec = 0;
        if((ret = select(max_fd, &t_fds, NULL, NULL, &t_val)) <= 0)//调用select函数
        {
            if(ret == 0)
            {
                printf("timeout-----------\n");
                //return -1;
                continue;
            }
            else
            {
                perror("select");
                return -1;
            }
        }

        for(i = 0; i < max_fd; i++)//逐一检测从0到最大的fd中的可操作文件描述符,或者进行我们想要的操作
        {
            if(1 == FD_ISSET(i, &t_fds))
            {
                if(i == 0)//i=0时从键盘输入数据
                {
                    memset(buf, 0, sizeof(buf));
                    fgets(buf, sizeof(buf), stdin);
                    buf[strlen(buf) - 1] = '\0';
                    printf("%s\n", buf);
                }
                else if(i == fd)//等于我们的fd时,获取鼠标的坐标
                {
                    memset(buf, 0, sizeof(buf));
                    read(fd, buf, sizeof(buf));
                    printf("%d--%d--%d\n", buf[0], buf[1], buf[2]);
                }
            }
        }

    }
    return 0;

select函数的缺点:

        1、文件描述符有上限,Linux系统一般默认1024个.
        2、监听表与返回表是同一张表,没有实现分离.
        3、文件描述符表需要被复制到内核,又需要从内核复制到用户区.
        4、需要循环线性的监测已就绪的IO操作,会出现无意义的循环.

poll

函数原型:

int poll(struct pollfd *fds, nfds_t nfds, int timeout);


struct pollfd {
               int   fd;         /* file descriptor */
               short events;     /* requested events */
               short revents;    /* returned events */
           };

参数说明:

        fds为系统指定的结构体数组(传首地址),nfds最大的文件描述符+1,timeout设置的超时,其值为负整数或正整数

示例:

#include <strings.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <poll.h>
#include <fcntl.h>
#include <unistd.h>
#define FDSIZE 3002//最大的文件描述符个数
int main(int argc, char *argv[])
{ 
    struct pollfd fd_arr[FDSIZE];//定义结构体类型
    int m_fd = open("/dev/input/mice", O_RDONLY);
    if(m_fd == -1)
    {
        perror("open");
        return -1;
    }
    /*init*/
    bzero(fd_arr, FDSIZE);//清空结构体

    int i = 0;
    for(i = 0; i < FDSIZE; i++)//将结构体里面的fd全部置为-1
    {
        fd_arr[i].fd = -1;
    }

    int maxpos = 0;
    fd_arr[maxpos].fd = 0;
    fd_arr[maxpos].events = POLLIN;//pollon为系统定义的宏,意思是读操作
    maxpos++;
    
    fd_arr[maxpos].fd = m_fd;
    fd_arr[maxpos].events = POLLIN;
    maxpos++;

    char buf[32] = {0};
    while(1)
    {
        if(-1 == poll(fd_arr, FDSIZE, 0))//调用poll函数
        {
            perror("poll");
            return -1;
        }
        for(i = 0; i < maxpos; i++)//逐一检测fd的状态,实现我们想要进行的操作
        {
            if(fd_arr[i].fd == -1)
                continue;
            if(fd_arr[i].revents == POLLIN)
            {
                if(fd_arr[i].fd == 0)
                {
                    fgets(buf, sizeof(buf), stdin);
                    buf[strlen(buf)-1] = '\0';
                    printf("buf=%s\n", buf);
                }
                else if(fd_arr[i].fd == m_fd)
                {
                    memset(buf, 0, sizeof(buf));
                    read(m_fd, buf, sizeof(buf));
                    printf("%d\n", buf[0]);
                }
            }
        }
    }

    return 0;
} 

poll的优缺点:

优点:

1、文件描述符无上限(使用链表进行管理),但是受硬件限制. ​ 2、监听表与返回表实现分离.

缺点:

1、文件描述符表需要被复制到内核,又需要从内核复制到用户区.

2、需要循环线性的监测已就绪的IO操作,会出现无意义的循环.

epoll

epoll跟上面两个函数,稍微有点区别,它的函数包括了create,ctl,wait等,可以说是一个函数族

函数原型:

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);

  函数和参数说明

epoll_create:返回的是一个引用epoll后的文件描述符,该文件描述符为后面所有文件描述符的接口,这跟epoll的实现有关系,相当于创建一个棵树,size为树的节点个数,

epoll_ctl:文件描述符控制函数(把文件描述符添加到树上、或者从数上删除以及修改)

              epfd: epoll_create 函数返回的树操作句柄
              op:    要对文件描述符执行的操作.
                   EPOLL_CTL_ADD: 在树中添加文件描述符
                   EPOLL_CTL_MOD: 修改
                   EPOLL_CTL_DEL: 删除.       
            fd:  要操作的文件描述符.
            event: 一个结构体的地址.
            typedef union epoll_data {
               void        *ptr;
               int          fd;
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;
           struct epoll_event {
               uint32_t     events;      /* Epoll events */
               epoll_data_t data;        /* User data variable */
           };
           events: 文件描述符需要的操作
                      例如:读:EPOLLIN
                                 写:EPOLLOUT
                                 异常:EPOLLERR
           data是一个联合体: 
                      通常使用第二个成员fd.代表需要检测的文件描述符 
           返回值: 正确返回 0  错误返回 -1

epoll_wait:系统调用等待文件描述epfd引用的实例

        epfd:      epoll_create 函数返回的树操作句柄
        events:    结构体数组的首地址.
        maxevents: 数组中要操作的文件描述符最大个数.(一般和create函数传入的参数一致)
        timeout: 
                -1: 代表一直等待直到有io操作才返回
                0 :不等待立即返回
               >0: 定时的时间 单位毫秒
            返回值:
                正确返回 可操作的文件描述符个数 
                错误返回 -1 

示例:

#include <strings.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <unistd.h>
#define MAXSIZE 2000
int main(int argc, char *argv[])
{ 
    int i = 0;
    int m_fd = open("/dev/input/mice", O_RDONLY);
    if(m_fd == -1)
    {
        perror("open");
        return -1;
    }
    struct epoll_event epfd, lfds[MAXSIZE];//定义操作句柄和结构体数组
    int e_fd = epoll_create(1);
    if(e_fd == -1)
    {
        perror("epoll_create");
        return -1;
    }
    bzero(lfds, sizeof(lfds));
    bzero(&epfd, sizeof(epfd));
    epfd.events = EPOLLIN;//EPOLL系统定义的宏,读操作
    epfd.data.fd = 0;
    if(-1 == epoll_ctl(e_fd, EPOLL_CTL_ADD, 0, &epfd))//将e_fd加入到我们的操作树句柄中
    {
        perror("ctl");
        return -1;
    }

    epfd.events = EPOLLIN;//设置读操作
    epfd.data.fd = m_fd;//将fd设置为我们的鼠标fd
    if(-1 == epoll_ctl(e_fd, EPOLL_CTL_ADD, m_fd, &epfd))
    {
        perror("ctl");
        return -1;
    }
    int nread = 0;
    char buf[32] = {0};

    while(1)
    {
        if((nread = epoll_wait(e_fd, lfds, MAXSIZE, -1)) == -1)//调用epoll_wait函数,并保留我们可操作的文件描述符个数
        {
            perror("epoll_wait");
            return -1;
        }
        printf("fd=%d\n", lfds[0].data.fd);

        for(i = 0; i < nread; i++)//根据自己的需求进行对相应的文件描述符进行操作
        {
            if(lfds[i].events == EPOLLIN)
            {
                if(lfds[i].data.fd == 0)
                {
                    fgets(buf, sizeof(buf), stdin);
                    buf[strlen(buf)-1] = '\0';
                    printf("buf=%s\n", buf);
                }
                else if(lfds[i].data.fd == m_fd)
                {
                    memset(buf, 0, sizeof(buf));
                    read(m_fd, buf, sizeof(buf));
                    printf("%d\n", buf[0]);
                }
            }
        }
    }

    return 0;
} 

epoll函数的优缺点:

优点:

1、文件描述符无上限,但是受硬件限制.

2、监听表与返回表实现分离.

3、文件描述符表采用映射(mmap)机制.

4、利用二叉树(红黑树) 以及一个就绪链表来管理文件描述符,就绪链表只会有就绪的IO 操作(不会有无意义的循环).

缺点:

1、内部数据结构的管理比较麻烦,如果文件描述符少的情况下建议使用select或poll

        

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

I/O多路复用(select、poll、epoll) 的相关文章

  • 是否可以允许jenkins访问只有root或某些特定程序可以访问的文件?

    我基本上想做的是允许 jenkins 访问我的 android sdk linux 文件夹和所有子目录 我的老板不想自己更改文件夹的权限 我应该在构建过程中这样做 我见过一些在构建过程中在执行 shell 中运行一些命令的示例 是否可以在该
  • Kubernetes Pod 已终止 - 退出代码 137

    我需要一些关于 k8s 1 14 和在其上运行 gitlab 管道所面临的问题的建议 许多作业都会抛出退出代码 137 错误 我发现这意味着容器突然终止 集群信息 库伯内特版本 1 14 使用的云 AWS EKS 节点 C5 4xLarge
  • 升级到Python 3.6后启动时无法启动cloud-init

    我在新的云服务器 Ubuntu 16 04 上安装了 Python 3 6 而不是默认的 3 5 版本 我重启服务器后发现执行失败cloud init启动时出现以下错误syslog Sep 20 16 16 14 cloud init 13
  • 为什么 SDL 在 Mac 上比 Linux 上慢得多?

    我正在研究使用 SDL2 渲染的单线程图形程序 https github com TurkeyMcMac intergrid 请参阅末尾的较小示例 它既可以在旧的 Linux 机器上运行 也可以在不太旧的 Mac 上运行 Linux 计算机
  • 使用 perf 查找线程瓶颈并优化挂机时间

    对 cpu 周期进行采样perf record如果核心利用率大致恒定 则对于寻找优化候选非常有用 但对于具有并行性不同的多个阶段的代码 计算 cpu 周期将重点强调并行阶段 而低估影响挂机时间的顺序或有限并行阶段 简而言之 天真的 perf
  • 在ubuntu 18.04上安装python 2.7

    有没有办法在 Ubuntu 18 04 上安装 Python 2 7 我尝试了这个命令 但它不起作用 sudo apt install python minimal 有没有办法手动安装 我尝试使用 python 2 7 作为不支持 pyth
  • Vim 中的空格作为制表符和退格键行为

    在我的 vimrc 中我有 set shiftwidth 4 set tabstop 4 set expandtab 当我点击 Tab 按钮时 设置为使用 4 个空格而不是 Tab 但是当我在 Tab 之后按退格键时 我需要退格所有 4 个
  • 找出某个日期时间自unix纪元以来的时间?

    我想找出 2009 年 10 月 1 日 9 00 BST 的 UNIX 时间 即自 Unix 纪元以来的秒数 我如何在 Linux 命令行上执行此操作 我知道你可以使用date UNIXTIME someformat 但是unix时间是我
  • Foldmethod=同时标记和语法?

    同一文件是否可以根据标记和语法使用折叠 Since foldmethod一次只能设置为一个 我认为您真正可以做到这一点的唯一方法是使用 set foldmethod expr并与foldexpr 即使如此 我也不确定它是否能够完全按照您的要
  • 在 Linux 中使用仅限 CLI 的工具生成磁盘使用情况图/图表

    在这个问题中 https stackoverflow com questions 32230 tracking down where disk space has gone on linux有人询问如何在 Linux 中显示磁盘使用情况 我
  • Vim / vi 生存指南

    基本的 vim 命令有哪些 新用户需要了解什么才能避免陷入麻烦 请每条评论一条命令 我发现不可替代的 因为它也可以在 vi 中使用 与 vim 的视觉模式不同 是标记 您可以用以下标记标记不同的点m 小写 然后是您选择的字母 例如 x 然后
  • 使用正在运行的进程的共享内存收集核心转储

    核心转储仅收集进程空间 而不收集为进程间通信创建的共享内存 如何使核心转储也包含正在运行的进程的共享内存 设置核心文件过滤器 proc PID coredump filter per http man7 org linux man page
  • 如何检测文本文件中大于 n 的一系列“空洞”(孔、与模式不匹配的线)?

    Case scenario cat Status txt 1 connected 2 connected 3 connected 4 connected 5 connected 6 connected 7 disconnected 8 di
  • 每个进程是否都存在内核堆栈?

    每个用户空间进程是否都存在一个内核堆栈和一个用户空间堆栈 如果两个堆栈都存在 那么每个用户空间进程应该有 2 个堆栈指针 对吗 在 Linux 中 每个任务 用户空间或内核线程 都有一个 8kb 或 4kb 的内核堆栈 具体取决于内核配置
  • 安全地记住 bash 脚本中的 ssh 凭据[重复]

    这个问题在这里已经有答案了 假设我有一个 bash 脚本 它通过 ssh 在远程计算机上执行命令 Do something here ssh otheruser host command1 Do something else ssh oth
  • 使用多线程的套接字服务器或文件服务器实现:概念不清楚

    请帮我理清这个概念 假设我们有一个使用线程实现的套接字端口服务器 套接字服务器侦听套接字端口 并在消息到达时创建一个线程来服务该请求 客户端代码向服务器发送给定数量的消息 该客户端代码也可以由多个用户在不同的计算机上运行 我知道客户端代码作
  • 以 Vim 的 -o - 模式打开 Grep 输出中的文件

    如何将文件列表放入 Vim 的 o 模式 我有一个文件列表作为 Grep 的输出 我运行失败 1 grep il sid vim o 2 grep il sid xargs vim o 3 grep il sid xargs vim 4 v
  • vagrant + virtualbox 等待机器启动时超时

    抱歉 我对 vagrant 很陌生 我正在运行 vagrant 1 4 3 和 virtualbox 4 3 26 我的主机操作系统是 ubuntu 14 04 2 我是一名 php 开发人员 我正在努力让我的开发环境为几个项目做好准备 我
  • 数百个空闲线程的影响

    我正在考虑使用可能数百个线程来实现通过网络管理设备的任务 这是一个在带有 Linux 内核的 powerpc 处理器上运行的 C 应用程序 在每个任务进行同步以将数据从设备复制到任务的初始阶段之后 任务变得空闲 并且仅在收到警报或需要更改一
  • C中的内存使用问题

    请帮忙 操作系统 Linux 其中 sleep 1000 中 此时 top 显示Linux任务 给我写了7 7 MEM使用 valgrind 未发现内存泄漏 我明白 写得正确 所有 malloc 结果都是 NULL 但是为什么这次 睡眠 我

随机推荐

  • 为什么推荐科研工作使用git

    为什么推荐科研工作使用git 每个人都会犯错 而使用Git 的最大好处就在于 几乎在所有的情况下你都能 撤消 你的错误操作 比如如果你忘记了把一个小小的改动包含进来 因此你要改正你的上个提交 又或者你想要撤销一个完整的提交 因为这个功能有可
  • 【C/C++】获取计算机CPUID序列号

    1 GetGPUId h文件 pragma once include
  • 【解决报错】c#使用ManagedWifi报错出现“不能作为非托管结构进行封送处理;无法计算有意义的大小或偏移量。”

    最近在做C 上位机wifi通信的时候使用了MangedWifi库 但这个库并没有想象中好用 遇到了不少问题 首先网上流传的例程又不能运行 再接着当wifi断开或连接时会异常退出的bug 通过反反复复的调试后 我最终确认了错误的来源 发现是M
  • 微信公众号 config:fail,Error: 系统错误,错误码:1

    微信公众号开发 微信开发者工具 打开调试模式 出现config fail Error 系统错误 错误码 1 查看一下wx config是否成功渲染了 重新赋值 修改后的代码如下 chooseImage var this this 新增代码块
  • 生产环境lvm磁盘扩容!!!

    一次就好 亲身体验生产环境lvm磁盘扩容 这一天体验了真正的生产环境 三急 中午客户打电话说报表几个小时没更新了 是不是你们系统有问题啊 于是开始排除发现磁盘空间满了 需要进行扩容 咱又没有扩容经验潜心研究一下午 终于得出结论 以下将描述我
  • 如何将计算机的硬盘分割,电脑硬盘如何快速分区

    电脑硬盘一般有2个盘或者4个盘 怎样自己增添一个硬盘 或者来均分电脑那300G或者500G的硬盘空间呢 今天学习啦小编给大家介绍下电脑硬盘如何快速分区吧 电脑硬盘快速分区方法一 1 点击我的电脑 点击鼠标右键 选择管理项 2 打开后选择磁盘
  • Python基础教程,Python入门教程(非常详细)

    Python 英文本意为 蟒蛇 直到 1989 年荷兰人 Guido van Rossum 简称 Guido 发明了一种面向对象的解释型编程语言 后续会介绍 并将其命名为 Python 才赋予了它表示一门编程语言的含义 图 1 Python
  • C# TCPclient 服务器保持长连接的一种办法(变相的心跳包功能)

    本文章向大家介绍C TCPclient 服务器保持长连接的一种办法 变相的心跳包功能 主要包括C TCPclient 服务器保持长连接的一种办法 变相的心跳包功能 使用实例 应用技巧 基本知识点总结和需要注意事项 具有一定的参考价值 需要的
  • neo4j从安装到远程访问一气呵成

    从安装到远程访问配置 安装Java JDK JDK下载 JDK配置环境 安装Neo4j Neo4j下载 系统变量设置 通过控制台启动 Neo4j 注册 Neo4j 服务 启动 Neo4j 服务 停止 Neo4j 服务 重启 Neo4j 服务
  • 【千律】C++基础:类的派生与继承

    include
  • postman环境设置

    本来是想在另一篇博客基础上接着写的 但是考虑到不想搞太长 干脆分开写 看起来更直接清楚一下 使用postman调接口 经常会遇到不同的的环境 但是接口是一样的 不想添加太多没用的请求 因为除了同一个接口请求要写多遍 更重要的是环境地址和端口
  • gprmax3.0安装、GPU加速(cuda)配置、通过python使用gprmax的问题

    gprmax3 0安装 GPU加速 cuda 配置 通过python使用gprmax的问题 一 安装过程 二 通过NVIDIA CUDA使用GPU加速功能 三 通过VS2019或VScode操纵gprmax 鉴于网上其他教程版本较低 本篇记
  • 【GD32篇】驱动AD7616完成数据采集

    1 AD7616介绍 1 1 概述 AD7616 是一款 16 位 DAS 数据采集系统 支持对 16 个通道进行双路同步采样 AD7616 采用 5 V 单电源供电 可以处理 10 V 5 V 和 2 5 V 真双极性输入信号 同时每对通
  • 代码随想录算法训练营第三天

    今天是算法训练营的第三天 写了454 四数相加 II这道题目 力扣链接 代码随想录链接 代码如下 class Solution def fourSumCount self nums1 List int nums2 List int nums
  • 在window模式下硬盘安装linux

    随着linux的迅速发展和其强大的安全体系的成熟 越来越多的人希望能学习linux 但也有不愿很快的离开window那中友好的界面 最好的办法就是在你的PC机上做两个系统 这样就可以学习和娱乐两不误 等到自己在linux上学习的狠命熟练的时
  • Android APK 程序实现自动更新,java服务命令处理无弹窗,终极解决方案

    安卓更新方式 网上五花八门 但是真正实现apk自动更新无痕迹的方式 少之又少 毕竟不要钱的方式 稳定的方式才能让开发者在困难中脱颖而出 安卓程序如何做到自动更新 安卓程序如何实现无弹框更新 1 安卓apk自动更新方式 a 第三方平台更新ap
  • 无向图有向图最小环

    floyd求 for int k 1 k lt n k for int i 1 i
  • 合肥先进光源高速数据采集网的规划

    合肥先进光源束测后台的初步设计 这里的网络相关的部分摘出来换个名字重新整理一下 合肥光源中 没有把数据量大的设备比如摄像头 示波器规划进单独的网络 所有的设备都直接接入控制网 运行实践的过程中 有过高帧率的一个摄像头就拖慢整个网络响应的情况
  • Java基础11--时间日期

    Java基础11 时间日期 文章目录 Java基础11 时间日期 获取当前日期时间 日期比较 使用 SimpleDateFormat 格式化日期 日期和时间的格式化编码 解析字符串为时间 Java 休眠 sleep Calendar类 Ca
  • I/O多路复用(select、poll、epoll)

    基本思想 1 先构造一张有关文件描述符的表 然后使用我们的select poll epoll函数 2 我们的应用程序会将这张表复制给内核 3 内核层初始化表中的需要检测的描述符 4 当检测到有文件操作时 则立即将文件描述符作为标志并返回给应