设计模式——Reactor模式

Reactor模式

        Reactor是一种事件处理的设计模式,经常用于高并发的服务端网络开发中。异步的收取消息。将不同的消息绑定到不同的回调函数上。传统的设计是一种同步的停等协议,读写操作执行后要等待当前fd的下一次可读/写事件,这期间什么都不能干,程序就阻塞在事件上。

        有了Reactor以后的设计,告诉Reactor你所关注的事件和事件发生后的处理器,Reactor充当一个中间人的角色,非阻塞的检测事件是否发生,如果发生就调用注册的处理器。与传统设计不同的是Reactor可以同时非阻塞的检测多个I/O上的事件并处理,大大提高了程序运行效率。在高性能的I/O设计中,有两个比较著名的模式ReactorProactor模式,其中Reactor模式用于同步I/O,Proactor用于异步I/O操作。

Reactor 的事件处理机制

        首先来回想一下普通函数调用的机制:程序调用某函数->函数执行,程序等待->函数将结果和控制权返回给程序->程序继续处理。

        Reactor 释义“反应堆”,是一种事件驱动机制。和普通函数调用的不同之处在于:应用 程序不是主动的调用某个 API 完成处理,而是恰恰相反,Reactor 逆置了事件处理流程,应 用程序需要提供相应的接口并注册到 Reactor 上,如果相应的时间发生,Reactor 将主动调用 应用程序注册的接口,这些接口又称为“回调函数”。使用 Libevent 也是想 Libevent 框架注册相应的事件和回调函数;当这些时间发声时,Libevent 会调用这些回调函数处理相应的事 件(I/O 读写、定时和信号)。    

        Reactor模式称为反应器模式或应答者模式,是基于事件驱动的设计模式,拥有一个或多个并发输入源,有一个服务处理器和多个请求处理器,服务处理器会同步的将输入的请求事件以多路复用的方式分发给相应的请求处理器。

        Reactor设计模式是一种为处理并发服务请求,并将请求提交到一个或多个服务处理程序的事件设计模式。当客户端请求抵达后,服务处理程序使用多路分配策略,由一个非阻塞的线程来接收所有请求,然后将请求派发到相关的工作线程并进行处理的过程。

        在事件驱动的应用中,将一个或多个客户端的请求分离和调度给应用程序,同步有序地接收并处理多个服务请求。对于高并发系统经常会使用到Reactor模式,用来替代常用的多线程处理方式以节省系统资源并提高系统的吞吐量。

        用“好莱坞原则”来形容 Reactor 再合适不过了:不要打电话给我们,我们会打电话通 知你。 举个例子:你去应聘某 xx 公司,面试结束后。 “普通函数调用机制”公司 HR 比较懒,不会记你的联系方式,那怎么办呢,你只能面 试完后自己打电话去问结果;有没有被录取啊,还是被据了;

        “Reactor”公司 HR 就记下了你的联系方式,结果出来后会主动打电话通知你:有没有 被录取啊,还是被据了;你不用自己打电话去问结果,事实上也不能,你没有 HR 的留联系 方式。

Reactor 模式的优点

        Reactor 模式是编写高性能网络服务器的必备技术之一,它具有如下的优点:

         1)响应快,不必为单个同步时间所阻塞,虽然 Reactor 本身依然是同步的;

         2)编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/ 进程的切换开销;

        3)可扩展性,可以方便的通过增加 Reactor 实例个数来充分利用 CPU 资源;

         4)可复用性,reactor 框架本身与具体事件处理逻辑无关,具有很高的复用性。

缺点:

      1) Reactor模式增加了一定的复杂性。

      2) Reactor模式需要底层的同步事件多路分配器的支持。

      3)Reactor模式在IO读写数据时还是在同一个线程中实现的。

Reactor 模式框架

        使用 Reactor 模型,必备的几个组件:事件源、Reactor 框架、多路复用机制和事件处理 程序,先来看看 Reactor 模型的整体框架,接下来再对每个组件做逐一说明。

(1) 事件源

        Linux 上是文件描述符,Windows 上就是 Socket 或者 Handle 了,这里统一称为“句柄 集”;程序在指定的句柄上注册关心的事件,比如 I/O 事件。 

(2) event demultiplexer——事件多路分发机制

        由操作系统提供的 I/O 多路复用机制,比如 select 和 epoll。 程序首先将其关心的句柄(事件源)及其事件注册到 event demultiplexer 上; 当有事件到达时,event demultiplexer 会发出通知“在已经注册的句柄集中,一个或多 个句柄的事件已经就绪”; 程序收到通知后,就可以在非阻塞的情况下对事件进行处理了。 对应到 libevent 中,依然是 select、poll、epoll 等,但是 libevent 使用结构体 eventop 进行了 封装,以统一的接口来支持这些 I/O 多路复用机制,达到了对外隐藏底层系统机制的目的。

(3) Reactor——反应器

        Reactor,是事件管理的接口,内部使用 event demultiplexer 注册、注销事件;并运行事 件循环,当有事件进入“就绪”状态时,调用注册事件的回调函数处理事件。 对应到 libevent 中,就是 event_base 结构体。

一个典型的Reactor声明方式:

class Reactor 
{ 
public: int register_handler(Event_Handler *pHandler, int event); int remove_handler(Event_Handler *pHandler, int event); void handle_events(timeval *ptv); // ... 
};

(4) Event Handler——事件处理程序

         事件处理程序提供了一组接口,每个接口对应了一种类型的事件,供 Reactor 在相应的 事件发生时调用,执行相应的事件处理。通常它会绑定一个有效的句柄。 对应到 libevent 中,就是 event 结构体。 

下面是两种典型的 Event Handler 类声明方式,二者互有优缺点。


第一种:


class Event_Handler 
{ 
public: virtual void handle_read() = 0; virtual void handle_write() = 0; virtual void handle_timeout() = 0; virtual void handle_close() = 0; virtual HANDLE get_handle() = 0; // ... 
}; 

第二种:


class Event_Handler 
{ 
public: // events maybe read/write/timeout/close .etc virtual void handle_events(int events) = 0; virtual HANDLE get_handle() = 0; // ... 
}; 

 Reactor 事件处理流程

        前面说过 Reactor 将事件流“逆置”了,那么使用 Reactor 模式后,事件控制流是什么 样子呢? 可以参见下面的序列图。

Reactor应用场景

        Reactor模式是为了应对高并发的服务器端开发。redis、netty、ZeroMQ和一些游戏服务器都采用了反应堆模式。 

 Reactor模式与观察者模式区别

        观察者模式也称发布-订阅模式,主要是适用于对象间一对多的依赖关系,通常用作消息分发和处理,当某对象状态发生改变时,要通知其他依赖对象做出更新。是一种一对多的关系。当然,如果依赖的对象只有一个时,也是一种特殊的一对一关系。通常,观察者模式适用于消息事件处理,监听者监听到事件时通知事件处理者对事件进行处理。

        而Reactor模式主要用于高效的IO模式,明显的特征是“回调”思想的运用,提高效率,避免没有必要的耗时的等待,与对象间的依赖关系无关。

        两者相同点:

        当一个主体发生改变时,所有依属体都得到通知。

       差异点:

        观察者模式与单个事件源关联,而反应器模式则与多个事件源关联 。

 Reactor应用实例

libevent和libev就是比较典型的Reactor应用实例,下面以libev为例说明Reactor模式:

//初始化事件处理器
ev_io_init()//注册事件处理器,把事件处理器添加到ev_loop的观察列表中
ev_io_start()移除事件处理器,把事件处理器从ev_loop的观察列表中移除
//ev_io_stop()//reactor主循环,I/O多路复用和事件分发器
ev_run()
{//检查关注fd列表fdchanges,新增加或者修改的fd都保存在这个列表中//通过fd查找注册的事件处理器ANFD,检查事件处理器的关注事件fd_reify()//调用跨平台的多路复用api,封装过的,epoll的封装在ev_epoll.cbackend_poll()    //把事件加入待处理列表ev_feed_event()    //调用所有的待处理事件处理器ev_invoke_pending()
}

Reactor模式的主要步骤

1.client通过ev_io_init和ev_io_start接口把fd和事件处理器、套接字、关注的事件加入到reactor中;

2.Reactor的主循环调用I/O多路复用API获取就绪事件,并把就绪事件添加到就绪列表中;

3.事件分发器根据就绪列表中的fd,查找并调用client注册的事件处理器。

本文链接:https://my.lmcjl.com/post/16175.html

展开阅读全文

4 评论

留下您的评论.