首页 > 系统相关 >Linux:多路转接 select、poll、epoll

Linux:多路转接 select、poll、epoll

时间:2024-09-09 22:51:40浏览次数:13  
标签:文件 set epoll 描述符 fd Linux poll select

1:select

#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

   select 函数是 POSIX 标准定义的一个系统调用,用于监视多个文件描述符(file descriptors),以确定它们是否具有可读、可写或异常条件。

1. 参数解释

        1: int nfds:这是监视的文件描述符集合中最大的文件描述符加一。也就是说,如果你监视的文件描述符最大是 fd_max,那么你应该将 nfds 设置为 fd_max + 1。

        2: fd_set *readfds:这是一个指向 fd_set 结构的指针,该结构包含了你想要监视以检查是否有数据可读的文件描述符集合。如果 NULL,则表示不监视任何读文件描述符。

        3: fd_set *writefds:这是一个指向 fd_set 结构的指针,包含了你想要监视以检查是否可写的文件描述符集合。如果 NULL,则表示不监视任何写文件描述符。

        4: fd_set *exceptfds:这是一个指向 fd_set 结构的指针,包含了你想要监视以检查是否有异常条件的文件描述符集合。在大多数情况下,这用于检查 OOB(out-of-band,紧急)数据。如果 NULL,则表示不监视任何异常文件描述符。

        5: struct timeval *timeout:这是一个指向 timeval 结构的指针,它指定了 select 调用等待文件描述符状态改变的最大时间。

参数 timeout 取值:

        • NULL: 则表示 select() 没有 timeout, select 将一直被阻塞, 直到某个文件描述符上发生了事件;

        • 0: 仅检测描述符集合的状态, 然后立即返回, 并不等待外部事件的发生。

        特定的时间值: 如果在指定的时间段里没有事件发生, select 将超时返回。

2. 函数返回值

        1: 如果有文件描述符准备好了,select 返回准备好的文件描述符的数量。

        2: 如果返回 0 代表在描述词状态改变前已超过 timeout 时间, 没有返回。

        3: 当有错误发生时则返回-1, 错误原因存于 errno, 此时参数 readfds, writefds,exceptfds 和 timeout 的值变成不可预测。

错误值可能为:

        • EBADF 文件描述词为无效的或该文件已关闭。

        • EINTR 此调用被信号所中断。

        • EINVAL 参数 n 为负值。

        • ENOMEM 核心内存不足。

3. fd_set

        其实这个结构就是一个整数数组, 更严格的说, 是一个 "位图"。使用位图中对应的位来表示要监视的文件描述符。

4. fd_set 相关接口
void FD_CLR(int fd, fd_set *set); // 用来清除描述词组 set 中相关fd 的位

int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组 set 中相关fd 的位是否为真

void FD_SET(int fd, fd_set *set); // 用来设置描述词组 set 中相关fd 的位

void FD_ZERO(fd_set *set); // 用来清除描述词组 set 的全部位
5. timeval 

5. 常见使用
fd_set readset;
FD_SET(fd,&readset);
select(fd+1,&readset,NULL,NULL,NULL);
if(FD_ISSET(fd,readset)){……}
6. 理解 select 执行过程

        fd_set 长度为 1 字节, fd_set中的每一 bit 可以对应一个文件描述符 fd。 则 1 字节长的 fd_set 最大可以对应 8 个 fd。

        (1): FD_ZERO(&set); 初始化fd_set,所有位都为0,即0000,0000

        (2)FD_SET(5, &set); 将第5位设置为1,其他位保持不变,变为0001,0000

        (3):接着将fd=2fd=1加入集合,分别设置第2位和第1位为1,变为0001,0011

        (4):select(6, &set, 0, 0, 0); 调用select函数,监控fd_set中文件描述符的可读事件。参数6表示监控的文件描述符最大值为5(因为是从0开始计数的,所以6-1=5),select会阻塞直到有事件发生或超时。

        (5):如果fd=1fd=2上都发生了可读事件,select会返回,此时fd_set中只有第1位和第2位为1,变为0000,0011。注意,没有事件发生的fd=5对应的位被清空了,因为select函数的行为是只保留那些有事件发生的文件描述符。

 7. select 的特点

         1:可监控的文件描述符个数取决于 sizeof(fd_set) 的值. 我这边服务器上sizeof(fd_set)= 512, 每 bit 表示一个文件描述符, 则服务器上支持的最大文件描述符是 512*8=4096

        2:将 fd 加入 select 监控集的同时, 还要再使用一个数据结构 array (辅助数组)保存放到 select监控集中的 fd。
        一:是用于再 select 返回后, array 作为源数据和 fd_set 进行 FD_ISSET 判断。
        二:是 select 返回后会把以前加入的但并无事件发生的 fd 清空, 则每次开始select 前都要重新从 array 取得 fd 逐一加入(FD_ZERO 最先), 扫描 array 的同时取得 fd 最大值 maxfd, 用于 select 的第一个参数。

8. select 缺点

        1:每次调用 select, 都需要手动设置 fd 集合, 从接口使用角度来说也非常不便。

        2:每次调用 select, 都需要把 fd 集合从用户态拷贝到内核态, 这个开销在 fd 很多时会很大。

        3:同时每次调用 select 都需要在内核遍历传递进来的所有 fd, 这个开销在 fd 很多时也很大。

        5:select 支持的文件描述符数量太小。
 

9. select 应用


SelectServer · XiangChao/Linux - 码云 - 开源中国 (gitee.com)icon-default.png?t=O83Ahttps://gitee.com/RuofengMao/linux/tree/master/SelectServer

2:socket 就绪条件

1. 读事件就绪(Readable)

        • socket 内核中, 接收缓冲区中的字节数, 大于等于低水位标记 SO_RCVLOWAT。 此时可以无阻塞的读该文件描述符, 并且返回值大于 0;

        • socket TCP 通信中, 对端关闭连接, 此时对该 socket 读, 则返回 0

        • 监听的 socket 上有新的连接请求;

        • socket 上有未处理的错误;

 2. 写就绪(Writable)

        • socket 内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小), 大于等于低水位标记 SO_SNDLOWAT, 此时可以无阻塞的写, 并且返回值大于 0;

        • socket 的写操作被关闭(close 或者 shutdown). 对一个写操作被关闭的 socket 进行写操作, 会触发 SIGPIPE 信号;

        • socket 使用非阻塞 connect 连接成功或失败之后;

        • socket 上有未读取的错误;

3. 异常事件就绪(Exception)

        •异常事件就绪意味着socket上发生了一些非正常的事件,如连接断开、错误发生等。

        •这通常发生在连接被对方强制关闭,或者在socket上发生了一些错误,需要立即处理。

        •在select函数中,如果一个socket的异常事件就绪,那么在调用select后,该socket会在返回的异常文件描述符集合中被标记。

 3:poll

#include <poll.h>

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

struct pollfd {
    int   fd;         /* 文件描述符 */
    short events;     /* 需要监控的事件 */
    short revents;    /* 已经发生的事件,由 `poll` 函数填充 */
};

参数说明:
    fd:需要监控的文件描述符。
    events:需要监控的事件类型,可以是以下宏的组合:
    POLLIN:有数据可读。
    POLLOUT:写入不会阻塞。
    POLLERR:发生错误。
    POLLHUP:对端关闭连接。
    revents:实际发生的事件,由 poll 函数在调用返回后填充。

返回值:
    返回值小于 0, 表示出错;
    返回值等于 0, 表示 poll 函数等待超时;
    返回值大于 0, 表示 poll 由于监听的文件描述符就绪而返回;

1. events 和 revents

2. poll优点

        1. 不同与select使用三个位图来表示三个fdset的方式,poll使用一个pollfd的指针实现。

        2. pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式. 接口使用比select更方便。

        3. poll并没有最大数量限制 (但是数量过大后性能也是会下降)。

3. poll缺点

       1. 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。

       2. 每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中。

       3. 同时连接的大量客户端在一时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增长, 其效率也会线性下降。

4. poll 应用

 Poll · XiangChao/Linux - 码云 - 开源中国 (gitee.com)icon-default.png?t=O83Ahttps://gitee.com/RuofengMao/linux/tree/master/Poll

4:epoll 

1:
    创建 epoll 实例:

    int epoll_create(int size);
 
    size 参数通常被忽略,但可以作为内核的提示,告知可能监控的文件描述符数量。

2:
    使用 epoll_ctl 注册事件:

    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    
    epfd:epoll 实例的文件描述符。
    op:操作类型,如 EPOLL_CTL_ADD(添加)、EPOLL_CTL_MOD(修改)、EPOLL_CTL_DEL(删除)。
    fd:要监控的文件描述符。
    event:指向 struct epoll_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 : 表示对应的文件描述符可以读 (包括对端 SOCKET 正常关闭);
    • EPOLLOUT : 表示对应的文件描述符可以写;
    • EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来);
    • EPOLLERR : 表示对应的文件描述符发生错误;
    • EPOLLHUP : 表示对应的文件描述符被挂断;
    • EPOLLET : 将 EPOLL 设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.
    • EPOLLONESHOT: 只监听一次事件, 当监听完这次事件之后, 如果还需要继
续监听这个 socket 的话, 需要再次把这个 socket 加入到 EPOLL 队列里.

3:
    等待事件的发生:

    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    
    epfd:epoll 实例的文件描述符。
    events:用于存储发生的事件的数组。
    maxevents:数组的大小,即最多可以返回的事件数量。
    timeout:等待时间(毫秒),-1 表示无限期等待,0 表示立即返回。

4:
    处理事件:
    在 epoll_wait 返回后,遍历 events 数组,处理每个发生的事件。
 1. epoll_wait

        参数 events 是分配好的 epoll_event 结构体数组。

        • epoll 将会把发生的事件赋值到 events 数组中 (events 不可以是空指针, 内核只负责把数据复制到这个 events 数组中, 不会去帮助我们在用户态中分配内存)。

        • maxevents 告之内核这个 events 有多大, 这个 maxevents 的值不能大于创建epoll_create()时的 size。

        • 参数 timeout 是超时时间 (毫秒, 0 会立即返回, -1 是永久阻塞)。

        如果函数调用成功返回对应 I/O 上已准备好的文件描述符数目, 如返回 0 表示已超时, 返回小于 0 表示函数失败。

2. epoll 工作原理

         每一个 epoll 对象都有一个独立的 eventpoll 结构体, 用于存放通过 epoll_ctl 方法向 epoll 对象中添加进来的事件。

        • 这些事件都会挂载在中, 如此, 重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是 lgn, 其中 n 为树的高度)。

        • 而所有添加到 epoll 中的事件都会与设备(网卡)驱动程序建立回调关系, 也就是说, 当响应的事件发生时会调用这个回调方法。

        • 这个回调方法在内核中叫 ep_poll_callback,它会将发生的事件添加到 rdlist 双链表中。

         在 epoll 中, 对于每一个事件, 都会建立一个 epitem 结构体。

        • 当调用 epoll_wait 检查是否有事件发生时, 只需要检查 eventpoll 对象中的rdlist 双链表中是否有 epitem 元素即可。

        • 如果 rdlist 不为空, 则把发生的事件复制到用户态, 同时将事件数量返回给用户. 这个操作的时间复杂度是 O(1)。

 3. 接口总结

         调用 epoll_create 创建一个 epoll 句柄;

        • 调用 epoll_ctl, 将要监控的文件描述符进行注册;

        • 调用 epoll_wait, 等待文件描述符就绪;

4. epoll 的优点

        • 接口使用方便: 虽然拆分成了三个函数, 但是反而使用起来更方便高效. 不需要每次循环都设置关注的文件描述符, 也做到了输入输出参数分离开。

        • 数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频繁(而 select/poll 都是每次循环都要进行拷贝)。

        • 事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中, epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1)。 即使文件描述符数目很多, 效率也不会受到影响。

        • 没有数量限制: 文件描述符数目无上限。

注意:

        我们定义的 struct epoll_event 是我们在用户空间中分配好的内存. 势必还是需要将内核的数据拷贝到这个用户空间的内存中。

5. epoll 应用

Epoll · XiangChao/Linux - 码云 - 开源中国 (gitee.com)icon-default.png?t=O83Ahttps://gitee.com/RuofengMao/linux/tree/master/Epoll 

标签:文件,set,epoll,描述符,fd,Linux,poll,select
From: https://blog.csdn.net/MaoRuofeng/article/details/142064919

相关文章

  • linux 安装软件
    转自:http://os.51cto.com/art/201003/186467.htm特别值得一提的是Linux操作系统有很多值得学习的地方,这里我们主要介绍Linux操作系统,包括介绍Linux操作系统等方面。Linux操作系统软件安装方法总结一、rpm包安装方式步骤:引用:1、找到相应的软件包,比如soft.version.rpm,下载到本......
  • Linux网盘,编程者的选择,让技术为数据服务,创造无限价值!“#Linux系统编程《网盘项目》
    "Linux网盘,编程者的选择,让技术为数据服务,创造无限价值!"#Linux系统编程《网盘项目》前言预备知识一、项目功能二、程序基本框架2.1服务器程序流图2.2客户端程序流图三、程序代码解析3.1服务器代码解析3.1.1主函数代码解析3.1.2信息处理函数代码解析3.1.3获取命......
  • Linux系统上安装Docker的详细教程
    感谢浪浪云支持发布浪浪云活动链接:https://langlangy.cn/?i8afa52文章目录1.在Ubuntu/Debian系统上安装Docker1.1更新软件包1.2安装依赖包1.3添加DockerGPG密钥1.4添加Docker仓库1.5安装Docker引擎1.6启动并验证Docker2.在CentOS/RHEL系统上安装Docker2.1......
  • 【实验楼】Linux系统管理-实验一:初识命令行
    Linux系统管理-实验一:初识命令行尝试简单命令请在命令行中显示当前所处的目录的名字。pwd请在命令行中显示当前主机名。hostname请在命令行中显示当前所使用的用户的名称。whoami尝试命令的选项请下达在15分钟内模拟关机的命令。shutdown-k15shutdown命令:https:/......
  • windows和Linux常用路径
    Linux/home普通用户在此目录下/etc 程序的配置文件/etc/resolv,conf  存放dns信息/etc/passwd文件内有用户的所有基本信息,密码是*储存在shadow里/etc/shadow存放密码/etc/group存放的组信息/tmp 存放临时文件/user/local/bin本地命令/car/spool/mail存......
  • Linux下io模型
    目录一. 阻塞式IO:最常见、效率低、不耗费cpuudp丢包​编辑tcp粘包tcp拆包二.非阻塞io:轮询、耗费CPU,可以处理多路IO概念设置非阻塞的方式1.通过函数自带参数设置2.通过设置文件描述符的属性,把对应属性设置为非阻塞三. 信号驱动IO/异步IO:异步通知方式,需要底层驱动......
  • Linux上rpm安装MySQL8
    1.下载安装包下载链接https://downloads.mysql.com/archives/community/或者在MySQL官网根据提示一步一步点进去选择合适的版本2.上传至服务器,解压tar-xvfmysql-8.0.33-1.el7.x86_64.rpm-bundle.tar3.安装参考官方文档https://dev.mysql.com/doc/refman/8.0/en......
  • WSL(Windows Subsystem for Linux)密码忘记了怎么办
    首先以管理员身份打开powershell输入wsl.exe--userroot(root账户)输入passwdroot接着输入新密码再确认一遍就OK了修改成功(普通账户)输入 passwd(username)修改成功 ......
  • windows和Linux上安装nvm及相关配置
    Windows安装:1、详情参考:https://blog.csdn.net/goods_yao/article/details/137854626本文详细介绍了在Windows系统中使用nvm(NodeVersionManager)管理Node.js版本的过程,包括卸载Node.js、nvm的安装与配置、修改npm镜像源、环境变量设置及常见问题解决。 Linux(centos7.6)安装:0、机......
  • 对于linux文件权限的思考
    ​ 一个文件或目录可以从权限和其所属讲起。1.从用户的创建开始​ 一个用户从创建开始就有了所属用户组,这是因为每个用户必须属于一个主组,通常在用户创建时系统会默认为其创建一个同名的主组(后续可由超级用户(root)通过修改用户的账户设置来更改主组)。​ 那么由该用户创建的文......