Reactor模式

Reactor模式

Reactor模式简介

Reactor反应器模式也称发布者模式或通知者模式,是一种将就绪事件派发给对应服务处理程序的事件设计模式

Reactor模式的角色构成

角色 解释
Handler (句柄) 用于表示不同的事件,本质就是一个文件描述符
Sychronous Event Demultiplexer (同步事件分离器) 本质就是一个系统调用,用于等待事件的发生,对于Linux来说,同步事件分离器指的是I/O多路复用,比如select , poll, epoll 等
Event Handler (事件处理器) 由多个回调方法构成,这些回调方法构成了与应用相关的对于某个事件的处理反馈
Concrete Event Handler (具体事件处理器) 事件处理器中的各个回调方法的具体实现
Initiation Dispatchar (初始分发器) 初始分发器实际上就是Reactor角色,初始分发器会通过同步事件分离器等待事件的发生,当对应的事件就绪时调用事件处理器,最后调对应的回调方法来处理这个事件

Reactor模式的工作流程

1、当应用程序向初始分发器注册具体事件处理器时,应用会标识出该事件处理器希望初始分发器在某个事件发生时向其通知,该事件和Handler关联

2、初始分发器会要求每个事件处理器向其内部的Handler,该Handler向操作系统标识了事件处理器

3、当所有的事件处理器注册完毕后,应用会启动初始分发器的事件循环,这时初始分发器会将每个事件处理器的Handler合并起来,并使用同步时间分离器等待这些事件的发生

4、当某个事件处理器的Handler变成Ready状态时,同步事件分离器会通知初始分发器

5、初始分发器会调用其对应的事件处理器中的对应的回调方法来响应该事件

epoll ET服务器(Reactor模式)

设计思路

epoll ET服务器

在epoll 服务器中,我们需要处理三种事件:

  • 读事件:如果是监听套接字的读事件就绪则调用accept函数获取底层连接,如果是其它套接字的读事件就绪则调用recv函数读取客户端发来的数据
  • 写事件:写事件就绪则将待发送的数据写入到发送缓冲区
  • 异常事件:当某个套接字的异常事件就绪时我们不做过多处理,直接关闭套接字

当epoll ET服务器监测到某一事件就绪后,就会将该事件交给对应的服务处理程序进行处理

epoll ET服务器中的五个角色

  • 句柄: 文件描述符
  • 同步时间分离器:I/O多路复用epoll
  • 事件处理器:包括读回调,写回调,异常回调
  • 具体事件处理器:读回调,写回调,异常回调的具体实现
  • 初始分发器:Reactor当中的Dispatcher(调度员)函数

Dispatcher函数要做的就是调用epoll_wait函数等待事件发生,当有事件发生时,将就绪事件派发给对应的服务处理程序即可

epoll ET服务器的工作流程

  • 首先epoll ET服务器需要进行套接字的创建、绑定、监听

  • 定义一个Reactor对象初始化,建立epoll模型

  • 为需要关注的文件描述符添加到epoll模型中并创建EventItem 结构,并将映射关系添加到Reactor对象的u_map中

  • 调用Reactor中的Dispatcher函数进行事件派发

Eventliem结构的设计思路

  • 在Reactor的工作流程中说到,在注册事件处理器时需要将其与Handler关联,本质上就是需要将读回调,写回调,异常回调和某个文件描述符关联起来
  • 这样做的目的是当某个文件描述符上的事件就绪就可以找到对应的各种回调函数,进而执行对应的回调方法

EventItem结构除了高阔文件描述符,和对应的事件回调函数外,还需要包含一个输入缓冲区(解决粘包问题),输出缓冲区,以及一个回调指针R

  • 因为某个文件描述符的读事件就绪时,我们调用recv函数读取客户端发来的数据,但是我们并不能保证我们读取到的是一个完整的报文,因此需要将读取的数据暂时存放到文件描述符对应的输入缓冲区中,我们读取数据是从输入缓冲区中读取一个个完整的报文。输入缓冲区主要解决粘包问题

  • 当处理完一个报文请求后,将响应数据发送给客户端,但是我们不能保证TCP的发送缓冲区有足够的空间给我们写入,因此我们呢将发送的数据暂存在发送缓冲区中,当TCP的发送缓冲区有空间,即读事件就绪时,再依次发送输出缓冲区的数据

  • EventItem结构中设置回指指针R,便于快速找到对应Reactor对象,因为后续我们需要通过EventItem结构找到Reactor对象

#pragma once 
#include 
#include "reactor.hpp"


class EventItem;
typedef int(*callback_t)(EventItem*);

class EventItem{
public:
  EventItem();
  void ManageCallBacks(callback_t _recv_handler, callback_t _send_handler, callback_t _error_handler);
  ~EventItem(){};
public:
  int sock;                   
  Reactor* R;                 

  callback_t recv_handler;    
  callback_t send_handler;    
  callback_t error_handler;  
  
  std::string inbuffer;          
  std::string outbuffer;
};

EventItem::EventItem() 
  :sock(-1), R(nullptr),
  recv_handler(nullptr),
  send_handler(nullptr),
  error_handler(nullptr)
{}

void EventItem::ManageCallBacks(callback_t _recv_handler, callback_t _send_handler, callback_t _error_handler){
  recv_handler = _recv_handler;
  send_handler = _send_handler;
  error_handler = _error_handler;
}

Reactor类的设计思路

  • 当所有事件处理器注册完成后,会用同步时间分离器等待事件发生,当某个事件处理器的Handle变成Ready状态时,会使用同步事件分离器初始分发器,然后通过事件的句柄Handle作为key来寻找对应的事件处理器,调用事件处理器中的对应的回调方法处理事件
  • 本质就是当事件注册 完毕后,会调用epoll_wait函数来等待这些事件发生,当某个事件就绪时epoll_wait函数会告知调用方,然后调用方根据就绪的文件描述符找到对应的各种回调函数,并调用对应的回调函数进行事件处理

对此我们可以设计出一个Reactor类的核心成员

  • 成员函数Dispatcher,这个函数就是所谓的初始分发器,该函数内部会调用epoll_wait函数等待事件的发生,当事件发生后会告知Dispatcher已经就绪的事件
  • 成员变量 u_map ,unordered_map 建立文件描述符和EventItem结构之间的映射,只要获取到文件描述符就可以获得到事件的处理方法
#pragma once 
#include "event_item.hpp"

#include 
#include 

#include 
#include 
#define EPOLL_CAP_MAX 1024


class Reactor{
public:
  Reactor() : epfd(-1){}
  ~Reactor(){ if (epfd > 0) close(epfd); }
  void InitReactor();
  void Dispatcher(int timeout);
private:
  int epfd; 
  std::unordered_map<int, EventItem> event_items;
};

void Reactor::InitReactor() {
  epfd = epoll_create(EPOLL_CAP_MAX);
  if (epfd < 0) {
    std::cerr << "epoll_create error" << std::endl;
    exit(5);
  }
}

Dispatcher的设计思路

Dispatcher是Reactor的一个成员函数,也是我们Reactor模式中的事件派发器。其工作就是调用epoll_wait等待事件发生,当某个文件描述符上的事件发生后,通过unordered_map找到该文件描述符对应的EventItem结构,然后调用其中的回调函数对事件进行处理


你可能感兴趣的:(Linux操作系统学习,linux)