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

IO多路复用

(一)一些基础概念

  IO多路复用是什么?

    IO多路复用是一种机制,允许一个进程同时来监控多个文件描述符(fd)的IO事件,不使用多线程,只用一个进程来提高并发性和资源的利用,避免为每个连接或IO操作创建一个单独的进程或者线程,减少上下文切换的开销

Socket是什么?

   之前我们在网络编程的时候有使用过socket,我们可以把他抽象成客户端和服务器进程之间通信端点的一个抽象,就比如我们客户端给服务器发送数据,那么我们数据需要有一个出口,服务器接受数据需要一个入口,这个出口和入口就叫做Socket

fd是什么?

  刚刚我们就有提到过IO多路复用就是用进程来监控fd的IO事件,我们在linux中“一切皆文件”,fd本身是一个非负整数,我们可以来把他简单理解为数组的下标,我们根据fd就可以找到我们内部的一个资源,以此来进行访问和资源管理

用户态是什么?

  不能直接使用系统资源,也不能改变 CPU 的工作状态,并且只能访问这个用户程序自己的存储空间!

内核态是什么?

  系统中既有操作系统的程序,也有普通用户程序。为了安全性和稳定性,操作系统的程序不能随便访问,这就是内核态。即需要执行操作系统的程序就必须转换到内核态才能执行

  这也导致我们内核态和用户态的切换是很消耗资源的

(二)抽象理解

  在这一篇博客中我会讲到同步阻塞IO,同步非阻塞IO,select/poll,epoll

那我们来抽象的理解一下这几种方式

假设我是一个老师,我去收同学的作业

1)同步阻塞IO:学生在座位上坐好后,我去逐个的收取作业,收到了这个作业,我就拿走去下一个学生的位置,当这个学生找不到作业了,我就待在他旁边等着他找,直到找到了为止(此时我不会管其他同学,就会导致我们的IO阻塞了)

2)同步非阻塞IO:学生在座位上坐好后,我还是逐个的收取作业,收到了作业,我就拿走去下一个学生的位置,如果这个学生找不到作业了,我也不管她,我去收下一个学生的作业(此时我们就不会阻塞在一个学生哪里)

3)select/poll:学生在座位上坐好后,所有写完作业的学生大喊了一声:“快来收我的作业”(此时我知道有多少个学生写完了),然后就都不说话了,我就需要一个个的去问,是不是你写完了作业。

4)epoll:学生在座位上坐好后,写完作业的学生直接把作业交到了我的面前

(三)同步阻塞IO

首先我们来看一张图模拟一下服务器和客户端是如何进行交互的

  首先服务器完成初始化之后,会在accept进行阻塞,来等待用户给我们建立连接没有建立连接的客户端就进行阻塞,建立连接后我们会保存一个用户的fd,客户端会进行写操作,我们会把fd从用户态拿到内核态看看数据是否能够执行,如果可以那么执行好了就返回,如果没执行完就在内核态进行阻塞,这个期间其他客户端再发起连接请求,我们也不去管了

总结:某个socket连接阻塞,就会影响到其他socket的处理,这就会导致我们的资源会造成浪费,因为全部socket中一般不会全部就绪,所以我们就绪了的socket没有被处理反而浪费时间在未就绪socket上。

  如果我们想使用同步阻塞IO还想解决我上面所说的问题,那么我们可以使用多线程的方式,我们给每一个socket都分配一个线程,这样就可以解决阻塞问题,但此时又会有另一个问题,如果有1000个客户端连接,我们创建1000个线程?这也不合理啊,而且创建那么多线程,我客户端也没有就绪,创建的线程也是浪费

 所以我们勉强把他的用途放在并发量少且用户频繁传输获取数据的场景

(四)同步非阻塞IO

我们还是来看一张图

  在我们刚刚同步阻塞IO中,我们看到服务器是阻塞在accept的,但是此时我们就不阻塞在accept了,而是获取到一个非法的fd值(一个负数),然后继续循环,直到客户端发送了连接请求,那么发送了连接之后我们同样会保存用户的一个fd

  假设我们先的服务器跟四个客户端建立了连接那么我们用户态就有四个fd,当我们在用户空间调用某个fd的read函数时,我们先把此对应的fd拷贝到内核态,然后判断是否有数据到达,如果没有返回-1

  然后我们用户态收到这个-1之后,我们就会继续去检查下一个fd,直到某个fd的数据到达,然后返回一个fd的合法值

  总结:从系统层面解决了阻塞问题,但是因为我们又涉及到遍历,又涉及到用户态和内核态的频繁转换,所以他的效率也不是很高 

(五)select

首先我们来看一下select函数以及他的参数

当这个超市时间为-1时,就表示没有超时时间

  我们select的方式和同步阻塞IO和同步非阻塞IO不同,他们都是在用户态来进行一个遍历,而select是在内核态进行一个遍历(我们会直接把所有fd先从用户态拷贝到内核态)如果有已经准备好的fd我们就会返回一个fd就绪的一个数量,这样可以大幅度减少我们用户态和内核态的一个转换,我们来看一张图

  那我们已经知道了fd就绪的数量,如果>0我们就会在用户态进行一个遍历(有的人会说,你不是说遍历在内核态进行吗,这会怎么用户态和内核态都要进行遍历呢?确实都需要遍历,但是意义是不同的,select是已知有fd就绪了才会在用户空间遍历,而同步非阻塞IO则是我不知道是否就绪,我就是用户空间遍历一下拿这个fd去内核态瞅一眼,两者间涉及到的用户态内核态转换次数是完全不同的)然后我们就可以在用户态拿到我们的一个就绪fd对他进行处理,然后节奏进行select的调用

  我们刚刚说了扫描以此内核态有就绪fd的一个情况,那么没有就绪fd是什么样呢?

  我们最开始可能想到就是一直在内核空间进行遍历,此时对比同步非阻塞IO虽然还是效率高,但是还是多了一层轮询的开销

  所以我们select是这样实现的(我们这里来简单理解一下,因为我在网上看别人讲就是很抽象)我这里来用大白话方便咱们理解,本质上说我们会暂停这个内核态的一个遍历,然后等待一个客户端给我们发送一条数据时,我们就唤醒我们这个内核态的遍历(因为客户端发了数据,很可能有一条fd就就绪了)或者超时时间到了,然后遍历到了就进行返回,没有遍历到就继续暂停

  这是有一个问题,我们用户态是怎么告诉内核态我们要监听哪一些fd或者回参时哪些fd就绪了?

我们看一张图就是通过我们刚刚传入的参数

他的底层通过位图来实现的,入参时表示要监听哪些fd,回参时表示哪些fd就绪(这次虽然我们肉眼能看出来那个是1,得知哪些fd就绪了,但是程序是不知道,是需要遍历的)

总结:将我们fd是否就绪检查逻辑放到了内核态,避免了用户态和内核态的频繁调用,只告诉你有事件就绪,但是没告诉你具体是哪一个fd

优点:不需要每个fd都进行用户态和内核态的转换

缺点:位图的长度存在限制,所以我们最多监听1024个(默认)

还是涉及到用户态和内核态的拷贝(fd多了资源消耗大)

不知道哪些fd就绪了,还是需要遍历

入参的三个fd_set的入参和回参代表含义不同,每次调用需要我们去重置

(六)poll

我们还是来看一下参数,因为poll跟select很像,这里就不多赘述

优点:我们使用了链表去存fd(select用位图)没有1024的限制了

不需要每个fd都进行用户态和内核态转换

对比select我们不需要把fdset重置了,因为我们把监听的事件和就绪的事件分开存储

(七)epoll

这是我们epoll的三个函数

我们先来讲一下三个函数的作用

epoll_create:用来创建一个epoll实例,在内核态中开辟空间来维护epoll的数据结构(红黑树,链表等),同时返回一个代表该epoll的fd

epoll_ctl:用于像epoll实例中添加,修改或者删除要监控的fd它有三个参数,分别是epoll实例的文件描述符、操作类型(如添加EPOLL_CTL_ADD、修改EPOLL_CTL_MOD、删除EPOLL_CTL_DEL)以及要操作的文件描述符和事件结构体。

epoll_wait:用于等待epoll实例中就绪的文件描述符,他会阻塞当前线程,直到有fd就绪或者超时,返回继续的文件描述符数量和相关信息

  内核数据结构:epoll在内核中维护了红黑树和就绪链表等数据结构。红黑树用于存储用户通过 epoll_ctl 添加进来的需要监控的文件描述符,保证了快速的查找和插入删除操作。就绪链表则用于存放已经就绪的文件描述符,当有文件描述符对应的IO事件发生时,内核会将其放入就绪链表。

  事件通知机制:epoll使用回调机制来实现事件通知。当文件描述符上发生了注册的事件(如可读、可写等)时,内核会调用相应的回调函数,将该文件描述符添加到就绪链表中。用户空间的程序通过调用 epoll_wait 函数,就可以获取到就绪链表中的文件描述符,从而得知哪些文件描述符有事件发生。

工作模式
 
- 水平触发(LT):是epoll的默认工作模式。在这种模式下,当文件描述符上有未处理的事件时, epoll_wait 会不断返回该文件描述符,直到用户对该文件描述符上的事件进行了处理。
 
- 边缘触发(ET):是一种更高效的触发模式。在ET模式下,只有当文件描述符上的事件状态发生变化时, epoll_wait 才会返回该文件描述符。这就要求用户在收到事件通知后,必须尽可能多地处理该文件描述符上的事件,直到没有更多的事件可读或可写,否则可能会错过一些事件。

使用epoll解决了poll的用户态和内核态的转换问题,同时解决了我们用户态不知道哪一个fd就绪需要遍历一次的问题

但是epoll的跨平台性不够好,同时相较于epoll,select更轻量,可移植性高,在监听连接数和事件少的情况下,select可能更好

select与epoll

那我们来对比一下select和epoll:
跨平台兼容性需求高
select在几乎所有主流操作系统上都有支持,如Unix、Linux、Windows等,而epoll是Linux特有的机制。如果开发的程序需要在多种操作系统上运行,且对不同平台的兼容性要求很高,使用select可以减少因平台差异带来的开发和维护成本,确保程序在各平台上都能稳定运行。
 
监控的文件描述符数量少
 
当需要监控的文件描述符数量较少时,select的简单性和开销小的特点就可能成为优势。因为select的实现相对简单,在这种情况下其线性扫描文件描述符集合的时间成本并不高,不会对性能产生明显影响,而且不需要像epoll那样创建额外的内核数据结构等,资源占用相对较少。

相关文章:

  • 模型 - QwQ-32B
  • VSCode输入npm xxx,跳转到选择应用
  • 双向选择排序算法
  • qt作业day5
  • 进程相关知识day1
  • 【经验分享】Ubuntu20.04编译RK3568 AI模型报错问题(已解决)
  • 时间序列预测实操
  • better-sqlite3之exec方法
  • hom_mat2d_to_affine_par 的c#实现
  • django中序列化器serializer 的高级使用和需要注意的点
  • Unity中Stack<T>用法以及删除Stack<GameObject>的方法
  • WordPress开发到底是开发什么?
  • 在 Aspire 项目下使用 AgileConfig
  • Python学习第十天
  • 数据库复习(第五版)- 第六章 关系数据理论
  • 阿里云MaxCompute面试题汇总及参考答案
  • Electron-Forge + Vue3 项目初始化
  • 010---基于Verilog HDL的分频器设计
  • 二阶RC+PWM实现DDS
  • 风控模型算法面试题集结
  • 哪个网站做网销更好/网络营销与直播电商专业就业前景
  • 网站建设邀标函/给网站做seo的价格
  • 怎样做美瞳网站/开网店怎么开 新手无货源
  • 电脑做网站怎么解析域名/吸引客人的产品宣传句子
  • 呼和浩特制作网站/best网络推广平台
  • 如何做网站充值接口/刷粉网站推广马上刷