Reactor 和 Proactor 模式
Posted WoLannnnn
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Reactor 和 Proactor 模式相关的知识,希望对你有一定的参考价值。
文章目录
同步 IO 模型通常用来实现 Reactor 模式,异步 IO 模型通常用来实现 Proactor 模型
服务器框架
首先我们了解一下服务器的基本框架:
I/O 处理单元是服务器管理客户连接的模块。它通常完成以下工作:等待并接受新的客户连接,接受客户数据,将服务器响应的数据返回给客户端。但数据的收发也不一定是由 I/O 处理单元完成的,也有可能是逻辑单元完成的,这取决于事件处理模式(Reactor 还是 Proactor)。对于一个服务器机群来说,I/O 处理单元是一个专门的接入服务器。它实现负载均衡,从所有的逻辑服务器中选取负荷最小的一台来为新客户服务。
一个逻辑单元通常是一个进程或线程。它负责分析并处理客户数据,并将结果传递给 I/O 处理单元或直接给客户端(取决于事件处理模式)。对服务器机群而言,一个逻辑单元本身就是一台逻辑服务器。服务器通常拥有多个逻辑单元, 以实现对多个客户的并行处理。
网络存储单元可以是数据库、缓存和文件,甚至是一台独立的服务器。但它不是必须的,比如 ssh、telnet 等登录服务就不需要这个单元。
请求队列是各个单元之间的通信方式的抽象。I/O 处理单元接收到客户请求时,需要以某种方式通知一个逻辑单元来处理该请求。同样,多个逻辑单元同时访问一个存储单元时,也需要采用某种机制来协调处理静态条件。对于服务器机群而言,请求队列是各台服务器之间预先建立的、静态的、永久的 TCP 连接。这种 TCP 连接能提高服务器之间交换数据的效率,避免了每次重复建立 TCP 连接的额外开销。
Reactor模式
Reactor 模式要求主线程只负责监听文件描述符上是否有事件发生,有的话就立即将该事件通知工作线程。除此之外,主线程不做任何其他的工作。连接客户端,读取客户端数据,发送数据给客户端都是由工作线程完成的。
使用同步 IO 模型(以 epoll 为例)实现的 Reactor 模式的工作流程:
- 主线程往 epoll 事件表上注册 socket 上的读事件
- 主线程调用epoll_wait 等待 socket 的读就绪事件
- 当 socket 上有数据可读时,epoll_wait 通知主线程,主线程将 socket 可读事件放入请求队列
- 睡眠在请求队列上的工作线程被唤醒,它从 socket 上读取数据,并处理客户请求,然后往 epoll 事件表上注册 socket 的写就绪事件
- 主线程调用 epoll_wait 等待 socket 的写就绪事件
- 当 socket 上有数据可写时,epoll_wait 通知主线程,主线程将 socket 可写事件放入请求队列
- 睡眠在请求队列上的工作线程被唤醒,它往 socket 上写入服务器处理客户请求的结果
Reactor 工作流程的总结:
图中,工作线程从请求队列中取出事件后,将根据事件的类型来决定如何处理它:如果是读事件,则读取并处理数据;如果是写事件,则执行写操作。
Proactor 模式
与 Reactor 模式不同,所有的 I/O 操作都交给主线程和内核处理,工作线程仅仅负责业务逻辑。因此 Proactor 更符合我们最开始描述的服务器框架。
使用异步 I/O 模型(以 aio_read 和 aio_write 为例)实现的 Proactor 模式的工作流程是:
- 主线程调用 aio_read 向内核注册 socket 上的读完成事件,并告诉内核用户读缓冲区的位置,以及读操作完成时如何通知应用程序(这里以信号为例)
- 主线程继续处理其他逻辑
- 当 socket 上的数据被读入用户缓冲区后,内核向应用程序发送一个信号,通知应用程序数据已可用
- 应用程序预先定义好的信号处理函数选择一个工作线程来处理客户请求。工作线程处理完客户请求后,调用 aio_write 函数向内核注册 socket 上的写完成事件,并告诉内核写缓存区的位置,以及完成写操作时该如何通知应用程序(仍然以信号为例)
- 主线程继续处理其他逻辑
- 当用户缓冲区的数据被写入 socket 后,内核向应用程序发送一个信号,以通知应用程序数据已经发送完毕
- 应用程序预先定义好的信号处理函数选择一个工作线程来做善后处理,比如是否关闭 socket
下图总结了 Proactor 模式的工作流程:
连接 socket 上的读写事件是通过 aio_read/aio_write 向内核注册的,因此内核将通过信号来向应用程序报告连接 socket 上的读写事件。所以,主线程中的 epoll_wait 调用仅能用来检测监听 socket 上的连接请求事件,而不能用来检测连接 socket 上的读写事件。
模拟 Proactor 模式
我们使用同步 I/O 方式模拟出 Proactor 模式。工作原理:主线程完成读写事件,读写完成后通知工作线程,工作线程负责逻辑处理。
使用同步 I/O 模型(以 epoll 为例) 模拟出的 Proactor 模式的工作流程如下:
- 主线程往 epoll 事件表中注册 socket 的读就绪事件
- 主线程调用 epoll_wait 等待 socket 上有数据可读
- 当 socket 上有数据可读时,epoll_wait 通知主线程。主线程读取数据,将读取好的数据封装成一个请求对象放入请求队列
- 睡眠在请求队列上的某个工作线程被唤醒,它获得请求对象并处理客户请求,然后往 epoll 事件表中注册 socket 的写事件
- 主线程调用 epoll_wait 等待 socket 上有数据可写
- 当 socket 上有数据可写时,epoll_wait 通知主线程。主线程往 socket 上写入处理结果
下图总结了同步 I/O 模型模拟出的 Proactor 模式的工作流程:
以上是关于Reactor 和 Proactor 模式的主要内容,如果未能解决你的问题,请参考以下文章