事件处理模式
使用反应器(Reactor)结构化模式,事件驱动的应用可以多路分解并分配从一个或者多个客户机发送给应用的服务请求。反应器模式引入的结构“逆转”了应用中的控制流,这就是所说的好莱坞原则(“不要打电话给我们,我们会打电话给你的”)。然而它的事件分解是串行的。
主动器(Proactor)结构模式使事件驱动应用能有效地多路分解和分配由完成的异步操作所触发的服务请求。能获得并发的性能优势。
异步完成标记(Asynchronous Completion Token)设计模式使应用程序能对它在服务中调用异步操作而引起的响应进行有效地多路分解和处理。
一旦完成了连接和初始化,接受器-连接器(Acceptor-Connector)设计模式将网络化系统中同级服务的连接和协作初始化与随后进行的处理分开。接受器-连接器允许应用程序配置它们的连接拔掉结构,进行这种配置不依赖于应用程序所提供的服务。该模式可以置于反应器或者主动器之上,处理那些和建立服务间连接关系有关的事件。
3.1反应器
1.问题
在分布式系统,特别是服务器中的事件驱动应用,必须随时准备同时处理多个服务请求,即使这些请求在应用程序中最终还是被串行处理的。因此,在顺序执行特殊的服务之前,一个事件驱动应用程序必须先将并发到达的指示事件多路分解和分配给相应的服务实现。要有效地解决这些问题,需要考虑以下四个强制条件:
1)为了提高可扩展性和响应性能,应用程序不应该被单个指示事件源所阻塞,而排斥其他的事件源,否则来降低服务器对客户机的响应能力。
2)为了使吞吐率最大,应避免CPU之间的任何不必要的语境切换、同步和数据移动。
3)新的或改进的服务应该很容易地集成已有的指示事件多路分解和分配机制。
4)应用程序代码应尽量不受多线程和同步机制的复杂性的影响。
2.解决方案
同步等待多个事件源(如连接的Socket句柄)的指示事件到达。将对事件多路分解以及分配处理事件的服务的机制进行集成;将事件多路分解和分配机制从服务中对指示事件的与应用有关的处理分离出去。
细节:对于应用程序提供的每个服务,引入一个单独的事件处理程序处理某一个事件源的某一类型的事件。在反应器中注册所有的事件处理程序,反应器使用一个同步事件多路分解器等待一个或多个事件源的指示事件。发生指示事件后,同步事件多路分解器通知反应器,后者同步地分配与事件相关的事件处理程序,以便这些事件处理程序可以执行请求的服务。
3.结构
操作系统提供句柄(handle)来识别像网络连接或打开文件这样的事件源,事件源产生指示事件并对它的进行排队。指示事件可以来自于外部,也可以来源于内部。一旦一个事件源产生了一个指示事件,该提示事件就被送入相关句柄的队列中,句柄被标记为“就绪”。这样不会阻塞调用线程。
同步事件多路分解器(synchronous event demultiplexer)是一个函数。调用该函数,可以等待发生在一组句柄(句柄集)上的一个或多个指示事件。该函数在开始一直阻塞着,直到句柄集上的指示事件通知同步事件多路分解器“该句柄集上的一个或多个句柄变为‘就绪’”,这就意味着在没有阻塞的情况下可以启动一个对句柄的操作。
事件处理程序定义一个由一个或多个钩子方法组成的接口。钩子方法代表操作,这些操作可用于处理与应用有关的发生在与某事件处理程序相关联的句柄上的指示事件。
具体事件处理程序(concrete event handle)是实现应用程序所提供的特定服务的事件处理程序。每个具体事件处理程序都和一个句柄相关,句柄决定应用程序中的服务。特别是具体事件处理程序实现了钩子方法,这些钩子方法负责处理通过对应的句柄接收来的指示事件。将服务结果写到句柄上,并返回给调用者。
反应器(reactor)定义了一个接口,允许应用程序注册或删除事件处理程序及其相应的句柄,并运行应用程序的事件循环。反应器使用同步事件多路分解器等待在句柄集上发生指示事件。当有指示事件时,反应器首先从发生指示事件的句柄上将每个指示事件多路分解给相应的事件处理程序,然后在句柄上分配合适的钩子方法以处理这些事件。
4.实现
反应器模式的各个部分分成两层:
·多路分解/分配基础设施层的组件。本层执行一个通用的,与应用无关的策略,用于将指示事件多路分解到事件处理程序,然后分配相应的事件处理程序钩子的方法。
·应用层组件。本层定义具体事件处理程序,在具体事件处理程序的钩子方法中进行与应用有关的处理。
1)定义事件处理程序接口。事件处理程序具体指定了由一个或几个钩子方法组成的接口。这些钩子方法代表了处理由反应器接收并分配的指示事件件可用的服务集。
1.1)确定分配目标的类型。为实现反应器的分配策略,一个句柄可以有两类事件处理程序。
·事件处理程序对象(event handler object)。在面向对象的应用中,将事件处理程序与句柄结合起来的一个常用的方法是建立一个事件处理程序对象。
·事件处理程序函数(event handler function)。将事件处理程序与句柄结合起来的另一个策略是向反应器注册一个指向函数的指针。使用函数指针作为分配目标,不用定义继承事件处理程序基类的新的子类,就可以很方便地注册回调函数。
1.2)确定事件处理程序分配接口策略。下一步必须定义事件处理程序时支持的接口类型。假设使用事件处理程序对象而不用函数指针,这里有两种大致的策略:
·单方法分配接口策略(single-method dispatch interface strategy)。该接口只包括一个事件处理方法。使用单方法分配接口策略,就可以在不改变类接口的情况下,支持新的指示事件类型。但是该策略要求在具体事件处理程序的方法中大量使用switch和if语句来处理特定事件,从而降低了可扩展性。
·多方法分配接口策略。该策略是为处理每一类事件创建一个单独的钩子方法。这比单方法分配接口策略更具有扩展性,因为是由反应器实现而不是由具体事件处理程序的方法来进行多路分解的。
2)定义反应器接口。应用程序使用反应器接口注册或删除事件处理程序及相应的句柄,并调用应用程序的事件循环。
3)实现反应器接口。
3.1)开发反应器的实现层次。
3.2)选择一种同步事件多路分解器机制。
3.3)实现一个多路分解表。除了调用同步事件多路分解器等待句柄集发生指示事件之外,反应器实现还要维护一个多路分解表。该表是一个管理者,包含一个格式为<句柄, 事件处理程序, 指示事件类型>的三元组表。反应器实现使用句柄作为关键字和多路分解表中的事件处理程序建立关联。该表也存储了事件处理程序注册到句柄上的指示事件的类型。
3.4)定义反应器的具体实现。
4)确定应用程序所需要的反应器数量。对于某些实时应用程序,通过将不同的反应器与具有不同优先级的线程相关联,可以为不同类型的同步操作处理指示事件提供了不同等级的服务质量。
5)实现具体事件处理程序。
5.1)确定维护具体事件处理程序状态的策略。一个事件处理程序需要维护与特定请求有关的状态信息。
5.2)实现用一个句柄配置各具体事件处理程序的策略。
·硬编码。该策略将句柄或者句柄的包装器外观硬编码到具体事件处理程序中。这种方法很容易实现,但是如果对于不同的用况必须将不同类型的句柄或IPC机制配置到事件处理程序中时,重用性不好。
·类属(generic)方法。一个更通用的策略是在模板化事件处理程序类中以类型为参数来实例化包装器外观。这种方法能产生更灵活和可重用的事件处理程序,虽然在总使用一种句柄类型或者IPC机制的情况下,这种通用性是不必要的。
5.3)实现具体事件处理程序的功能。
5.结论
优点:
1)事务分离。反应器模式将与应用程序无关的多路分解和分配机制和与应用有关的钩子方法功能分开。与应用无关的机制被设计成可重用的组件。该组件知道如何多路分解指示事件并分配由事件处理程序定义的适当的钩子方法。相反,钩子方法中与应用有关的功能知道如何完成特定类型的服务。
2)模块化,可重用性与可配置性。该模式将事件驱动的应用功能分解成几个组件。它们被松散地集成在反应器中,这种模块化有助于更高级的软件组件重用。
3)可移植性。只要把反应器接口从实现中使用的低层操作系统的同步事件多路分解器函数中分离出来,就可以很容易地跨平台移植使用反应器模式的应用程序。
不足:
1)应用范围受到限制。如果操作系统支持对句柄集的同步事件多路分解,使用反应器模式会很高效。然而,如果操作系统并不提供这类支持,可以在反应器实现中使用多线程模拟反应器模式的语义。
2)非抢先的方式(Non-pre-emptive)。事件处理程序不应该执行耗时长的操作,因为这样会阻塞整个进程,并妨碍反应器对连接到其他句柄上的客户机的响应。
3)调试和测试的复杂性。