五种IO网络模型

在这里插入图片描述

前言

网络 IO 的本质是 socket 的读取,socket 在 linux 系统被抽象为流,IO 可以理解为对流的操作。刚才说了,对于一次 IO 访问 (以 read 举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个 read 操作发生时,它会经历两个阶段:

  • 第一阶段:等待数据准备 (Waiting for the data to be ready)。
  • 第二阶段:将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)。

网络应用需要处理的无非就是两大类问题,网络 IO,数据计算。相对于后者,网络 IO 的延迟,给应用带来的性能瓶颈大于后者。网络 IO 的模型大致有如下几种:

  • 阻塞 IO (bloking IO)
  • 非阻塞 IO (non-blocking IO)
  • 多路复用IO (multiplexing IO)
  • 信号驱动式 IO (signal-driven IO)
  • 异步 IO (asynchronous IO)

阻塞式IO模型

同步阻塞 IO 模型是最常用的一个模型,也是最简单的模型。在 Linux 中,默认情况下所有的 socket 都是 blocking。它符合人们最常见的思考逻辑。阻塞就是进程 “被” 休息,CPU 处理其它进程去了。

在这个 IO 模型中,用户空间的应用程序执行一个系统调用 (recvform),这会导致应用程序阻塞,什么也不干,直到数据准备好,并且将数据从内核复制到用户进程,最后进程再处理数据,在等待数据到处理数据的两个阶段,整个进程都被阻塞。不能处理别的网络 IO。调用应用程序处于一种不再消费 CPU 而只是简单等待响应的状态,因此从处理的角度来看,这是非常有效的。在调用 recv()/recvfrom() 函数时,发生在内核中等待数据和复制数据的过程,大致如下图:
在这里插入图片描述
当用户进程调用了 recv()/recvfrom() 这个系统调用,kernel 就开始了 IO 的第一个阶段:准备数据 (对于网络 IO 来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的 UDP 包。这个时候 kernel 就要等待足够的数据到来)。这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边,整个进程会被阻塞 (当然,是进程自己选择的阻塞)。第二个阶段:当 kernel 一直等到数据准备好了,它就会将数据从 kernel 中拷贝到用户内存,然后 kernel 返回结果,用户进程才解除 block 的状态,重新运行起来。

所以,blocking IO 的特点就是在 IO 执行的两个阶段都被 block 了

特点 在IO执行的两个阶段(等待数据和拷贝数据都被阻塞)
典型应用 阻塞Socket,java BIO
优点 进程阻塞挂起不消耗CPU资源,及时响应每个操作
缺点 需要为每个请求分配一个处理进程以及时响应,系统开销大

非阻塞I/O模型

同步非阻塞就是 “每隔一会儿瞄一眼进度条” 的轮询 (polling)方式。在这种模型中,设备是以非阻塞的形式打开的。这意味着 IO 操作不会立即完成,read 操作可能会返回一个错误代码,说明这个命令不能立即满足 (EAGAIN 或 EWOULDBLOCK)。

在网络 IO 时候,非阻塞 IO 也会进行 recvform 系统调用,检查数据是否准备好,与阻塞 IO 不一样,“非阻塞将大的整片时间的阻塞分成 N 多的小的阻塞,所以进程不断地有机会 ‘被’ CPU光顾”。

也就是说非阻塞的 recvform 系统调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个 error。进程在返回之后,可以干点别的事情,然后再发起 recvform 系统调用。重复上面的过程,循环往复的进行 recvform 系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。

在这里插入图片描述

特点 用户进程需要不断主动询问内核,数据准备好了没有
典型应用 Socket设置为NON_BLOCK
优点 实现难度低,开发应用相对阻塞I/O较难
缺点 进程轮询调用,消耗CPU资源

多路复用I/O模型

由于同步非阻塞方式需要不断主动轮询,轮询占据了很大一部分过程,轮询会消耗大量的 CPU 时间,而 “后台” 可能有多个任务在同时进行,人们就想到了循环查询多个任务的完成状态,只要有任何一个任务完成,就去处理它。如果轮询不是用户的进程,而是有人帮忙就好了。这就是所谓的 “IO 多路复用”。UNIX/Linux 下的 select、poll、epoll 就是干这个的 (epoll 比 poll、select 效率高,做的事情是一样的)。

I/O 复用模型会用到 select、poll、epoll 函数,这几个函数也会使进程阻塞。select 调用是内核级别的。

  • select 轮询相对非阻塞的轮询的区别在于:select 可以对多个 socket 端口进行监听,当其中任何一个 socket 的数据准好了,就能返回进行可读,然后进程再进行 recvform 系统调用,将数据由内核拷贝到用户进程,当然这个过程是阻塞的

  • select 相对 blocking IO 阻塞不同在于:此时的 select 不是等到 socket 数据全部到达再处理,而是有了一部分数据就会调用用户进程来处理。如何知道有一部分数据到达了呢?监视的事情交给了内核,内核负责数据到达的处理。也可以理解为"非阻塞"吧。

对于多路复用,也就是轮询多个 socket。多路复用既然可以处理多个 IO,也就带来了新的问题,多个 IO 之间的顺序变得不确定了,当然也可以针对不同的编号。具体流程,如下图所示:

在这里插入图片描述

注意:IO 多路复用是同步阻塞模型还是异步阻塞模型,在此给大家分析下:

同步是需要主动等待消息通知,而异步则是被动接收消息通知,通过回调、通知、状态等方式来被动获取消息。IO 多路复用在阻塞到 select 阶段时,用户进程是主动等待并调用 select 函数获取数据就绪状态消息,并且其进程状态为阻塞。所以,把 IO 多路复用归为同步阻塞模式。

特点 对于每个socket,一般都设置为非阻塞,但是整个用户的进程其实是一直被阻塞的,只不过进程是被select函数阻塞,而不是被Socket I/O阻塞
典型应用 Java NIO,Nginx(epoll,poll,select)
优点 专一进程解决多个进程I/O阻塞的问题,性能好,Reactor模式
缺点 实现和开发难度较大

信号驱动I/O模型

信号驱动式 I/O:首先我们允许 Socket 进行信号驱动 IO,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个 SIGIO 信号,可以在信号处理函数中调用 I/O 操作函数处理数据。过程如下图所示:

在这里插入图片描述

特点 并不符合异步I/O要求,只能算是伪异步,并且实际中并不常用

异步非阻塞 IO (asynchronous IO)

相对于同步 IO,异步 IO 不是顺序执行。用户进程进行 aio_read 系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到 socket 数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。IO 两个阶段,进程都是非阻塞的。

在这里插入图片描述

特点 真正实现了异步I/O,是五种I/O模型中唯一的异步模型
典型应用 Java7 AIO,高性能服务器应用
缺点 需要操作系统底层的支持,Linux2.5内核首现

容易混淆

  • 阻塞、非阻塞,同步、非同步的区别?
  1. 阻塞和非阻塞是指客户端等待消息处理时本身的状态,是挂起还是继续干别的。
  2. 同步和异步是指对于消息结果是客户端主动获取的,还是由服务端间接推送的。

猜你喜欢

转载自blog.csdn.net/qq_37362891/article/details/112735994