常见的网络 I/O 模型

基础概念

同步、异步、阻塞、非阻塞

    通俗来说,同步就是两件事由同一线程顺序执行(做完一件事接着再干另一件事),异步是这个线程只干第一件事,直接返回,第二件事由别人(另一线程)来做,如果第一个线程想要根据第二个线程来做那件事的结果来做点什么,可以添加回调,由第二个线程做完第二件事后来执行或者交给线程池执行。

       而阻塞与否就是在等一个资源的时候是决定继续等还是不等了直接返回,继续等就是阻塞,不等了直接返回就是非阻塞

同步阻塞

应用程序执行系统调用,应用程序会一直阻塞,直到系统调用完成。应用程序处于不再消费CPU而只是简单等待响应的状态。当响应返回时,应用程序将数据移动到用户空间的缓冲区(注意哦,这里是应用程序而不是内核程序),继续向下执行。

 

 同步阻塞I/O模型.

同步非阻塞

设备以非阻塞形式打开,I/O操作不会立即完成,read操作可能会返回一个错误代码。应用程序可以执行其他操作,但需要请求多次I/O操作,直到数据可用。

 

同步非阻塞形式实际上是效率低下的,因为:

  • 应用程序需要在不同的任务之间切换。异步非阻塞是你只需要执行当前任务,系统调用会主动通知你,不用频繁切换。
  • 数据在内核中变为可用到调用read返回数据之间存在时间间隔,会造成整体数据吞吐量降低

异步非阻塞

应用程序的其他处理任务与I/O任务重叠进行。读请求会立即返回,说明请求已经成功发起,应用程序不被阻塞,继续执行其它处理操作。当read响应到达,将数据拷贝到用户空间,产生信号或者执行一个基于线程回调函数完成I/O处理。应用程序不用在多个任务之间切换。

 

非阻塞I/O和异步I/O区别在于,在非阻塞I/O中,虽然进程大部分时间不会被block,但是需要不停的去主动check,并且当数据准备完成以后,也需要应用程序主动调用recvfrom将数据拷贝到用户空间;异步I/O则不同,就像是应用程序将整个I/O操作交给了内核完成,然后由内核发信号通知。期间应用程序不需要主动去检查I/O操作状态,也不需要主动从内核空间拷贝数据到用户空间。

非阻塞I/O看起来是non-blocking的,但是只是在内核数据没准备好时,当数据准备完成,recvfrom需要从内核空间拷贝到用户空间,这个时候其实是被block住的。而异步I/O是当进程发起I/O操作后,再不用主动去请求,知道内核数据准备好并发出信号通知,整个过程完全没有block。


文件描述符(基础概念补充)

文件描述符用于表示指向文件引用的抽象画概念。在形式上是一个非负整数,实际上是一个索引值,指向内核为每一个进程维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回个文件描述符。

Linux 系统中,包括文件、设备在内的许多事物的操作都被当成文件来操作,当 Linux 系统对文件操作时都会调用操作系统的系统调用,然后该系统调用返回一个文件描述符。在 Linux 系统中对网络 I/O 的操作同样也会返回相应的 Socket 文件描述符。

Linux 操作系统总共有 5 种网络 I/O 模型

l  阻塞I/O

l  非阻塞I/O

l  I/O复用(select、poll、linux 2.6种改进的epoll)

l  信号驱动IO(SIGIO)

l  异步I/O(POSIX的aio_系列函数)

阻塞 I/O 模型

阻塞 I/O 模型是在操作系统发起系统调用调用之后,要等到操作系统系统内核所有的 I/O 操作完成才返回。阻塞 I/O 模型的内核态调用过程如下:首先操作系统内核调用 recvfrom()方法,调用之后进程进入阻塞状态,等待数据包达到。如果数据包到达或者在执行过程中出现 I/O 等方面的错误时才会调用完成,代码返回。阻塞 I/O 模型的特征主要是:当调用者使用阻塞 I/O 系统调用时,在 I/O 操作在内核态完成所有操作前,调用者会一直在这个点等待等待,处于阻塞状态;只有在操作系统内核完成了相应的操作之后函数才返回,调用者才能继续执行下面的代码。

 

非阻塞 I/O 模型是:进程使用非阻塞 I/O 系统调用时,如果系统由于繁忙等原因不能立即返回相应操作的结果,则该 I/O 函数会置相应的错误号并且立即返回,而不是和阻塞 I/O 操作一样,等待数据到来。非阻塞 I/O 模型的内核态调用过程如下:当调用 recvfrom()方法时,内核马上给该系统调用返回错误码。当再次调用recvfrom()方法时,如果操作系统的数据已经就绪,则会将数据复制到缓存区等待读取,同时 recvfrom()方法返回成功。如果操作系统的数据没有准备好,则继续返回错误码

I/O 复用模型

 I/O 复用模型中,系统会首先构造一张有关文件或者 Socket 描述符的列表,然后调用一个特定的函数,当至少有一个描述符准备好进行 I/O 操作时,函数才会返回结果。此时进程就能够获取到可进行 I/O 操作的描述符集合。Linux 提供了select()/poll()接口来执行多路复用的功能

然而,select/poll 依次扫描文件描述符,依次判断文件描述符是否就绪。但是由于 select/poll 所能使用的文件描述符数量有限,因此它在实际使用过程中会有些限制。为了解决 select/poll 顺序扫描效率低下的问题,Linux 系统还有一种基于事件驱动方式的系统调用 epoll。由于 epoll 根据事件来查询文件描述符,因此性能会高很多。当有文件描述符的状态就绪时,模型马上执行之前传入的回调函数。

多路复用的本质是同步非阻塞I/O,多路复用的优势并不是单个连接处理的更快,而是在于能处理更多的连接。

I/O编程过程中,需要同时处理多个客户端接入请求时,可以利用多线程或者I/O多路复用技术进行处理。
I/O多路复用技术通过把多个I/O的阻塞复用到同一个select阻塞上,一个进程监视多个描述符,一旦某个描述符就位, 能够通知程序进行读写操作。因为多路复用本质上是同步I/O,都需要应用程序在读写事件就绪后自己负责读写。
最大的优势是系统开销小,不需要创建和维护额外线程或进程。

  • 应用场景
    • 服务器需要同时处理多个处于监听状态或者多个连接状态的套接字
    • 需要同时处理多种网络协议的套接字
    • 一个服务器处理多个服务或协议

目前支持多路复用的系统调用有select, poll, epoll。

 

信号驱动 I/O 模型

在信号驱动的 I/O 模型中,实现了真正意义上的异步形式的通知,通过信号机制来获取描述符的状态信息。首先在等待的描述符上注册回调函数,当事件发生后,回调函数负责将描述符状态写入用户空间并通知相关进程,对于某个描述符,

发生了所关心的事件。之后就可以在处理程序中调用 recvfrom()方法来读数据

异步 I/O

这种模型是真正意义上的异步。应用进程发起一个系统调用后,会马上返回,可以执行其他的操作。 但该调用会让内核触发一个操作,完成数据从内核拷贝到用户自己的缓冲区,操作完成后,内核会通知进程,然后进程对放在缓冲区的数据再进行处理。如图

 

同步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完成事件。

资料来源:https://www.cnblogs.com/wuchanming/p/4442146.html作者:Jessica程序猿
链接:https://www.jianshu.com/p/439e8b349f48

作者:rainybowe
來源:简书
https://blog.csdn.net/moakun/article/details/81042877作者:茅坤宝骏氹

论文:基于Netty的高可服务消息中间件的研究与实现_崔晓旻

          基于Netty的消息中间件的研究与实现_夏斐

原文地址:https://www.cnblogs.com/cai-cai777/p/10222095.html