一、概述

我们看到上面的TCP客户同时处理两个输入:标准输入和TCP套接字。我们遇到的问题就是在客户阻塞于(标准输入上的)fgets调用期间,服务器进程会被杀死。服务器TCP虽然正确地给客户TCP发送一个FIN,但是既然客户进程阻塞于从标准输入读入的过程,它将看不到这个ROF,知道从套接字读时为止(可能已经过了很长时间)。这样的进程需要一种预先告知内核的能力,使得内核一旦发现进程指定的一个或多个I/O条件就绪(也就是说输入已准备好被读取,或者描述符已能承接更多的输出),它就通知进程。这个能力成为I/O复用,是由select和poll这两个函数支持的。

I/O复用典型使用在下列网络应用场合:

1)当客户处理多个描述符(通常是交互式输入和网络套接字)时,必须使用I/O复用

2)一个客户同时处理多个套接字是可能的,不过比较少见。在16.5节结合一个web客户的上下文给出这种场合使用select的例子

3)如果一个TCP服务器既要处理监听套接字,又要处理已连接套接字,一般就要使用I/O复用

4)如果一个服务器既要处理TCP,又要处理UDP,一般就要使用I/O复用。8.15节有这么一个例子

5)如果一个服务器要处理多个服务或者镀铬协议(在13.5节讲述的inetd守护进程),就要用I/O复用

I/O复用并非只限于网络编程,许多重要的应用程序也需要使用这项技术

二、I/O模型

在UNIX下可用的5种I/O模型:

阻塞式I/O;

非阻塞式I/O;

I/O复用(select和poll);

信号驱动式I/O;

异步I/O

在上述所说的那样,一个输入操作通常包括两个不同的阶段:

1)等待数据准备好;

2)从内核向进程复制数据

对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所等待分组到达时,它被复制到内核总的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用进程缓冲区。

(1)阻塞时I/O模型:

最流行的I/O模型,本书到目前为止的所有例子都使用该模型。默认情形下,所有套接字都是阻塞的。

使用UDP而不是TCP为例子的原因在于就UDP而言,数据准备好读取的概念比较简单:要么整个数据报已经收到,要么还没有。对于TCP而言,诸如套接字低水位标记等额外变量开始起作用,道指这个概念复杂。

我们把recvfrom函数视为系统调用,因为我们正在区分应用进程和内核。不管如何实现,一般都会从在应用进程空间中国运行切换到在内核空间中运行,一端时间之后再切换回来。 在上图中,进程调用recvfrom,其系统调用直到数据报到达且被复制到应用进程的缓冲区中或者发送错误才返回。最常见的错误是系统调用被信号中断,我们说进程在从调用recvfrom开始到它返回的整段时间内是被阻塞的。recvfrom成功返回后,应用进程开始处理数据报。

(2)非阻塞式I/O模型:

进程把一个套接字设置成非阻塞是在通知内核:当所有请求的I/O操作非得把本进程投入睡眠才能完成时,不要把本进程投入睡眠,而是返回一个错误。将在16章中详细介绍非阻塞是I/O

前三次调用recvfrom时没有数据可返回,因此内核转而立即返回一个EWOULDBLOCK错误。第四次调用recvfrom时已有一个数据报准备好,它被复制到应用进程缓冲区,于是recvfrom成功返回。接着处理数据。

当一个应用进程像这样对一个非阻塞描述符循环调用recvfrom时,我们成为轮询,应用进程持续轮询内核,以查看某个操作是否就绪。这么做往往耗费大量CPU时间,不过这种模型偶尔也会遇到。

(3)I/O复用模型:

有了I/O复用,我们就可以调用select或者poll,阻塞在这两个系统调用中的某一个,而不是阻塞在真正的I/O系统调用上。下图展示了I/O复用模型

我们阻塞与select调用,等待数据报套接字变为可读。当select返回套接字可读这一条件时,我们调用recvfrom把所可读数据报复制到应用进程缓冲区。比较上面两图,I/O复用并不显得有什么优势,事实上由于使用select需要两个而不是单个系统调用,其优势在于可以等待多个描述符就绪

(4)信号驱动式U/O模型:

可以用信号,让内核在描述符就绪时发送SIGIO信号通知我们。称为信号驱动式I/O

我们首先开启套接字的信号驱动式I/O功能,并通过sigaction系统调用安装一个信号处理函数。该系统调用将立即返回,我们的进程继续工作,也就是说它没有被阻塞。当数据报准备好读取时,内核就为该进程产生一个SIGIO信号。我们随后既可以在信号处理函数中调用recvfrom读取数据报,并通知主循环数据已准备好待处理。也可以立即通知循环,让它读取数据报。

无论如何处理SIGIO信号,这种模型的优势在于等待数据报到达期间进程不被阻塞。主循环可以继续执行,只要等待来自信号处理函数的通知:既可以是数据已准备好被处理,也可以是数据报已准备好被读取。

(5) 异步I/O模型:

告知内核启动某个操作,并让内核在整个操作(包括将数据从内核复制到我们自己的缓冲区)完成后通知我们。这种模型与前一节介绍的信号驱动模型的主要区别在于:信号驱动I/O是由内核通知我们如何启动一个I/O操作,而异步I/O模型是由内核通知我们I/O操作何时完成。

我们调用aio_read函数,给内核传递描述符、缓冲区指针。缓冲区大小和文件偏移,并告诉内核当整个操作完成时如何通知我们。该系统调用立即返回,而且在等到I/O完成期间,我们的进程不被阻塞。

前四种模型的主要区别在于第一阶段,因为它们的第二阶段是一样的:在数据从内核复制到调用者的缓冲区期间,进程阻塞与recvfrom调用。相反,异步I/O模型在这两个阶段都要处理。

4 同步IO和异步IO

  • 同步IO操作导致请求进程阻塞,直到IO操作完成
  • 异步IO操作不导致请求进行阻塞

从理论上讲,非阻塞IO、阻塞IO、IO复用和信号驱动IO都是同步IO模型。因为这四种IO模型中,IO的读写操作,都是在IO事件发生之后,由应用进程来完成的。而POSIX规范所定义的异步IO模型则不同。对异步IO而言,用户可以直接对IO执行读写操作,这些操作告诉内核用户读写缓冲区的位置,以及IO操作完成之后内核通知应用程序的方式。异步IO的读写操作总是立即返回,而不论IO是否是阻塞的,因为真正的读写操作已经由内核接管。也就是说,同步IO模型要求用户代码自行执行IO操作(将数据从内核缓冲区读入用户缓冲区,或将数据从用户缓冲区写入内核缓冲区),而异步IO机制则由内核来执行IO操作(数据在内核缓冲区和用户缓冲区之间的移动是由内核在“后台”完成的)。你可以这样认为,同步IO向应用程序通知的是IO就绪事件,而异步IO向应用程序通知的是IO完成事件。

I/O复用 - 各种不同的IO模型的更多相关文章

  1. I/O复用——各种不同的IO模型

    一.概述 我们看到上面的TCP客户同时处理两个输入:标准输入和TCP套接字.我们遇到的问题就是在客户阻塞于(标准输入上的)fgets调用期间,服务器进程会被杀死.服务器TCP虽然正确地给客户TCP发送 ...

  2. linux基础编程:IO模型:阻塞/非阻塞/IO复用 同步/异步 Select/Epoll/AIO(转载)

      IO概念 Linux的内核将所有外部设备都可以看做一个文件来操作.那么我们对与外部设备的操作都可以看做对文件进行操作.我们对一个文件的读写,都通过调用内核提供的系统调用:内核给我们返回一个file ...

  3. 7.3 5种IO模型与IO复用

    5种IO模型分别如下: 1.阻塞IO模型 当上层应用app1调用recv系统调用时,如果对等方没有发送数据(缓冲区没有数据),上层app1将阻塞(默认行为,被linux内核阻塞). 当对等方发送了数据 ...

  4. IO模型浅析-阻塞、非阻塞、IO复用、信号驱动、异步IO、同步IO

    最近看到OVS用户态的代码,在接收内核态信息的时候,使用了Epoll多路复用机制,对其十分不解,于是从网上找了一些资料,学习了一下<UNIX网络变成卷1:套接字联网API>这本书对应的章节 ...

  5. 🍛 餐厅吃饭版理解 IO 模型:阻塞 / 非阻塞 / IO 复用 / 信号驱动 / 异步

    IO 概念 一个基本的 IO,它会涉及到两个系统对象,一个是调用这个 IO 的进程对象,另一个就是系统内核 (kernel).当一个 read 操作发生时,它会经历两个阶段: 通过 read 系统调用 ...

  6. 高性能IO模型浅析

    高性能IO模型浅析 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking  ...

  7. 简述linux同步与异步、阻塞与非阻塞概念以及五种IO模型

    1.概念剖析 相信很多从事linux后台开发工作的都接触过同步&异步.阻塞&非阻塞这样的概念,也相信都曾经产生过误解,比如认为同步就是阻塞.异步就是非阻塞,下面我们先剖析下这几个概念分 ...

  8. Linux下常见的IO模型

    前言 阻塞IO(blocking IO) 非阻塞IO(nonblocking IO) IO复用(IO multiplexing) 异步IO(asynchronous IO (the POSIX aio ...

  9. IO模型

    前言 说到IO模型,都会牵扯到同步.异步.阻塞.非阻塞这几个词.从词的表面上看,很多人都觉得很容易理解.但是细细一想,却总会发现有点摸不着头脑.自己也曾被这几个词弄的迷迷糊糊的,每次看相关资料弄明白了 ...

随机推荐

  1. 设计模式之十五:訪问者模式(Visitor Pattern)

    訪问者模式(Visitor Pattern)是GoF提出的23种设计模式中的一种,属于行为模式. 据<大话设计模式>中说算是最复杂也是最难以理解的一种模式了. 定义(源于GoF<De ...

  2. 体绘制(Volume Rendering)概述之4:光线投射算法(Ray Casting)实现流程和代码(基于CPU的实现)

    转自:http://blog.csdn.net/liu_lin_xm/article/details/4850630 摘抄“GPU Programming And Cg Language Primer ...

  3. 你使用 Web 平台安装程序命令行工具

    你使用 Web 平台安装程序命令行工具 获取的软件由其所有者授权给你.Microsoft 未授予你第三方软件的任何权利.已成功加载主源: https://go.microsoft.com/?linki ...

  4. 【python】如何去掉使用BeautifulSoup读取html出现的警告UserWarning: You provided Unicode markup but also provided a value for from_encoding

    如果我们这样读取html页面 soup= BeautifulSoup(rsp.text,'html.parser',from_encoding='utf-8')  # 粗体部分多余了 就会出现下面的警 ...

  5. 执行Socket socket = new Socket(ip, port);时抛出个异常:android.os.NetworkOnMainThreadException解决办法

    首先,确认你的android版本是4.0之后再用此方法解决,因为在4.0之后在主线程里面执行Http请求才会报这个错,也许是怕Http请求时间太长造成程序假死的情况吧.Android在4.0之前的版本 ...

  6. EventBus源码分析

      一.         EventBus简介 1.1.EventBus EventBus 是一个 Android 事件发布/订阅框架,通过解耦发布者和订阅者简化Android 事件传递,这里的事件可 ...

  7. Test Double

    我不知道Test Double翻译成中文是什么,测试替身?Test Double就像是陈龙大哥电影里的替身,起到以假乱真的作用.在单元测试时,使用Test Double减少对被测对象的依赖,使得测试更 ...

  8. Class.forName和registerDriver的区别

    我们都知道JDBC的代码怎么写,比如以MySQL JDBC为例 //注册JDBC驱动 Class.forName("com.mysql.jdbc.Driver"); //然后就可以 ...

  9. Linux下监视NVIDIA的GPU使用情况(转)

    在使用TensorFlow跑深度学习的时候,经常出现显存不足的情况,所以我们希望能够随时查看GPU时使用率.如果你是Nvidia的GPU,那么在命令行下,只需要一行命令就可以实现. 1. 显示当前GP ...

  10. NGINX原理 之 SLAB分配机制(转)

    1 引言 众所周知,操作系统使用伙伴系统管理内存,不仅会造成大量的内存碎片,同时处理效率也较低下.SLAB是一种内存管理机制,其拥有较高的处理效率,同时也有效的避免内存碎片的产生,其核心思想是预分配. ...