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
逆置了程式處理的流程,其基本的思想即為
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下為I/O事件,由作業系統觸發
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等等。對于傳統的線程池模型來說隻能每個對于每個請求使用一個單獨的線程去處理,這就導緻了當請求增加時過多了線程上下文切換,出現了性能上的瓶頸。
事件驅動的模型如下圖,充分的利用linux select epoll模型,并注冊相應的回調函數對于不同的事件,根據I/O多路複用的機制,實作了高并發,和高可擴充性。
實作
如下是一種Reactor的簡單實作,監聽STDIN,并注冊不同的事件。處理網絡請求也類似,具體可參考 reactor-server源碼
#include <sys/epoll.h>
#include <unistd.h>
#include <iostream>
#include <array>
#include <unordered_map>
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();
}
Reference
- Pattern-Oriented Software Architecture: Patterns for Concurrent and Networked Objects, Volume 2
- 《libevent源碼剖析》
- libevent
- reactor(推薦)
- twisted Python事件驅動網絡架構