# 【NO.442】后端开发—一文详解网络IO模型

### 1.网络IO：

网络IO会涉及到两个系统对象 一个是 用户空间 调用 IO 的进程或者线程，另一个是 内核

空间的 内核系统， 比如 发生 IO 操作 read 时，它会经历两个阶段

1. 等待数据准备就绪

2. 将数据 从内核拷贝到进程 或者线程 中。

### 2.服务器模型 Reactor 与 Proactor

　　 对高并发编程网络连接上的消息处理，可以分为两个阶段：等待消息准备好、消息 处理。当使用默认的阻塞套接字时（例如上面提到的 1 个线程捆绑处理 1 个连接），往往是把这两个阶段合而为一，这样操作套接字的代码所在的线程就得睡眠来等待消息准备好，这导致了高并发下线程会频繁的睡眠、唤醒，从而影响了 CPU 的使用效率。高并发编程方法当然就是把两个阶段分开处理。即，等待消息准备好的代码段，与处理消息的代码段是分离的。当然，这也要求套接字必须是非阻塞的，否则，处理消息的代码段很容易导致条件不满足时，所在线程又进入了睡眠等待阶段。那 么问题来了，等待消息准备好这个阶段怎么实现？它毕竟还是等待，这意味着线程还是要睡眠的！解决办法就是，线程主动查询，或者让 1 个线程为所有连接而等待！这就是 IO 多路复用了。多路复用就是处理　　等待消息准备好这件事的，但它可以同时处理多个连接！它也可能 等待 ””，所以它也会导致线程睡眠，然而这不要紧，因为它一对多、它可以监控所有连接。这样，当我们的线程被唤醒执行时，就一定是有一些连接准备好被我们的代码执行了。

作为一个高性能服务器程序通常需要考虑处理三类事件： I/O 事件，定时事件及信号。两种 高效 的事件处理模型： Reactor 和 Proactor。

### 3.Reactor模型

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/13/20221213160712_29194.jpg)

Reactor 释义 反应堆 ””，是一种事件驱动机制。和普通函数调用的不同之处在于：应用程序不是主动的调用某个 API 完成处理，而是恰恰相反， Reactor 逆置了事件处理流程，应用程序需要提供相应的接口并注册到 Reactor 上，如果相应的时间发生， Reactor 将主动调用应用程序注册的接口，这些接口又称为 回调函数 。

Reactor 模式是处理并发I/O 比较常见的一种模式，用于同步I/O，中心思想是将所有要处理的I/O 事件注册到一个中心I/O 多路复用器上，同时主线程/进程阻塞在多路复用器上；一旦有I/O 事件到来或是准备就绪(文件描述符或socket 可读、写)，多路复用器返回并将事先注册的相应I/O 事件分发到对应的处理器中。

### 4.Reactor 模型有三个重要的组件：

多路复用器：由操作系统提供，在linux 上一般是select, poll, epoll 等系统调用。

事件分发器：将多路复用器中返回的就绪事件分到对应的处理函数中。

事件处理器：负责处理特定事件的处理函数。

### 5.具体流程如下：

注册读就绪事件和相应的事件处理器；

事件分离器等待事件；

事件到来，激活分离器，分离器调用事件对应的处理器；

事件处理器完成实际的读操作，处理读到的数据，注册新的事件，然后返还控制权；

### 6.Reactor 模式是编写高性能网络服务器的必备技术之一，它具有如下的优点：

响应快；编程相对简单，可以最大程度的避免复杂的多线程及同步问题，并且 避免了多线程进程的切换开销可扩展性，可以方便的通过增加 Reactor 实例个数来充分利用 CPU 资源；可复用性， reactor 框架本身与具体事件处理逻辑无关，具有很高的复用性；

### 7.实际Reactor 模型可能是这样：

![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/13/20221213160712_74383.jpg)

### 8.Proactor模型

具体流程如下：

处理器发起异步操作，并关注I/O 完成事件

事件分离器等待操作完成事件

分离器等待过程中，内核并行执行实际的I/O 操作，并将结果数据存入用户自定义缓冲区，最后通知事件分离器读操作完成I/O 完成后，通过事件分离器呼唤处理器事件处理器处理用户自定义缓冲区中的数据

　　我们可以发现proactor 模型最大的特点就是Proactor 最大的特点是使用异步I/O。所有的I/O 操作都交由系统提供的异步I/O 接口去执行。工作线程仅仅负责业务逻辑。在Proactor 中，用户函数启动一个异步的文件操作。同时将这个操作注册到多路复用器上。多路复用器并不关心文件是否可读或可写而是关心这个异步读操作是否完成。异步操作是操作系统完成，用户程序不需要关心。多路复用器等待直到有完成通知到来。当操作系统完成了读文件操作——将读到的数据复制到了用户先前提供的缓冲区之后，通知多路作系统完成了读文件操作——将读到的数据复制到了用户先前提供的缓冲区之后，通知多路复用器相关操作已完成。多路复用器再调用相应的处理程序，处理数据。

　　　　Proactor 增加了编程的复杂度，但给工作线程带来了更高的效率。Proactor 可以在系统态将读写优化，利用I/O 并行能力，提供一个高性能单线程模型。在windows 上，由于没有epoll 这样的机制，因此提供了IOCP 来支持高并发， 由于操作系统做了较好的优化，windows 较常采用Proactor 的模型利用完成端口来实现服务器。在linux 上，在2.6 内核出现了aio 接口，但aio 实际效果并不理想，它的出现，主要是解决poll 性能不佳的问题，但实际上经过测试，epoll 的性能高于poll+aio，并且aio 不能处理accept，因此linux 主要还是以Reactor 模型为主。

### 9.Libevent libev libuv

libevent

名气最大，应用最广泛，历史悠久的跨平台事件库；

libev :

较较libevent而言，设计更简练，性能更好，但对而言，设计更简练，性能更好，但对Windows支持不够好

libuv :

开发nodenode的过程中需要一个跨平台的事件库，他们首选了的过程中需要一个跨平台的事件库，他们首选了libev，但又要支持Windows，故重新封装了一套，linux下用libev实现，Windows下用IOCP实现；

### 10.总结:

　　掌握了Reactor原理，为以后学习其他网络框架底层代码，打下了基础。

原文链接：https://zhuanlan.zhihu.com/p/489567205

作者：[Hu先生的Linux](https://www.zhihu.com/people/huhu520-10)