五种 I/O 模型

先花费点时间了解这几种 I/O 模型,有助于后面的理解。

阻塞 I/O 与非阻塞 I/O

阻塞和非阻塞的概念能应用于所有的文件描述符,而不仅仅是 socket。我们称阻塞的文件描述符为阻塞 I/O,称非阻塞的文件描述符为非阻塞 I/O。

socket 在创建的时候默认是阻塞的,我们可以给 socket 系统调用的第 2 个参数传递 SOCK_NONBLOCK 标志,或者通过 fcntl 系统调用的 F_SETFL 命令将其设置为非阻塞的。

  • 针对阻塞 I/O 执行的系统调用可能因为无法立即完成而被操作系统挂起,直到等待的事件发生为止。可能被阻塞的系统调用为 acceptsendrecvconnect

  • 针对非阻塞 I/O 执行的系统调用则总是立即返回,而不管事件是否已经发生。如果事件没有立即发生,这些系统调用就返回 -1,和出错的情况一样。此时我们必须根据 errno 来区分这两种情况。

    • acceptsendrecv 而言,事件未发生时 errno 通常被设置为 EAGAIN(意为“再来一次”)或者 EWOUDBLOCK(意为“期望阻塞”);

    • connect 而言,errno 则被设置成 EINPROGRESS(意为“在处理中”)。

很显然,只有在事件已经发生的情况下操作非阻塞 I/O(读、写等),才能提高程序的效率。因此,非阻塞 I/O 通常要和其他 I/O 通知机制一起使用,比如 I/O 复用和 SIGIO 信号。

笔者认为,我们使用非阻塞 I/O 的最佳情况是:【当我们进行系统调用的时候,它所需要的事件已经发生了】,这样系统调用就不会被阻塞,直接进行处理。比如 accept 函数,I/O 复用的好处就是当我们调用 accept 函数的时候,已经有客户端在请求连接,这样直接调用 accept,提高运行效率。

I/O 复用

I/O 复用是一种 I/O 通知机制,而且是最常用的通知机制。

I/O 复用是指应用程序通过 I/O 复用函数(select、poll、epoll_wait)向内核注册一组事件,内核通过 I/O 复用函数把其中就绪的事件通知给应用程序。

需要注意的是 I/O 复用函数本身是阻塞的,它们能提高程序效率的原因在于它们具有同时监听多个 I/O 事件的能力。

信号驱动 I/O

为一个目标文件描述符指定宿主进程,那么被指定的宿主进程将捕获到 SIGIO 信号。这样,当文件描述符上有事件发生时,SIGIO 信号的信号处理函数将被触发,我们也就可以在该信号处理函数中对目标文件描述符执行非阻塞 I/O 操作了。

异步 I/O

理论上讲,阻塞 I/O、非阻塞 I/O、信号驱动 I/O 和 I/O 复用都是同步 I/O

  • 同步I/O:内核向应用程序通知的是就绪事件,比如只通知有客户端连接,要求用户代码自动执行I/O操作(将数据从内核缓冲区读入用户缓冲区,或将数据从用户缓冲区写入内核缓冲区);
  • 异步I/O:内核向应用程序通知的是完成事件,比如读取客户端的数据之后才通知应用程序,由内核完成I/O操作(数据在内核缓冲区和用户缓冲区之间的移动是由内核在“后台”完成的)。

对异步 I/O 而言,用户可以直接对 I/O 执行读写操作,这些操作告诉内核用户读写缓冲区的位置,以及 I/O 操作完成之后内核通知应用程序的方式。异步 I/O 的读写操作总是立即返回,不论 I/O 是否是阻塞的,因为真正的读写操作已经由内核接管。

两种高效的事件处理模式

Reactor 模式

Reactor 模式要求主线程(I/O 处理单元)只负责监听文件描述符上是否有事件发生。有的话立即通知工作线程(逻辑单元),读写数据、接受新的连接及处理客户请求均在工作线程中完成。通常由同步I/O实现。

工作流程:

  • 主线程向 epoll 内核事件表中注册 socket 上的读就绪事件;

    告诉 socket 的对方(客户端):我这边准备好读数据啦,你可以发数据啦!

  • 主线程调用 epoll_wait() 等待 socket 上有数据可读;

  • 当 socket 上有数据可读时,epoll_wait() 通知主线程,主线程将 socket 可读事件插入请求队列;

    主线程:干活啦干活啦,这有个活,你们看看谁干了它!

  • 睡眠在请求队列上的某个工作线程被唤醒,它从 socket 上读取数据,并处理客户请求,然后往 epoll 内核事件表中注册该 socket 上的写就绪事件;

    某一个苦工(工作线程)干完活之后告诉 socket 的对方:我准备好写了!

  • 主线程调用 epoll_wait() 等待 socket 可写;

  • 当 socket 可写时,epoll_wait() 通知主线程,主线程将 socket 可写事件放入请求队列;

    主线程:又来活啦,你们看看谁来干!

  • 睡眠在请求队列上的某个工作线程被唤醒,它往 socket 上写入服务器处理客户请求的结果。

    某个苦工又被唤醒来干活,写入处理结果。

Reacto 模式类似于老板(主线程)与苦工(工作线程)之间的关系,有活了老板就派给苦工来干(哭了.....莫名被 cue 到)

Proactor 模式

Proactor 模式将所有的 I/O 操作都交给主线程和内核来处理,工作线程仅仅负责业务逻辑。通常由异步 I/O (aio_read()aid_write())实现。

工作流程

  • 主线程调用 aio_read() 向内核注册 socket 上的读完成事件,并告诉内核 用户读缓冲区的位置,以及读操作完成时如何通知应用程序(这里以信号为例);

    主线程告诉内核:用户这边准备好收货了,你直接把货卸在这!你卸完了直接给用户打电话!

  • 主线程继续处理其他逻辑;

    溜了溜了,我先干点别的...

  • 当 socket 上的数据被读入用户缓冲区后,内核将向应用程序发送一个信号,以通知应用程序数据已经可用;

    苦逼的内核干完活,给用户打了电话通知他活干完了...

  • 应用程序预先定义好的信号处理函数选择一个工作线程来处理客户请求。工作线程处理完客户请求之后,调用 aio_write() 函数向内核注册 socket 上的写完成事件,并告诉内核 用户写缓冲区的位置,以及写操作完成时如何通知应用程序(仍然以信号为例);

    用户这边有很多苦工(工作线程),预先指定好了一个苦工来对接这批货物。这个苦工加工完所有的货物,告诉内核我这边货加工好了,放在老地方了,你直接过来拿!

  • 主线程继续处理其他逻辑;

    没我什么事,继续摸鱼(bushi

  • 当用户缓冲区的数据被写入 socket 之后,内核将向应用程序发送一个信号,以通知应用程序数据已经发送完毕;

    然后,内核就来拉货了,拉完货之后又给用户打电话:货我全拉走了!

  • 应用程序预先定义好的信号处理函数选择一个工作线程来做善后处理,比如决定是否关闭 socket。

    苦工收到消息,来看看是否需要清理场地...

可以看到,Proactor 模式相当于找了个快递员(内核)来帮助运输货物(读写数据),工作线程只需要处理业务逻辑,主线程只需要监听连接事件,读写事件由内核和工作线程直接通信。

模拟 Proactor 模式

由于 Proactor 模式需要异步 I/O 来实现,这里提出使用同步 I/O 的方式模拟出 Proactor 模式的一种方法。其原理是:主线程执行数据读写操作,读写完成后,主线程向工作线程通知这一“完成事件”。那么从工作线程的角度来看,它们就直接获得了数据读写的结果,接下来要做的只是对读写的结果进行逻辑处理。

工作流程

  • 主线程往 epoll 内核事件表中注册 socket 上的读就绪事件;
  • 主线程调用 epoll_wait() 等待 socket 上有数据可读;
  • 当 socket 上有数据可读时,epoll_wait() 通知主线程。主线程从 socket 循环读取数据,直到没有更多数据可读,然后将读到的数据封装成一个请求对象并插入请求队列;
  • 睡眠在请求队列上的某个工作线程被唤醒,它获得请求对象并处理客户请求,然后往 epoll 内核事件表中注册 socket 上的写就绪事件;
  • 主线程调用 epoll_wait() 等待 socket 可写;
  • 当 socket 可写时,epoll_wait() 通知主线程,主线程往 socket 上写入服务器处理客户请求的结果。

可以看到,模拟 Proactor 模式其实就是主线程自己来充当快递员(内核)的角色,所以在工作线程的角度来看与 Proactor 模式差不多。

本文参考自游双大神的《Linux 高性能服务器编程》一书。

【Linux 网络编程】生动讲解 Reactor 模式与 Proactor 模式的更多相关文章

  1. Linux网络编程学习(十一) ----- 五种I/O模式(第六章)

    1.五种I/O模式是哪几个? 阻塞I/O,非阻塞I/O,I/O多路复用,信号驱动I/O(SIGIO),异步I/O 一般来讲程序进行输入操作有两个步骤,一是等待有数据可读,二是将数据从系统内核中拷贝到程 ...

  2. Linux 网络编程的5种IO模型:多路复用(select/poll/epoll)

    Linux 网络编程的5种IO模型:多路复用(select/poll/epoll) 背景 我们在上一讲 Linux 网络编程的5种IO模型:阻塞IO与非阻塞IO中,对于其中的 阻塞/非阻塞IO 进行了 ...

  3. Linux 网络编程的5种IO模型:信号驱动IO模型

    Linux 网络编程的5种IO模型:信号驱动IO模型 背景 上一讲 Linux 网络编程的5种IO模型:多路复用(select/poll/epoll) 我们讲解了多路复用等方面的知识,以及有关例程. ...

  4. Linux 网络编程的5种IO模型:异步IO模型

    Linux 网络编程的5种IO模型:异步IO模型 资料已经整理好,但是还有未竟之业:复习多路复用epoll 阅读例程, 异步IO 函数实现 背景 上一讲< Linux 网络编程的5种IO模型:信 ...

  5. Linux 网络编程(IO模型)

    针对linux 操作系统的5类IO模型,阻塞式.非阻塞式.多路复用.信号驱动和异步IO进行整理,参考<linux网络编程>及相关网络资料. 阻塞模式 在socket编程(如下图)中调用如下 ...

  6. linux网络编程-(socket套接字编程UDP传输)

    今天我们来介绍一下在linux网络环境下使用socket套接字实现两个进程下文件的上传,下载,和退出操作! 在socket套接字编程中,我们当然可以基于TCP的传输协议来进行传输,但是在文件的传输中, ...

  7. Linux网络编程&内核学习

    c语言: 基础篇 1.<写给大家看的C语言书(第2版)> 原书名: Absolute Beginner's Guide to C (2nd Edition) 原出版社: Sams 作者: ...

  8. linux网络编程基础--(转自网络)

    转自 http://www.cnblogs.com/MyLove-Summer/p/5215287.html Linux下的网络编程指的是socket套接字编程,入门比较简单. 1. socket套接 ...

  9. 很全的linux网络编程技巧

    本文转载自:http://www.cnblogs.com/jfyl1573/p/6476607.html 1. LINUX网络编程基础知识 1 1.1. TCP/IP协议概述 1 1.2. OSI参考 ...

随机推荐

  1. Java 中 IO 流分为几种?

    按功能来分:输入流(input).输出流(output).按类型来分:字节流和字符流.字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数 ...

  2. Maria DB数据库基础知识

    Maria DB连接 与MariaDB建立连接的一种方法是在命令提示符下使用mysql二进制文件. Maria DB命令行登录数据库服务: mysql -u root -p -- 换行输入密码 上面给 ...

  3. 面试问题之C++语言:多态

    什么是多态? 概念:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性.简单的说,就是用基类的引用指向子类的对象. 为什么要用多态呢? 原因:封装可以隐藏实现细节,使得代码模 ...

  4. 如果在拦截请求中,我想拦截get方式提交的方法,怎么配置?

    可以在@RequestMapping注解里面加上method=RequestMethod.GET.

  5. Pandas基础笔记

    Basic knowledge of Pandas pandas库是以numpy库为基础建成的,是python数据分析的核心库.也正因如此,pandas内的数据结构与numpy的数组有许多相似的地方. ...

  6. C语言之关键字(知识点2)

    关键字又叫保留字,这些关键字不可以再次定义 解析

  7. Vue的computed(计算属性)使用实例之TodoList

    最近倒腾了一会vue,有点迷惑其中methods与computed这两个属性的区别,所以试着写了TodoList这个demo,(好土掩面逃~); 1. methods methods类似react中组 ...

  8. React系列——websocket群聊系统在react的实现

    前奏 这篇文章仅对不熟悉在react中使用socket.io的人.以及websocket入门者有帮助. 下面这个动态图展示的聊天系统是用react+express+websocket搭建的,很模糊吧, ...

  9. 两个echarts地图联动高亮区域

    项目要求左右两张地图能够在鼠标悬浮的时候高亮部分联动,曾尝试了connect不好使,所以自己写了这段代码.代码思路为: 鼠标移入地图时,另一侧的地图根据鼠标悬浮获取到的区域name使该区域高亮: 鼠标 ...

  10. TensorFlow使用GPU训练时CPU占用率100%而GPU占用率很低

    在训练keras时,发现不使用GPU进行计算,而是采用CPU进行计算,导致计算速度很慢. 用如下代码可检测tensorflow的能使用设备情况: from tensorflow.python.clie ...