服务器编程框架的两种高效的事件处理模式——Reactor模式 + Proactor模式

Reactor模式 + Proactor模式

1、背景(两句话把握基本思想)

  • reactor:老板,饭做好了你跟俺说一声,俺自己来拿就行。
  • proactor: 老板,给俺准备最多二两饭,准备好了跟俺送到桌上。

2、Reactor模式

2.1、事件处理流程

描述:重点理解

  • 它要求主线程只负责监听文件描述上是否有事件发生有的话就立即将该事件通知工作线程。除此之外,主线程不做任何其他实质性的工作。
  • 工作线程负责读写数据,接受新的连接,以及处理客户请求。

事件处理流程
在这里插入图片描述

  • 1、主线程往epoll内核事件表中注册socket上的读就绪事件。

  • 2、主线程调用epoll_wait等待socket上有数据可读。

  • 3、当socket上有数据可读时,epoll_wait通知主线程,主线程则将socket可读事件放入请求队列。(老板做好了饭,通知一声了

  • 4、睡眠在请求队列上的某个工作线程被唤醒,它从socket读取数据,并处理客户请求,然后往epoll内核事件表中注册该socket上的写就绪事件。(顾客自己来拿饭了

  • 5、主线程调用epoll_wait等待socket可写。

  • 6、当socket可写时,epoll_wait通知主线程,主线程将socket可写事件放入请求队列。

  • 7、睡眠在请求队列上的某个工作线程被唤醒,它往socket上写入服务器处理客户请求的结果。

注意:
工作线程从请求队列读取事件以后,将根据事件的类型决定如何处理它:

  • 1、对于可读事件,执行读数据和逻辑处理请求的操作(例如解析HTTP请求);
  • 2、对于可写操作,执行写数据操作(例如将HTTP响应报文写入写缓冲区);
    但是线程只是接受不同事件的返回信息进行逻辑业务操作,并不是又区分读工作线程和写工作线程

2.2、Reactor模型发展

单线程Reactor
在这里插入图片描述
在这里插入图片描述
Reactor:负责响应IO事件,当检测到一个新的事件,将其发送给相应的Handler去处理。

Handler:负责处理非阻塞的行为,标识系统管理的资源;同时将handler与事件绑定。

Reactor为单个线程,需要处理accept连接,同时发送请求到处理器中。

由于只有单个线程,所以处理器中的业务需要能够快速处理完。不过很容易发现,单线程忙不过来呀,处理的并发连接也不够。

多线程Reactor
在这里插入图片描述
将处理器的执行放入线程池,多线程进行业务处理。但Reactor仍为单个线程。

Using Multiple Reactors
继续改进:对于多个CPU的机器,为充分利用系统资源,将Reactor拆分为两部分。
在这里插入图片描述

3、Proactor模式

3.1、事件处理流程

描述:重点理解

将所有I/O操作都交给主线程和内核来处理,工作线程仅仅负责业务逻辑。更符合之前提到的服务器编程框架。

事件处理流程
在这里插入图片描述

使用异步I/O模型(以aio_read和aio_write为例)实现Proactor模式的工作流程是:

  • 1、主线程调用aio_read函数向内核注册socket上的读写完成事件,并告诉内核用户读缓冲区的位置,以及读操作完成后如何通知应用程序。
  • 2、主线程继续处理其他逻辑。
  • 3、当socket上的数据被读入用户缓冲区后,内核将向应用程序发送一个 信号,以通知应用程序数据可用。 (老板做好了饭,端来了
  • 4、应用程序预先定义好的信号处理函数选择一个工作线程来处理客户请求。工作线程处理完客户请求之后,调用aio_write函数想内核注册socket的写完成事件,并告诉内核用户写缓冲区的位置,以及写操作完成时如何通知应用程序。
  • 5、主线程继续处理其他逻辑。 6、当用户缓冲区的数据被写入socket之后,内核将向应用程序发送一个信号,以通知应用程序数据已经发送完毕。
  • 7、应用程序预先定义好的信号处理函数选择一个工作线程来做善后处理,比如决定是否关闭socket。
    在这里插入图片描述

3.2、同步I/O方式模拟Proactor模式

原理:

主线程执行数据读写操作,读写完成之后,主线程向工作线程通知这一“完成事件”,工作线程处理后续逻辑。

从工作线程角度出发,他们就直接获得了数据读写的结果,接下来要做的只是对读写结果进行逻辑业务的处理(例如解析HTTP请求,写HTTP响应报文)

流程:

  • 1、主线程往epoll内核事件表中注册socket上的读就绪事件。
  • 2、主线程调用epoll_wait等待socket上有数据可读。
  • 3、当socket上有数据可读时,epoll_wait通知主线程。主线程从socket循环读取数据,直到没有更多数据可读,然后将读取到的数据封装成一个请求对象并插入请求队列。
  • 4、睡眠在请求队列上的某个工作线程被唤醒,它获得请求对象并处理客户请求,然后往epoll内核事件表中注册socket上的写就绪事件。
  • 5、主线程调用epoll_wait等待socket可写。
  • 6、当socket可写时,epoll_wait通知主线程。主线程往socket上写入服务器处理客户请求的结果。
    在这里插入图片描述

4、Reactor和Proactor优缺点和对比

对比
Reactor和Proactor模式的主要区别就是真正的读取和写入操作是有谁来完成的:

  • Reactor中需要应用程序自己读取或者写入数据,
  • Proactor模式中,应用程序不需要进行实际的读写过程,它只需要从缓存区读取或者写入即可,操作系统会读取缓存区或者写入缓存区到真正的IO设备.

Reactor优缺点
优点

  • Reactor实现相对简单,对于耗时短的处理场景处理高效;

  • 操作系统可以在多个事件源上等待,并且避免了多线程编程相关的性能开销和编程复杂性;

  • 事件的串行化对应用是透明的,可以顺序的同步执行而不需要加锁; 事务分离:将与应用无关的多路分解和分配机制和与应用相关的回调函数分离开来,

缺点

  • Reactor处理耗时长的操作会造成事件分发的阻塞,影响到后续事件的处理;

Proactor优缺点

优点

  • Proactor性能更高,能够处理耗时长的并发场景;

缺点
Proactor实现逻辑复杂;依赖操作系统对异步的支持,目前实现了纯异步操作的操作系统少,实现优秀的如windows IOCP,但由于其windows系统用于服务器的局限性,目前应用范围较小;而Unix/Linux系统对纯异步的支持有限,应用事件驱动的主流还是通过select/epoll来实现;

适用场景

  • Reactor:同时接收多个服务请求,并且依次同步的处理它们的事件驱动程序;

  • Proactor:异步接收和同时处理多个服务请求的事件驱动程序;

参考

1、https://www.cnblogs.com/doit8791/p/7461479.html
2、https://blog.csdn.net/weixin_34402090/article/details/9470718
3、《Linux 高性能服务器编程》——游双
4、https://www.cnblogs.com/me115/p/4452801.html

猜你喜欢

转载自blog.csdn.net/JMW1407/article/details/107916786