【网络IO】IO多路复用
🧴【网络IO】IO多路复用
2022-3-23
| 2023-12-17
0  |  0 分钟
type
status
date
slug
summary
tags
category
icon
password
Sub-item
Last edited time
Dec 17, 2023 01:52 AM
Parent item
领域

异步同步

都是非阻塞。同步分为阻塞非阻塞,关系如下:
notion image

内核接收网络数据全过程

notion image
  1. 计算机收到了对端传送的数据
  1. 数据经由网卡DMA传送到内存
  1. 网卡通过中断信号通知cpu有数据到达,cpu执行中断程序
    1. 中断程序先将网络数据写入到对应socket的接收缓冲区里面
    2. 唤醒进程A,重新将进程A放入工作队列中
    3. notion image

阻塞IO

由系统调用read,导致线程一直等待数据返回。
notion image

非阻塞IO

系统调用read后立即返回一个状态,当数据达到内核缓冲区之前都是非阻塞的,即返回一个系统调用状态。
notion image
非阻塞IO需要不停地查询数据有没有准备好。

IO多路复用

多路复用是一种同步IO模型,实现一个线程可以监视多个文件句柄

select

通过select,我们可以把一组文件描述符的数组发给操作系统, 让操作系统去遍历,确定哪个文件描述符可以读写, 然后告诉我们去处理,处理时也需要再遍历一次:
notion image
select流程如下:
notion image
  1. 程序同时监视如下图的sock1、sock2和sock3三个socket,那么在调用select之后,操作系统把进程A分别加入这三个socket的等待队列中。程序阻塞。
    1. notion image
  1. kernel就会轮询检查所有select负责的fd,当找到一个client中的数据准备好了,select就会返回,中断程序将唤起进程。
    1. notion image
所谓唤起进程,就是将进程从所有的等待队列中移除,加入到工作队列里面。如下图所示:
notion image
notion image
  1. 程序就会系统调用(例如recvfrom),将数据从kernel复制到进程缓冲区。当进程A被唤醒后,它知道至少有一个socket接收了数据。程序只需遍历一遍socket列表,就可以得到就绪的socket。
虽然服务器进程会被select阻塞,但是select会利用内核不断轮询监听其他客户端的IO操作是否完成。

select机制的问题

  1. 一旦某个socket收到数据后,需要重新select所有IO。每次调用select,都需要把fd_set集合从用户态拷贝到内核态,如果fd_set集合很大时,那这个开销也很大;
  1. 同时,每次调用select都需要在内核遍历传递进来的所有fd_set,如果fd_set集合很大时,那这个开销也很大;
  1. 为了减少数据拷贝带来的性能损坏,内核对被监控的fd_set集合大小做了限制,并且这个是通过宏控制的,大小不可改变(32个整数大小,1024bits) — — 一次最多只能select 1024个socket
  1. 进程被唤醒后,程序并不知道哪些socket收到数据,还需要遍历一次。

poll

poll和select一样,但是对select进行了一些改进。但只解决了上述select的第3个问题,有了以下的改进:
  • 不限制监听槽的个数。poll使用 pollfd 结构,而不是select结构fd_set结构,所以poll是链式的,没有最大连接数的限制。
  • 重复通知。poll有一个特点是水平触发,也就是通知程序fd就绪后,这次没有被处理,那么下次poll的时候会再次通知同个fd已经就绪。

epoll

epoll相比poll更进一步,除了没有对描述符数目的限制,它所支持的文件描述符上限是整个系统最大可以打开的文件数目
notion image
因为select|/poll每次有数据到来并处理完后都需要重新传递列表给内核,把是将“维护等待队列”和“阻塞进程”两个步骤合二为一,导致效率比较低。然而大多数应用场景中,需要监视的socket相对固定,并不需要每次都修改。epoll将这两个操作分开,先用epoll_ctl维护等待队列,再调用epoll_wait阻塞进程。显而易见的,效率就能得到提升。
另一个低效的原因是序不知道哪些socket收到数据,只能一个个遍历。如果内核维护一个“就绪列表”,引用收到数据的socket,就能避免遍历。如下图所示,计算机共有三个socket,收到数据的sock2和sock3被rdlist(就绪列表)所引用。当进程被唤醒后,只要获取rdlist的内容,就能够知道哪些socket收到数据。
notion image
  1. 创建epoll对象
    1. 如下图所示,当某个进程调用epoll_create方法时,内核会创建一个eventpoll对象(也就是程序中epfd所代表的对象)。eventpoll对象也是文件系统中的一员,和socket一样,它也会有等待队列。
      notion image
  1. 维护监视列表
    1. 创建epoll对象后,可以用epoll_ctl添加或删除所要监听的socket。以添加socket为例,如下图,如果通过epoll_ctl添加sock1、sock和sock3的监视,内核会将eventpoll添加到这三个socket的等待队列中。
      notion image
  1. 接收数据
    1. 当socket收到数据后,中断程序会给eventpoll的“就绪列表”添加socket引用。如下图展示的是sock2和sock3收到数据后,中断程序让rdlist引用这两个socket。
      notion image
      socket的数据接收并不直接影响进程,而是通过改变eventpoll的就绪列表来改变进程状态。
      假设计算机中正在运行进程A和进程B,在某时刻进程A运行到了epoll_wait语句。当程序执行到epoll_wait时,如果rdlist已经引用了socket,那么epoll_wait直接返回,如果rdlist为空,阻塞进程。如下图所示,内核会将进程A放入eventpoll的等待队列中,阻塞进程。
      notion image
      当socket接收到数据,中断程序一方面修改rdlist,另一方面唤醒eventpoll等待队列中的进程,进程A再次进入运行状态(如下图)。也因为rdlist的存在,进程A可以知道哪些socket发生了变化。
      notion image
计算机基础
  • 高性能
  • 服务器
  • LInux
  • 【MySQL】操作基础【流控和削峰】漏桶算法和令牌桶算法
    目录