当前位置: 首页 > news >正文

【Linux】48.高级IO(2)

文章目录

      • 1.3.5 socket就绪条件
      • 1.3.6 select的特点
      • 1.3.7 select缺点
      • 1.3.8 I/O多路转接之poll [选学]
      • 1.3.9 socket就绪条件
      • 1.3.10 poll的优点
      • 1.3.11 poll的缺点
      • 1.3.12 I/O多路转接之epoll
      • 1.3.13 epoll的相关系统调用
        • epoll_create
        • epoll_ctl
        • epoll_wait
      • 1.3.14 epoll工作原理


1.3.5 socket就绪条件

读就绪

  • socket内核中, 接收缓冲区中的字节数, 大于等于低水位标记SO_RCVLOWAT. 此时可以无阻塞的读该文件描述符, 并且返回值大于0;
  • socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;
  • 监听的socket上有新的连接请求;
  • socket上有未处理的错误;

写就绪

  • socket内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小), 大于等于低水位标记SO_SNDLOWAT, 此时可以无阻塞的写, 并且返回值大于0;
  • socket的写操作被关闭(close或者shutdown). 对一个写操作被关闭的socket进行写操作, 会触发SIGPIPE信号;
  • socket使用非阻塞connect连接成功或失败之后;
  • socket上有未读取的错误;

异常就绪(选学)

  • socket上收到带外数据. 关于带外数据, 和TCP紧急模式相关(回忆TCP协议头中, 有一个紧急指针的字段),

1.3.6 select的特点

  • 可监控的文件描述符个数取决与sizeof(fd_set)的值. 我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096.
  • 将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,
    1. 用于再select 返回后,array作为源数据和fd_set进行FD_ISSET判断。
    2. select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。

备注: fd_set的大小可以调整,可能涉及到重新编译内核


1.3.7 select缺点

  • 每次调用select, 都需要手动设置fd集合, 从接口使用角度来说也非常不便.
  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
  • 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
  • select支持的文件描述符数量太小.

1.3.8 I/O多路转接之poll [选学]

poll函数接口

#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);// pollfd结构
struct pollfd {int fd; /* file descriptor */short events; /* requested events */short revents; /* returned events */
};

参数说明

  • fds是一个poll函数监听的结构列表. 每一个元素中, 包含了三部分内容: 文件描述符, 监听的事件集合, 返回的事件集合.
  • nfds表示fds数组的长度.
  • timeout表示poll函数的超时时间, 单位是毫秒(ms).

events和revents的取值:

a91c5409a16698ff86be447804b978f8

6812da0e8afb0ed2a38685ddf319650b

返回结果

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

1.3.9 socket就绪条件

同select


1.3.10 poll的优点

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

  • pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式. 接口使用比select更方便.
  • poll并没有最大数量限制 (但是数量过大后性能也是会下降).

1.3.11 poll的缺点

poll中监听的文件描述符数目增多时

  • 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符.
  • 每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中.
  • 同时连接的大量客户端在一时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增长, 其效率也会线性下降.

1.3.12 I/O多路转接之epoll

epoll初识:

按照man手册的说法: 是为处理大批量句柄而作了改进的poll.

它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44)

它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路


1.3.13 epoll的相关系统调用

epoll 有3个相关的系统调用

epoll_create
int epoll_create(int size);

创建一个epoll的句柄.

  • 自从linux2.6.8之后,size参数是被忽略的.
  • 用完之后, 必须调用close()关闭.
epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll的事件注册函数.

  • 它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型.
  • 第一个参数是epoll_create()的返回值(epoll的句柄).
  • 第二个参数表示动作,用三个宏来表示.
  • 第三个参数是需要监听的fd.
  • 第四个参数是告诉内核需要监听什么事.

第二个参数的取值:

  • EPOLL_CTL_ADD :注册新的fd到epfd中;
  • EPOLL_CTL_MOD :修改已经注册的fd的监听事件;
  • EPOLL_CTL_DEL :从epfd中删除一个fd;

struct epoll_event结构如下:

b2292a272b7c0e18809a8c8e690b6dd0

events可以是以下几个宏的集合:

  • EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);
  • EPOLLOUT : 表示对应的文件描述符可以写;
  • EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来);
  • EPOLLERR : 表示对应的文件描述符发生错误;
  • EPOLLHUP : 表示对应的文件描述符被挂断;
  • EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.
  • EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要再次把这个socket加入到EPOLL队列里.

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

收集在epoll监控的事件中已经发送的事件.

  • 参数events是分配好的epoll_event结构体数组.
  • epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存).
  • maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size.
  • 参数timeout是超时时间 (毫秒,0会立即返回,-1是永久阻塞).
  • 如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时, 返回小于0表示函数失败

1.3.14 epoll工作原理

73b45dc154693aa1cec2476d572399f2

  • 当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关.
struct eventpoll{ .... /*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/ struct rb_root rbr; /*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/ struct list_head rdlist; .... 
};
  • 每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件.
  • 这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度).
  • 而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时会调用这个回调方法.
  • 这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中.
  • 在epoll中,对于每一个事件,都会建立一个epitem结构体.
struct epitem{ struct rb_node rbn;//红黑树节点 struct list_head rdllink;//双向链表节点 struct epoll_filefd ffd; //事件句柄信息 struct eventpoll *ep; //指向其所属的eventpoll对象 struct epoll_event event; //期待发生的事件类型 
} 
  • 当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可.
  • 如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户. 这个操作的时间复杂度是O(1).

总结一下, epoll的使用过程就是三部曲:

  • 调用epoll_create创建一个epoll句柄;
  • 调用epoll_ctl, 将要监控的文件描述符进行注册;
  • 调用epoll_wait, 等待文件描述符就绪;

相关文章:

  • vs2017编译ncnn库
  • PostgreSQL架构
  • C语言入门
  • Vue3 Form 表单限制输入小写字母、数字和下划线
  • 使用VGG-16模型来对海贼王中的角色进行图像分类
  • 同源策略深度防御指南:CSP 高级应用与企业微信全场景适配(含 report-uri 实战)
  • 【产品经理从0到1】用户端产品设计与用户画像
  • 华三(H3C)IRF堆叠心跳的LACP MAD、BFD MAD和ARP MAD差异
  • HJ17 坐标移动【牛客网】
  • C++之set与map介绍
  • 《光与影:33号远征队》栩栩如生的角色动画是如何创建的?
  • vue+springboot+element-ui实现table的树懒加载
  • Redis学习专题(三)主从复制
  • Java大师成长计划之第28天:处理多线程的Web应用
  • python-数据可视化(大数据、数据分析、可视化图像、HTML页面)
  • 生产专属ERP系统源码,一套集成企业财务、供应链、生产、销售等核心业务流程的管理系统
  • 第三十天打卡
  • 【Java高阶面经】1.微服务架构核心:服务注册与发现之AP vs CP选型全攻略
  • 电子电器架构 --- 汽车高性能计算
  • 关于 APK 反编译与重构工具集
  • 商务部:“一国一策”落实对非合作“十大伙伴行动”
  • 嘴巴总是发干,喝水也不管用?小心是这几种疾病的警报
  • 权威访谈丨国家疾控局就《世界卫生组织大流行协定》答记者问
  • 上海婚登人聂晶:见证爱情故事开启,也向长久婚姻致敬
  • 来论|以法治之力激发民营经济新动能
  • 牛市早报|年内首次存款利率下调启动,5月LPR今公布