Reactor网络编程架构模型
常见的网络编程模型有:Reactor Proactor Asynchronous Completion Token and Acceptor_Connector
这里介绍最主流的reactor模型:
通常网络编程模型处理的主要流程如下: initiate => receive => demultiplex => dispatch => process events
I/O多路复用可以用作并发事件驱动(event-driven)程序
的基础,即整个事件驱动模型是一个状态机
,包含了:状态(state), 输入事件(input-event), 状态转移(transition), 状态转移即状态到输入事件的一组映射。通过I/O多路复用的技术检测事件的发生,并根据具体的事件(通常为读写),进行不同的操作,即状态转移。
Reactor是什么?
关于reactor 是什么,我们先从wiki上看下:
The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers
.
从上述文字中我们可以看出以下关键点 :
事件驱动(event handling
)
可以处理一个或多个输入源(one or more inputs)
通过Service Handler
同步的将输入事件(Event)
采用多路复用分发
给相应的Request Handler(多个)
处理
自POSA2 中的关于Reactor Pattern 介绍中,我们了解了Reactor 的处理方式
:
同步的等待多个事件源到达(采用select()实现
)
将事件多路分解以及分配相应的事件服务进行处理,这个分派采用server集中处理(dispatch
)
分解的事件以及对应的事件服务应用从分派服务中分离出去(handler
)
在reactor pattern处理模式中定义以下三种角色:
Reactor
将I/O事件分派给对应的Handler
Acceptor
处理客户端新连接,并分派请求到处理器链中
Handlers
执行非阻塞读/写 任务
常见的Reactor模型分为三种:
1、单reactor单线程模型
这是最基本的单Reactor单线程模型
。其中Reactor线程,负责多路分离套接字,有新连接到来触发connect
事件之后,交由Acceptor
进行处理,有IO读写事件之后交给hanlder
处理。
Acceptor
主要任务就是构建handler
,在获取到和client
相关的SocketChannel
之后 ,绑定到相应的hanlder
上,对应的SocketChannel
有读写事件之后,基于reactor 分发,hanlder就可以处理了(所有的IO事件都绑定到selector上,有Reactor分发)。
该模型 适用于处理器链中业务处理组件能快速完成的场景。不过,这种单线程模型不能充分利用多核资源,所以实际使用的不多。
2、单reactor多线程模型
相对于第一种单线程的模式来说,在处理业务逻辑,也就是获取到IO的读写事件之后,交由线程池 thread pool
来处理,这样可以减小主reactor的性能开销,从而更专注的做事件分发工作了,从而提升整个应用的吞吐。
3、多reactor多线程模型
第三种模型比起第二种模型,是将Reactor分成两部分,
mainReactor
负责监听server socket
,用来处理新连接的建立,将建立的socketChannel
指定注册给subReactor
。
subReactor
维护自己的selector
, 基于mainReactor
注册的socketChannel
多路分离IO读写事件,读写网络数据,对业务处理的功能,另其扔给worker线程池来完成。
Reactor模式是一种典型的事件驱动的编程模型,Reactor逆置了程序处理的流程,其基本的思想即为Hollywood Principle— 'Don't call us, we'll call you'.
Reactor事件处理机制为:主程序将事件以及对应事件处理的方法在Reactor上进行注册, 如果相应的事件发生,Reactor将会主动调用事件注册的接口,即 回调函数
. libevent即为封装了epoll并注册相应的事件(I/O读写,时间事件,信号事件)以及回调函数,实现的事件驱动的框架。
Reactor事件处理机制的编程模型,在Redis中也得到了很好的运用,Redis中基于I/O多路复用(mutiplexing)
开发Reactor
事件处理机制,监听多个套接字的AE_READABLE读,AE_WRITABLE写
事件。读事件绑定读操作和具体执行命令的操作函数,写事件绑定命令回复的操作函数。
架构
The Reactor architectural pattern allows event-driven applications to demultiplex and dispatch service requests that are delivered to an application from one or more clients.
Reactor架构模式允许事件驱动的应用通过多路分发的机制去处理来自不同客户端的多个请求。
上图是Reactor核心
的事件处理流程,有如下几个关键组件
事件(事件源)
Linux上为文件描述符,handler
即为注册在特定事件上的程序,事件发生通常在linux下为IO事件,由操作系统触发
Reactor(反应器)
事件管理的接口,内部使用event demultiplexer
注册,注销事件;并运行时间循环,当有事件进入"就绪“状态时,调用注册事件的回调函数处理事件;
class Reactor {
public:
int register_handler(EventHandler *pHandler, int event);
int remove_handler(EventHandler *pHandler, int event);
void handle_events(timeval *ptv);
}
Event demultiplexer(事件多路分发机制)
通常是由操作系统提供的I/O多路复用的机制,例如select, epoll. 程序首先将handler(事件源)
以及对应的事件注册到event demultiplexer
上;当有事件到达时,event demultiplexer
就会发出通知,通知Reactor调用事件处理程序进行处理;
Event Handler(事件处理程序)
事件处理程序提供了一组接口,在reactor
相应的事件发生时调用,执行相应的事件处理,通常会绑定一个有效的handler
class Event_Handler {
public:
// events maybe read/write/timeout/close .etc
virtual void handle_events(int events) = 0;
virtual HANDLE get_handle() = 0;
}
用一个简单的例子说明这个模型:
下图描述了一个简单的日志服务器,即一个或者多个客户端通过不同的请求获得不同设备的日志,例如打印机的运行情况,数据库的TPS等等。对于传统的线程池模型来说只能每个对于每个请求使用一个单独的线程去处理,这就导致了当请求增加时过多了线程上下文切换,出现了性能上的瓶颈。
模型的实现:
如下是一种Reactor的简单实现,监听STDIN,并注册不同的事件。处理网络请求也类似,具体可参考 reactor-server源码
#include
#include
#include
#include
#include
typedef int EventType;
class Epoll {
// 封装了epoll I/O 多路复用的机制, Event demultiplexer
public:
static const int NO_FLAGS = 0;
static const int BLOCK_INDEFINITELY = -1;
static const int MAX_EVENTS = 5;
Epoll() {
fileDescriptor = epoll_create1(NO_FLAGS);
event.data.fd = STDIN_FILENO;
// 设置epoll event 为EPOLLIN(对应文件描述符可读), EPOLLPRI(对应文件描述符有紧急事件可读)
event.events = EPOLLIN | EPOLLPRI;
}
int wait() {
return epoll_wait(fileDescriptor, events.data(), MAX_EVENTS, BLOCK_INDEFINITELY);
}
int control() {
return epoll_ctl(fileDescriptor, EPOLL_CTL_ADD, STDIN_FILENO, &event);
}
~Epoll() {
close(fileDescriptor);
}
private:
int fileDescriptor;
struct epoll_event
event;
std::array<epoll_event, MAX_EVENTS> events{};
};
class EventHandler {
// Event Handler
public:
int handle_event(EventType et) {
std::cout << "Event Handler: " << et << std::endl;
return 0;
}
};
class Reactor {
// Dispatcher
public:
Reactor() {
epoll.control();
}
//注册对应的回调函数到handlers中
void addHandler(std::string event, EventHandler callback) {
handlers.emplace(std::move(event), std::move(callback));
}
void run() {
while (true) {
int numberOfEvents = wait();
for (int i = 0; i < numberOfEvents; ++i) {
std::string input;
std::getline(std::cin, input);
try {
// 根据的具体的事件去找对应的handler,并执行相应的操作
handlers.at(input).handle_event(EventType(i));
} catch (const std::out_of_range &e) {
std::cout << "no handler for " << input << std::endl;
}
}
}
}
private:
// handlers Table, 存储事件以及其对应的handlers
std::unordered_map<std::string, EventHandler> handlers{};
Epoll epoll;
int wait() {
int numberOfEvents = epoll.wait();
return numberOfEvents;
}
};
int main() {
Reactor reactor;
reactor.addHandler("a", EventHandler{});
reactor.addHandler("b", EventHandler{});
reactor.run();
}