Linux网络编程--两种高效的事件处理模式

前言

  今天,我们主要介绍一下网络设计模式中的两种事件处理模式,Reactor和Proactor模式。通常使用同步I/O模型(select,poll,epoll等)用来实现Reactor模式,而异步(aio_read或aio_write等)用来实现Proactor模式,不过还可以使用同步来模拟Proactor模式。

Reactor模式

设计思路

  主线程(I/O处理单元)只负责对I/O事件的监听,而不负责对I/O的读写,如果有I/O事件发生,则主线程会将该事件通知给工作线程(逻辑单元),由工作线程来完成具体的I/O读写和逻辑处理,比如:读写数据、接收新的连接和处理客户请求。

工作流程

  使用同步I/O模型(epoll)实现的reactor模式的工作流程:
  (1) 主线程注册socket上的可读事件到epoll内核事件表;
  
  (2) 主线程调用epoll_wait等待socket上的有数据可读 ;
  
  (3) 当socket上有数据可读时,epoll_wait通知主线程,主线程将可读事件放到请求队列 ;
  
  (4) 睡眠在请求队列上的某个工作线程被唤醒,它从socket上读取数据,并处理客户请求,然后往epoll内核事件表中注册socket可写事件 ;
  
  (5) 主线程调用epoll_wait等待事件可写 ;
  
  (6) 当socket可写时,epoll_wait通知主线程,主线程将可写事件放到请求队列 ;
  
  (7) 睡眠在队列上的工作线程被唤醒,他往socket上的写入服务器处理客户请求的结果。

工作流程图

Linux网络编程--两种高效的事件处理模式

Proactor

设计思路

  Proactor与Reactor的不同之处在于Reactor将I/O处理交由工作线程来完成,而Proactor则将I/O处理通过异步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。

工作流程图

Linux网络编程--两种高效的事件处理模式

注意:在上图中,连接socket上的读写事件是通过aio_read/aio_write向内核注册的,因此内核将通过信号来向应用进程报告连接socket上的读写事件。所以,主线程中的epoll_wait调用仅能用来检测监听socket上的连接请求事件,而不能用来检测连接socket上的读写事件。

模拟Proactor模式

思想

  我们还可以使用同步I/O来模拟异步I/O,实际上也就是用主线程来执行Procactor中内核做的数据读写操作。主线程读写完成后,再向工作线程通知,这样在工作线程看来,就是直接获得了数据读写的结果,接下来就只是对读写的结果进行逻辑处理即可。

工作流程

(1)主线程往epoll内核事件表中注册socket上读就绪事件;

(2)主线程调用epoll_wait等待socket上有数据可读;

(3)当socket上有数据可读时,epoll_wait通知主线程。主线程从socket循环读取数据,直到没有更多数据可读,然后将读取到的数据封装成一个请求对象并插入请求队列;

(4)睡眠在请求队列上的某个工作线程被唤醒,它获取请求对象并处理客户请求,然后往epoll内核事件表中注册socket上的写就绪事件;

(5)主线程调用epoll_wait等待socket可写;

(6)当socket可写时,epoll_wait通知主线程。主线程往socket上写入服务器处理客户请求的结果;

工作流程图

Linux网络编程--两种高效的事件处理模式