第9章 接受器-连接器(Acceptor-Connector):用于连接和初始化通信服务的对象创建模式
Douglas C. Schmidt
接受器-连接器设计模式(Acceptor-Connector)使分布式系统中的连接建立及服务初始化与一旦服务初始化后所执行的处理去耦合。这样的去耦合通过三种组件来完成:acceptor、connector和service handler(服务处理器)。连接器主动地建立到远地接受器组件的连接,并初始化服务处理器来处理在连接上交换的数据。同样地,接受器被动地等待来自远地连接器的连接请求,在这样的请求到达时建立连接,并初始化服务处理器来处理在连接上交换的数据。随后已初始化的服务处理器执行应用特有的处理,并通过连接器和接受器组件建立的连接来进行通信。
图9-1 面向连接的应用级网关的物理体系
为演示接受器-连接器模式,考虑图9-1中所示的多服务、应用级Gateway(网关)。通常,Gateway使分布式系统中的协作组件去耦合,并允许它们在无需互相直接依赖的情况下进行交互。图9-1中的这个Gateway在不同的服务端点间路由数据,这些端点运行在用于监视和控制人造卫星群的远地Peer(对端)上。每个Peer中的服务经由Gateway发送和接收若干类型的数据,比如状态信息、大块数据和命令。一般而言,Peer可以分布在局域网和广域网中。
该Gateway是一个路由器,负责协调它的Peer之间的通信。从Gateway的角度来看,它为之路由数据的Peer服务仅由其应用级通信协议来进行区分,这些协议可能会使用不同的帧格式和有效负载类型。
Gateway在它的Peer之间使用面向连接的TCP/IP协议[11]来传输数据。在我们的示例网络配置中,每个服务都与一个连接端点绑定在一起;该端点由IP主机地址和TCP端口号指定。端口号唯一地标识服务的类型。为每种类型的服务/端口维护单独的连接增强了路由策略的灵活性,并提供了更为健壮的错误处理,如果网络连接意外关闭的话。
在我们的分布式应用中,Gateway和Peer必须能改变它们的连接角色,以支持不同的使用情况。特别地,或是可以主动地发起连接,或是可以被动地等待连接请求。例如,在一种配置中,Gateway可以主动地发起到远地Peer的连接,以便路由数据给它们。在另一种配置中,Gateway可以被动地接收来自Peer的连接请求,后者随即经由Gateway路由数据给另外的Peer。同样地,在一种使用情况下,Peer可以是主动的连接发起者,而在另一种使用情况下则是被动的连接接受者。
由于我们的分布式应用的天性,预先指定连接建立和服务初始化角色、并将它们硬编码进Gateway和Peer组件的传统设计太不灵活。这样的设计过度地将连接建立、服务初始化和服务处理组件耦合在一起。这样的紧耦合使得独立于通信角色改变连接角色变得很困难。
分布式系统中利用面向连接协议来在服务端点间进行通信的客户/服务器应用。
分布式应用常常含有复杂的代码,执行连接建立和服务初始化。一般而言,分布式应用中在服务端点间交换的数据的处理极大地独立于配置问题,比如(1)是哪一个端点发起连接,也就是,连接角色 vs. 通信角色,以及(2)连接管理协议 vs. 网络编程API。下面对这些问题进行概述:
一般而言,用于连接建立和服务初始化的策略变动的频度要远小于应用服务实现和通信协议。因而,使这些方面去耦合、以使它们能独立地变化,对于开发和维护分布式应用来说是必要的。对于将连接及初始化协议与通信协议分离的问题,下面一些压力会对解决方案产生影响:
对于分布式应用提供的每个服务,使用接受器-连接器模式来使连接建立及服务初始化与由服务的两个端点在连接和初始化之后执行的后续处理去耦合。
引入两个工厂,生成已连接和初始化的服务处理器,用于实现应用的服务。第一个工厂,称为接受器,创建并初始化传输端点,被动地在特定地址上侦听来自远地连接器的连接请求。第二个工厂,连接器,主动地发起到远地接受器的连接。接受器和连接器都初始化相应的服务处理器,处理在连接上交换的数据。一旦服务处理器被连接和初始化,它们就执行应用特有的处理,一般不再与接受器和连接器进行交互。
在图9-2中通过Booch类图[2]演示了接受器-连接器模式中的参与者的结构。
服务处理器(Service
Handler):Service Handler实现应用服务,通常扮演客户角色、服务器角色,或同时扮演这两种角色。它提供挂钩方法,由Acceptor或Connector调用,以在连接建立时启用应用服务。此外,Service Handler还提供数据模式传输端点,其中封装了一个I/O句柄,比如socket。一旦连接和初始化后,该端点被Service Handler用于与和其相连的对端交换数据。
接受器(Acceptor):Acceptor是一个工厂,实现用于被动地建立连接并初始化与其相关联的Service Handler的策略。此外,Acceptor包含有被动模式的传输端点工厂,它创建新的数据模式端点,由Service Handler用于在相连的对端间传输数据。通过将传输端点工厂绑定到网络地址,比如Acceptor在其上侦听的TCP端口号,Acceptor的open方法对该工厂进行初始化。
一旦初始化后,被动模式的传输端点工厂侦听来自对端的连接请求。当连接请求到达时,Acceptor创建Service Handler,并使用它的传输端点工厂来将新连接接受进Service Handler中。
连接器(Connector):Connector是一个工厂,实现用于主动地建立连接并初始化与其相关联的Service Handler的策略。它提供方法,由其发起到远地Acceptor的连接。同样地,它还提供另一个方法,完成对Service Handler的启用;该处理器的连接是被同步或异步地发起的。Connector使用两个分开的方法来透明地支持异步连接建立。
图9-2 Acceptor-Connector模式的参与者的结构
当连接建立时,Acceptor和Connector都通过调用Service Handler的启用挂钩方法来将其启用。一旦Service
Handler被Acceptor或Connector工厂完全初始化,它通常就不再与这些组件进行交互了。
分派器(Dispatcher):为Acceptor,Dispatcher将在一或多个传输端点上接收到的连接请求多路分离给适当的Acceptor。Dispatcher允许多个Acceptor向其登记,以侦听同时在不同端口上从不同对端而来的连接。
为Connector,Dispatcher处理异步发起的连接的完成。在这种情况下,当异步连接被建立时,Dispatcher回调Connector。Dispatcher允许多个Service Handler通过一个Connector来异步地发起和完成它们的连接。注意对于同步连接建立,Dispatcher并不是必需的,因为发起连接的线程控制也完成服务服务处理器的启用。
Dispatcher通常使用事件多路分离模式来实现,这些模式由反应堆(Reactor)[3]或前摄器(Proactor)[4]来提供,它们分别处理同步和异步的多路分离。同样地,Dispatcher也可以使用主动对象(Active Object)模式[5]来实现为单独的线程或进程。
下面的部分描述接受器-连接器模式中Acceptor和Connector组件所执行的协作。我们检查三种规范的情况:Acceptor、异步的Connector和同步的Connector。
图9-3演示Acceptor和Service Handler之间的协作。这些协作被划分为三个阶段:
图9-3 Acceptor参与者之间的协作
Connector组件可以使用两种常用方案来初始化它的Service
Handler:同步的和异步的。同步的服务初始化对于以下情形来说是有用的:
同样地,异步服务初始化在相反的情形中是有用的:
同步的Connector情况中的参与者之间的协作可被划分为以下三个阶段:
同步服务初始化的协作如图9-4所示。在此方案中,Connector将连接发起和服务初始化阶段结合进单一的阻塞操作中。在此情况中,只为每个线程控制中的每次connect调用建立一个连接。
图9-4 用于同步连接的Connector参与者之间的协作
异步的Connector中的参与者之间的协作可被划分为以下三个阶段:
图9-5演示这三个阶段的使用异步连接建立的协作。在异步方案中,注意连接发起阶段被暂时与服务初始化阶段分离开来。这样的去耦合使得多个连接发起(经由connect)和完成(经由complete)能够在各自的线程控制中并行地进行。
图9-5 用于异步连接的Connector参与者之间的协作
这一部分解释使用接受器-连接器模式来构建通信软件应用所涉及的步骤。这里的实现基于ACE OO网络编程工具包[6]中的可复用组件和应用。ACE提供一组丰富的可复用C++包装和构架组件,它们可在一系列OS平台上执行常用的通信软件任务。
接受器-连接器模式中的参与者被划分为反应、连接和应用层,如图9-6所示。
反应和连接层分别为分派事件和初始化服务执行通用的、与应用无关的策略。应用层通过提供建立连接和执行服务处理的具体类来实例化这些通用策略。这样的事务分离增强了接受器-连接器模式实现中的可复用性、可移植性和可扩展性。
图9-6 Acceptor-Connector模式实现中的参与者的分层和划分
下面对接受器-连接器模式实现的讨论从底部的反应层开始,并向上通过连接层和应用层。
反应层处理发生在由I/O句柄表示的传输端点(比如socket端点)上的事件。该层的两个参与者,Initiation Dispatcher(发起分派器)和Event Handler(事件处理器),是由反应堆(Reactor)模式[3]定义的。该模式使得程序在单线程控制中就能够高效地完成来自多个来源的多种类型的事件的多路分离。
反应层中的两个主要角色是:
事件处理器:它规定由挂钩方法[7]组成的接口,抽象地表示应用可提供的事件处理操作。例如,这些挂钩方法表示这样一些事件:新连接请求、异步开始的连接请求的完成,或是来自相连对端的数据的到达,等等。Acceptor和Connector组件是从Event Handler派生的具体的事件处理器。
发起分派器:为登记、移除和分派Event Handler定义接口。Synchronous Event
Demultiplexer(同步的事件多路分离器),比如select[8]或WaitForMultipleObjects[9],通知Initiation Dispatcher何时回调应用特有的事件处理器,以响应特定类型的事件。常用事件包括连接接受事件、数据输入和输出事件,以及超时事件。
注意Initiation
Dispatcher是9.6描述的Dispatcher的实现。一般而言,接受器-连接器Dispatcher可以是反应式、前摄式(Proactive)或多线程的。在这一实现中的特定的Initiation
Dispatcher使用反应式模型来在单线程控制中多路分离和分派具体的事件处理器。在我们的例子中,Initiation Dispatcher是单体(Singleton)[10],因为我们只需要它的一个实例用于整个进程。
连接层:
在此层中的所有行为都是完全通用的。特别地,注意下面描述的实现中的类是怎样委托具体的IPC机制和Concrete Service Handler的;后者是由在9.8.3中描述的应用层实例化的。
应用层委托连接层的方式与连接层委托反应层的方式相类似。例如,反应层中的Initiation Dispatcher代表连接层处理与初始化有关的事件,比如异步的建立连接。
在连接层中有三个主要角色:Service Handler(服务处理器)、Acceptor和Connector。
// PEER_STREAM
is the type of the
// Concrete IPC
mechanism.
template
<class PEER_STREAM>
class
Service_Handler : public Event_Handler
{
public:
// Pure virtual
method (defined by a subclass).
virtual int
open (void) = 0;
// Accessor
method used by Acceptor and
// Connector to
obtain the underlying stream.
PEER_STREAM
&peer (void)
{
return peer_stream_;
}
// Return the
address that we’re connected to.
PEER_STREAM::PEER_ADDR
&remote_addr (void)
{
return peer_stream_.remote_addr ();
}
protected:
// Concrete IPC
mechanism instance.
PEER_STREAM
peer_stream_;
};
一旦Acceptor或Connector建立了连接,它们调用Service Handler的open挂钩。该纯虚方法必须被Concrete
Service Handler子类定义;后者执行服务特有的初始化和后续处理。
连接器:该抽象类实现主动连接建立和初始化Service
Handler的通用策略。它的接口如下所示:
// The
SERVICE_HANDLER is the type of service.
// The
PEER_CONNECTOR is the type of concrete
// IPC active
connection mechanism.
template
<class SERVICE_HANDLER,
class
PEER_CONNECTOR>
class Connector
: public Event_Handler
{
public:
enum
Connect_Mode
{
SYNC, // Initiate connection
synchronously.
ASYNC // Initiate connection
asynchronously.
};
//
Initialization method.
Connector
(void);
// Actively
connecting and activate a service.
int connect
(SERVICE_HANDLER *sh,
const PEER_CONNECTOR::PEER_ADDR
&addr,
Connect_Mode mode);
protected:
// Defines the
active connection strategy.
virtual int
connect_service_handler(SERVICE_HANDLER *sh,
const PEER_CONNECTOR::PEER_ADDR
&addr,
Connect_Mode mode);
// Register the
SERVICE_HANDLER so that it can
// be activated
when the connection completes.
int
register_handler (SERVICE_HANDLER *sh, Connect_Mode mode);
// Defines the
handler’s concurrency strategy.
virtual int
activate_service_handler(SERVICE_HANDLER *sh);
// Activate a
SERVICE_HANDLER whose
// non-blocking
connection completed.
virtual int
complete (HANDLE handle);
private:
// IPC
mechanism that establishes
// connections
actively.
PEER_CONNECTOR
connector_;
// Collection
that maps HANDLEs
// to
SERVICE_HANDLER *s.
Map_Manager<HANDLE,
SERVICE_HANDLER *>handler_map_;
// Inherited
from the Event_Handler -- will be
// called back
by Eactor when events complete
//
asynchronously.
virtual int
handle_event (HANDLE, EVENT_TYPE);
};
// Useful
"short-hand" macros used below.
#define SH SERVICE_HANDLER
#define PC
PEER_CONNECTOR
Conncetor通过特定类型的PEER CONNECTOR和SERVICE HANDLER被参数化。PEER CONNECTOR提供的传输机制被Connector用于主动地建立连接,或是同步地、或是异步地。SERVICE
HANDLER提供的服务对与相连的对端交换的数据进行处理。C++参数化类型被用于使(1)连接建立策略与(2)服务处理器类型、网络编程接口和传输层连接协议去耦合。
参数化类型是有助于提高可移植性的实现决策。例如,它们允许整体地替换Connector所用的IPC机制。这使得Connector的连接建立代码可在含有不同网络编程接口(例如,有socket,但没有TLI;反之亦然)的平台间进行移植。例如,取决于平台是支持socket还是TLI[11],PEER CONNECTOR模板参数可以通过SOCK Connector或TLI Connector来实例化。使用参数化类型的另一个动机是改善运行时效率,因为模板实例化发生在编译时。
更为动态的去耦合可以经由继承和多态、通过使用[10]中描述的工厂方法(Factory Method)和策略(Strategy)模式来完成。例如,Connector可以存储指向PEER CONNECTOR基类的指针。根据从工厂返回的PEER CONNECTOR的子类,这个PEER CONNECTOR的connect方法可在运行时被动态地绑定。一般而言,在参数化类型和动态绑定之间的权衡是参数化类型可能带来额外的编译/链接时开销,而动态绑定可能带来额外的运行时开销。
connect方法是应用用以通过Connector来发起连接的入口。它的实现如下所示:
template
<class SH, class PC> int
Connector<SH,
PC>::connect(SERVICE_HANDLER *service_handler,
const
PEER_CONNECTOR::PEER_ADDR &addr,
Connect_Mode
mode)
{
connect_service_handler
(service_handler, addr, mode);
}
该方法使用桥接(Bridge)模式[10]来使Concrete Connector能透明地修改连接策略,而不用改变组件接口。为此,connect方法委托Connector的连接策略,connect_service_handler,来发起连接。如下所示:
template
<class SH, class PC> int
Connector<SH,
PC>::connect_service_handler
(SERVICE_HANDLER
*service_handler,
const
PEER_CONNECTOR::PEER_ADDR &remote_addr,
Connect_Mode
mode)
{
// Delegate to
concrete PEER_CONNECTOR
// to establish
the connection.
if
(connector_.connect (*service_handler,
remote_addr,
mode) == -1)
{
if (mode == ASYNC && errno
== EWOULDBLOCK)
{
// If connection doesn’t complete
immediately
// and we are using non-blocking
semantics
// then register this object with
the
// Initiation_Dispatcher Singleton
so it will
// callback when the connection is
complete.
Initiation_Dispatcher::instance
()->register_handler (this,
WRITE_MASK);
// Store the SERVICE_HANDLER in the
map of
// pending connections.
handler_map_.bind
(connector_.get_handle (),
service_handler);
}
}
else if (mode
== SYNC)
// Activate if we connect
synchronously.
activate_service_handler
(service_handler);
}
如图9-7所示,如果Connect_Mode参数的值是SYNC,一旦连接同步地完成,SERVICE HANDLER将会被启用。该图与图9-4相类似,但是提供了另外的实现细节,比如get_handle和handle_event挂钩方法的使用。
图9-7 用于同步连接的Connector参与者之间的协作
为高效地与多个Peer相连,Connector可能还需要主动、异步地建立连接,也就是,不阻塞调用者。如图9-8所示,异步行为通过将ASYNC连接模式传递给Connector::connect来指定。该图与图9-5相类似,但是还提供了其他与当前实现相应的细节。
一旦实例化,PEER CONNECTOR类提供具体的IPC机制来同步或异步地发起连接。这里所显示的Connector模式的实现使用OS和通信协议栈所提供的异步连接机制。例如,在UNIX或Win32上,Connector可以将socket设置进非阻塞模式,并使用像select或WaitForMultipleObject这样的事件多路分离器来确定连接何时完成。
为处理还未完成的异步连接,Connector维护Service Handler映射表。因为Connector继承自Event Handler,当连接完成时,Initiation Dispatcher可以自动回调Connector的handle_event方法。
handle_event方法是一个适配器(Adapter)[10],它将Initiation Dispatcher的事件处理接口转换为对Connector模式的complete方法的调用。
图9-8 用于异步连接的Connector参与者之间的协作
Connector的handle_event方法如下所示:
template
<class SH, class PC> int
Connector<SH,
PC>::handle_event (HANDLE handle, EVENT_TYPE type)
{
// Adapt the
Initiation_Dispatcher’s event
// handling API
to the Connector’s API.
complete
(handle);
}
complete方法启用刚刚成功完成非阻塞连接的SERVICE
HANDLER,如下所示:
template
<class SH, class PC> int
Connector<SH,
PC>::complete (HANDLE handle)
{
SERVICE_HANDLER
*service_handler = 0;
// Locate the
SERVICE_HANDLER corresponding
// to the
HANDLE.
handler_map_.find
(handle, service_handler);
// Transfer I/O
handle to SERVICE_HANDLER *.
service_handler->set_handle
(handle);
// Remove
handle from Initiation_Dispatcher.
Initiation_Dispatcher::instance
()->remove_handler (handle,
WRITE_MASK);
// Remove
handle from the map.
handler_map_.unbind
(handle);
// Connection
is complete, so activate handler.
activate_service_handler
(service_handler);
}
complete方法在其内部映射表中查找并移除已连接的SERVICE
HANDLER,并将I/O句柄传递给SERVICE HANDLER。最后,它通过调用activate_service_handler方法初始化SERVICE HANDLER。该方法委托由SERVICE HANDLER的open挂钩指定的并发策略。如下所示:
template
<class SH, class PC> int
Connector<SH,
PC>::activate_service_handler
(SERVICE_HANDLER
*service_handler)
{
service_handler->open
();
}
Service Handler的open挂钩在连接成功建立时被调用。注意该挂钩都将被调用,不管(1)连接是同步还是异步发起的,或(2)它们是被主动还是被动连接的。这样的统一性使得开发者有可能编写这样的Service Handler,其处理可以完全地与它们是怎样被连接和初始化的去耦合。
接受器(Acceptor):该抽象类为被动连接建立和初始化Service
Handler实现通用的策略。Acceptor的接口如下所示:
// The
SERVICE_HANDLER is the type of service.
// The
PEER_ACCEPTOR is the type of concrete
// IPC passive
connection mechanism.
template
<class SERVICE_HANDLER,
class PEER_ACCEPTOR>
class Acceptor :
public Event_Handler
{
public:
// Initialize
local_addr transport endpoint factory
// and register
with Initiation_Dispatcher Singleton.
virtual int
open(const PEER_ACCEPTOR::PEER_ADDR &local_addr);
// Factory
Method that creates, connects, and
// activates
SERVICE_HANDLER’s.
virtual int
accept (void);
protected:
// Defines the
handler’s creation strategy.
virtual
SERVICE_HANDLER *make_service_handler (void);
// Defines the
handler’s connection strategy.
virtual int
accept_service_handler(SERVICE_HANDLER *);
// Defines the
handler’s concurrency strategy.
virtual int
activate_service_handler(SERVICE_HANDLER *);
//
Demultiplexing hooks inherited from Event_Handler,
// which is
used by Initiation_Dispatcher for
// callbacks.
virtual HANDLE
get_handle (void) const;
virtual int
handle_close (void);
// Invoked when
connection requests arrive.
virtual int
handle_event (HANDLE, EVENT_TYPE);
private:
// IPC
mechanism that establishes
// connections
passively.
PEER_ACCEPTOR
peer_acceptor_;
};
// Useful
"short-hand" macros used below.
#define SH
SERVICE_HANDLER
#define PA
PEER_ACCEPTOR
Acceptor通过特定类型的PEER ACCEPTOR和SERVICE HANDLER被参数化。PEER ACCEPTOR提供的传输机制被Acceptor用于被动地建立连接。SERVICE HANDLER提供的服务对与远地对端交换的数据进行处理。注意SERVICE
HANDLER是由应用层提供的具体的服务处理器。
参数化类型使Acceptor的连接建立策略与服务处理器的类型、网络编程接口及传输层连接发起协议去耦合。就如同Connector一样,通过允许整体地替换Acceptor所用的机制,参数化类型的使用有助于提高可移植性。这使得连接建立代码可在含有不同网络编程接口(比如有socket,但没有TLI;反之亦然)的平台间移植。例如,取决于平台能够更为高效地支持socket还是TLI,PEER ACCEPTOR模板参数可以通过SOCK Acceptor或TLI Acceptor来实例化。
下面给出Acceptor的方法的实现。应用通过调用Acceptor的open方法来将其初始化。如下所示:
template
<class SH, class PA> int
Acceptor<SH,
PA>::open
(const
PEER_ACCEPTOR::PEER_ADDR &local_addr)
{
// Forward
initialization to the PEER_ACCEPTOR.
peer_acceptor_.open
(local_addr);
// Register
with Initiation_Dispatcher, which
//
‘‘double-dispatches’’ without get_handle()
// method to
extract the HANDLE.
Initiation_Dispatcher::instance
()->register_handler (this,
READ_MASK);
}
local_addr被传递给open方法。该参数含有网络地址,例如,本地主机的IP地址和TCP端口号,用于侦听连接。Open方法将此地址转发给PEER ACCEPTOR定义的被动连接接受机制。该机制初始化传输端点工厂,由后者将地址广告给有兴趣与此Acceptor连接的客户。
传输端点工厂的行为由用户所实例化的PEER ACCEPTOR的类型来决定。例如,它可以是socket[13]、TLI[14]、STREAM管道[15]、Win32命名管道等的C++包装。
在传输端点工厂被初始化后,open方法将其自身登记到Initiation Dispatcher。Initiation Dispatcher执行“双重分派”,回调Acceptor的get_handle方法,以获取底层传输端点工厂的HANDLE。如下所示:
template
<class SH, class PA> HANDLE
Acceptor<SH,
PA>::get_handle (void)
{
return
peer_acceptor_.get_handle ();
}
Initiation Dispatcher在内部表中存储此HANDLE。Synchronous Event
Demultipler(同步事件多路分离器),比如select,随即被用于检测和多路分离到来的来自客户的连接请求。因为Acceptor类继承自Event Handler,当连接从对端到达时,Initiation Dispatcher可以自动回调Acceptor的handle_event方法。该方法是一个适配器(Adapter),它将Initiation Dispatcher的事件处理接口转换为对Acceptor的accept方法的调用。如下所示:
template
<class SH, class PA> int
Acceptor<SH,
PA>::handle_event (HANDLE, EVENT_TYPE)
{
// Adapt the
Initiation_Dispatcher’s event handling
// API to the
Acceptor’s API.
accept ();
}
如下所示,accept方法是一个模板方法(Template Method)[10],它为创建新SERVICE HANDLER、将连接接受进其中并启用服务而实现接受器-连接器模式的被动初始化策略:
template
<class SH, class PA> int
Acceptor<SH,
PA>::accept (void)
{
// Create a new
SERVICE_HANDLER.
SH
*service_handler = make_service_handler ();
// Accept
connection from client.
accept_service_handler
(service_handler);
// Activate
SERVICE_HANDLER by calling
// its open() hook.
activate_service_handler
(service_handler);
}
该方法非常简洁,因为它将所有低级细节都分解进具体的SERVICE HANDLER和PEER ACCEPTOR中,后二者通过参数化类型被实例化,并可被Acceptor的子类定制。特别地,因为accept是模板方法,子类可以扩展Acceptor的任意或所有的连接建立和初始化策略。这样的灵活性使得开发者有可能编写这样的Service Handler,其行为与它们被被动地连接和初始化的方式是相分离的。
make_service_handler工厂方法定义Acceptor用于创建SERVICE HANDLER的缺省策略。如下所示:
template
<class SH, class PA> SH *
Acceptor<SH,
PA>::make_service_handler (void)
{
return new SH;
}
缺省行为使用了“请求策略”(demand
strategy),它为每个新连接创建新的SERVICE HANDLER。但是,Acceptor的子类可以重定义这一策略,以使用其他策略创建SERVICE HANDLE,比如创建单独的单体(Singleton)[10]或从共享库中动态链接SERVICE HANDLER。
accept_service_handler方法在下面定义Acceptor所用的SERVICE HANDLER连接接受策略:
template
<class SH, class PA> int
Acceptor<SH,
PA>::accept_service_handler(SH *handler)
{
peer_acceptor_->accept
(handler->peer ());
}
缺省行为委托PEER
ACCEPTOR所提供的accept方法。子类可以重定义accept_service_handler方法,以执行更为复杂的行为,比如验证客户的身份,以决定是接受还是拒绝连接。
Activate_service_handler定义Acceptor的SERVICE HANDLER并发策略:
template
<class SH, class PA> int
Acceptor<SH,
PA>::activate_service_handler(SH *handler)
{
handler->open
();
}
该方法的缺省行为是通过调用SERVICE
HANDLER的open挂钩将其启用。这允许SERVICE HANDLER选择它自己的并发策略。例如,如果SERVICE
HANDLER继承自Event
Handler,它可以登记到Initiation Dispatcher,从而在事件发生在SERVICE HANDLER的PEER STREAM通信端点上时,使Initiation Dispatcher能够分派其handle_event方法。Concrete Acceptor可以重定义此策略,以完成更为复杂的并发启用。例如,子类可以使SERVICE HANDLER成为主动对象(Active Object)[5],使用多线程或多进程来处理数据。
当Acceptor终止时,无论是由于错误还是由于整个应用的关闭,Initiation Dispatcher都调用Acceptor的handle_close方法,后者可以释放任何动态获取的资源。在此例中,handle_close方法简单地将close请求转发给PEER ACCEPTOR的传输端点工厂。如下所示:
template
<class SH, class PA> int
Acceptor<SH,
PA>::handle_close (void)
{
peer_acceptor_.close
();
}
应用层提供具体的进程间通信(IPC)机制和具体的Service Handler。IPC机制被封装在C++类中,以简化编程、增强复用,并使开发者能够整个地替换IPC机制。例如,9.9中使用的SOCK Acceptor、SOCK Connector,以及SOCK Stream类是ACE C++ socket包装类库[11]的一部分。它们通过高效、可移植和类型安全的C++包装来封装像TCP和SPX这样的面向连接协议的面向流的语义。
应用层中的三个主要角色描述如下:
具体的服务处理器(Concrete
Service Handler):该类定义具体的应用服务,由Concrete
Acceptor或Concrete
Connector启用。Concrete
Service Handler通过特定类型的C++ IPC包装(它与和其相连的对端进行数据交换)来实例化。
具体的连接器(Concrete
Connector):该类通过具体的参数化类型参数SERVICE
HANDLER和PEER
CONNECTOR来实例化通用的Connector工厂。
具体的接受器(Concrete
Acceptor):该类通过具体的参数化类型参数SERVICE
HANDLER和PEER
ACCEPTOR来实例化通用的Acceptor工厂。
Concrete Service Handler还可以定义服务的并发策略。例如,Service
Handler可以从Event
Handler继承,并采用反应堆(Reactor)[3]模式来在单线程控制中处理来自对端的数据。相反,Service Handler也可以使用主动对象(Active Object)模式[5]处理到来的数据,而其所在线程控制与Acceptor连接它所用的不相同。下面,我们为我们的Gateway例子实现Concrete Service Handler,演示怎样灵活地配置若干不同的并发策略,而又不影响接受器-连接器模式的结构或行为。
在9.9的示例代码中,SOCK Connector和SOCK Acceptor是分别用于主动和被动地建立连接的IPC机制。同样地,SOCK Stream被用作数据传输递送机制。但是,通过其他机制(比如TLI Connector或Named Pipe Acceptor)来参数化Connector和Acceptor也是相当直接的,因为IPC机制被封装在C++包装类中。同样地,通过使用不同的PEER STREAM,(比如SVR4 UNIX TLI Stream或Win32 Named Pipe Stream)来参数化Concrete Service Handler,很容易改变数据传输机制。
9.9演示怎样实例化Concrete Service
Handler、Concrete
Connector和Concrete
Acceptor,实现9.2中描述的Peer和Gateway。这个特定的应用层例子定制连接层中的Connector和Acceptor组件所提供的通用初始化策略。
下面的代码演示9.2中描述的Peer和Gateway怎样使用接受器-连接器模式来简化连接建立和服务初始化。9.9.1演示Peer怎样扮演被动角色,9.9.2演示Gateway怎样在与被动的Peer的连接建立中扮演主动角色。
图9-9演示Concrete Acceptor和Concrete Service Handler组件是怎样在Peer中构造的。该图中的Acceptor组件与图9-11中的Connector组件是互补的。
用于与Gateway通信的服务处理器:如下所示的Status Handler、Bulk Data Handler和Command Handler类处理发送到Gateway和从Gateway接收的路由消息。因为这些Concrete Service
Handler类继承自Service
Handler,它们可以被Acceptor被动地初始化。
为演示接受器-连接器模式的灵活性,这些Service Handler中的每个open例程都可以实现不同的并发策略。特别地,当Status Handler被启用时,它运行在单独的线程中;Bulk Data Handler作为单独的进程运行;而Command Handler运行在与Initiation Dispatcher相同的线程中,后者为Acceptor工厂进行连接请求的多路分离。注意这些并发策略的改变并不影响Acceptor的实现,它是通用的,因而也是高度灵活和可复用的。
我们从定义一个Service Handler开始,它为基于socket的数据传输使用SOCK Stream:
typedef
Service_Handler <SOCK_Stream>PEER_HANDLER;
图9-9 对端的Acceptor参与者的结构
PEER HANDLER的typedef构成所有后续服务处理器的基础。例如,Status Handler类处理发送到Gateway和从Gateway接收的状态数据:
class
Status_Handler : public PEER_HANDLER
{
public:
// Performs
handler activation.
virtual int
open (void)
{
// Make handler run in separate
thread (note
// that Thread::spawn requires a
pointer to
// a static method as the thread
entry point).
Thread::spawn
(&Status_Handler::service_run, this);
}
// Static entry
point into thread, which blocks
// on the handle_event
() call in its own thread.
static void
*service_run (Status_Handler *this_)
{
// This method can block since it
// runs in its own thread.
while (this_->handle_event () !=
-1)
continue;
}
// Receive and
process status data from Gateway.
virtual int
handle_event (void)
{
char buf[MAX_STATUS_DATA];
stream_.recv (buf, sizeof buf);
// ...
}
// ...
};
PEER HANDLER还可被子类化,以生成具体的服务处理器,处理大块数据和命令。例如,Bulk Data Handler类处理发送到Gateway和从Gateway接收的大块数据:
class
Bulk_Data_Handler : public PEER_HANDLER
{
public:
// Performs
handler activation.
virtual int
open (void)
{
// Handler runs in separate
process.
if (fork () == 0) // In child
process.
// This method can block since it
// runs in its own process.
while (handle_event () != -1)
continue;
// ...
}
// Receive and
process bulk data from Gateway.
virtual int
handle_event (void)
{
char buf[MAX_BULK_DATA];
stream_.recv (buf, sizeof buf);
// ...
}
// ...
};
Command Handler类处理发送到Gateway和从Gateway接收的命令。
class Command_Handler
: public PEER_HANDLER
{
public:
// Performs
handler activation.
virtual int
open (void)
{
// Handler runs in same thread as
main
// Initiation_Dispatcher singleton.
Initiation_Dispatcher::instance
()->register_handler (this,
READ_MASK);
}
// Receive and
process command data from Gateway.
virtual int
handle_event (void)
{
char buf[MAX_COMMAND_DATA];
// This method cannot block since
it borrows
// the thread of control from the
// Initiation_Dispatcher.
stream_.recv (buf, sizeof buf);
// ...
}
//...
};
用于创建Peer
Service Handler的接受器:如下所示的s_acceptor、bd_acceptor和c_acceptor对象是Concrete Acceptor工厂实例,它们分别创建并启用Status
Handler、Bulk
Data Handler和Command Handler。
// Accept
connection requests from Gateway and
// activate
Status_Handler.
Acceptor<Status_Handler,
SOCK_Acceptor> s_acceptor;
// Accept
connection requests from Gateway and
// activate
Bulk_Data_Handler.
Acceptor<Bulk_Data_Handler,
SOCK_Acceptor> bd_acceptor;
// Accept
connection requests from Gateway and
// activate
Command_Handler.
Acceptor<Command_Handler,
SOCK_Acceptor> c_acceptor;
注意模板和动态绑定的使用是怎样允许特定细节灵活地变化的。特别地,在整个这一部分中,当并发策略被修改时,没有Acceptor组件发生变化。这样的灵活性的原因是并发策略已被分解进Service Handler中,而不是与Acceptor耦合在一起。
图9-10 对端中的Acceptor组件的对象图
Peer主函数:主程序通过调用具体的Acceptor工厂的open挂钩(以每个服务的TCP端口为参数)来对它们进行初始化。如9.8.2所示,每个Acceptor工厂自动地在它的open方法中将其自身登记到Initiation
Dispatcher的实例。
// Main program
for the Peer.
int main (void)
{
// Initialize
acceptors with their
// well-known
ports.
s_acceptor.open
(INET_Addr (STATUS_PORT));
bd_acceptor.open
(INET_Addr (BULK_DATA_PORT));
c_acceptor.open
(INET_Addr (COMMAND_PORT));
// Event loop
that handles connection request
// events and
processes data from the Gateway.
for (;;)
Initiation_Dispatcher::instance()->handle_events
();
}
一旦Acceptor被初始化,主程序进入事件循环,使用Initiation
Dispatcher来检测来自Gateway的连接请求。当连接到达时,Initiation Dispatcher回调适当的Acceptor,由其创建适当的PEER HANDLER来执行服务、将连接接受进处理器、并启用处理器。
图9-10演示在与Gateway(如图9-12所示)的四个连接被建立、以及四个Service Handler被创建和启用后,Peer中的Concrete Acceptor组件之间的关系。在Concrete Service Handler与Gateway交换数据的同时,三个Acceptor也在主线程中持续地侦听新连接。
图9-11演示Concrete Connector和Concrete Service Handler组件是怎样在假想的Gateway配置中构造的。该图中的Connector组件与图9-9中的Acceptor组件是互补的。
图9-11 网关的Connector参与者的结构
用于Gateway路由的服务处理器:如下所示的Status Router、Buld Data Router和Command Router类将它们从源Peer接收到的数据路由到一或多个目的Peer。因为这些Concrete Service
Handler类继承自Service
Handler,它们可以被Connector主动地连接和初始化。
为演示接受器-连接器模式的灵活性,Service Handler中的每个open例程实现不同的并发策略。特别地,当Status Router被启用时,它运行在单独的线程中;Bulk Data Router作为单独的进程运行;而Command Router运行在与Initiation Dispatcher相同的线程中,后者为Connector工厂进行连接完成事件的多路分离。就如同Acceptor一样,注意这些并发策略的变动并不会影响Connector的实现,它是通用的,因而也是高度灵活和可复用的。
我们从定义一个Service Handler开始,它为基于socket的数据传输而定制:
typedef Service_Handler
<SOCK_Stream>PEER_ROUTER;
该类构成所有后续路由服务的基础。例如,Status Router类路由状态数据到Peer,或路由来自Peer的数据:
class
Status_Router : public PEER_ROUTER
{
public:
// Activate
router in separate thread.
virtual int
open (void)
{
// Thread::spawn requires a pointer
to a
// static method as the thread
entry point).
Thread::spawn
(&Status_Router::service_run, this);
}
// Static entry
point into thread, which blocks
// on the
handle_event() call in its own thread.
static void
*service_run (Status_Router *this_)
{
// This method can block since it
// runs in its own thread.
while (this_->handle_event () !=
-1)
continue;
}
// Receive and
route status data from/to Peers.
virtual int
handle_event (void)
{
char buf[MAX_STATUS_DATA];
peer_stream_.recv (buf, sizeof
buf);
// Routing takes place here...
}
// ...
};
PEER ROUTER可被子类化,以生成用于路由大块数据和命令具体的服务处理器。例如,Bulk Data Router路由数据到Peer,或路由来自Peer的数据:
class
Bulk_Data_Router : public PEER_ROUTER
{
public:
// Activates
router in separate process.
virtual int
open (void)
{
if (fork () == 0) // In child
process.
// This method can block since it
// runs in its own process.
while (handle_event () != -1)
continue;
// ...
}
// Receive and
route bulk data from/to Peers.
virtual int
handle_event (void)
{
char buf[MAX_BULK_DATA];
peer_stream_.recv (buf, sizeof
buf);
// Routing takes place here...
}
};
Command Router类路由命令数据到Peer,或路由来自Peer的命令数据:
class
Command_Router : public PEER_ROUTER
{
public:
// Activates
router in same thread as Connector.
virtual int
open (void)
{
Initiation_Dispatcher::instance
()->register_handler (this,
READ_MASK);
}
// Receive and
route command data from/to Peers.
virtual int
handle_event (void)
{
char buf[MAX_COMMAND_DATA];
// This method cannot block since it
borrows the
// thread of control from the
Initiation_Dispatcher.
peer_stream_.recv (buf, sizeof
buf);
// Routing takes place here...
}
};
用于创建Peer
Service Handler的接受器:下面的typedef定义为PEER ROUTER而定制的connector工厂:
typedef
Connector<PEER_ROUTERS, SOCK_Connector>PEER_CONNECTOR;
不像 Concrete
Acceptor组件,我们只需要单个Concrete Connector。原因是每个Concrete Acceptor都被用作创建特定类型的Concrete Service Handler(比如Bulk Data Handler或Command Handler)的工厂。因此,必须预先知道全部的类型,从而使多种Concrete Acceptor类型成为必要。相反,传递给Connector的connect方法的Concrete Service Handler是在外部初始化的。因此,它们可以统一地被当作PEER ROUTER处理。
Gateway主函数:Gateway的主程序如下所示。get_peer_addrs函数创建Status、Bulk Data和Command Router,通过Gateway路由消息。该函数(它的实现没有给出)从配置文件或名字服务中读取Peer地址列表。每个Peer地址含有IP地址和端口号。一旦Router被初始化,上面定义的Connector工厂通过将ASYNC标志传递给connect方法来异步地发起所有连接。
// Main program
for the Gateway.
// Obtain an
STL vector of Status_Routers,
//
Bulk_Data_Routers, and Command_Routers
// from a
config file.
void
get_peer_addrs (vector<PEER_ROUTERS> &peers);
int main (void)
{
// Connection
factory for PEER_ROUTERS.
PEER_CONNECTOR
peer_connector;
// A vector of
PEER_ROUTERs that perform
// the
Gateway’s routing services.
vector<PEER_ROUTER>
peers;
// Get vector
of Peers to connect with.
get_peer_addrs
(peers);
// Iterate
through all the Routers and
// initiate
connections asynchronously.
for
(vector<PEER_ROUTER>::iterator i = peers.begin ();
i != peers.end ();
i++)
{
PEER_ROUTER &peer = *i;
peer_connector.connect (peer,
peer.remote_addr (),
PEER_CONNECTOR::ASYNC);
}
// Loop forever
handling connection completion
// events and
routing data from Peers.
for (;;)
Initiation_Dispatcher::instance()->handle_events
();
/* NOTREACHED
*/
}
所有连接都被异步地调用。它们通过Connector的complete方法并发地完成,该方法在Initiation
Dispatcher的事件循环中被回调。此事件循环还为Command Router对象多路分离和分派路由事件;该对象运行在Initiation Dispatcher的线程控制中。Status Router和Bulk Data Router分别执行在单独的线程和进程中。
图9-12演示在与Peer(如图9-10所示)的四个连接被建立、以及四个Concrete Service Handler被创建和启用后,Gateway中的组件之间的关系。该图演示到另一个Peer的四个连接,它们被Connector“拥有”,还没有完成。当所有Peer连接完全建立时,Gateway将路由并转发由Peer发送给它的消息。
图9-12 网关中的Connector组件的对象图
接受器-连接器模式已被用于广泛的构架、工具包和系统中:
UNIX网络超级服务器:比如inetd[13]、listen[14]以及来自ASX构架[6]的Service Configurator(服务配置器)看守。这些超级服务器利用主Acceptor进程,在一组通信端口上侦听连接。每个端口都和与通信有关的服务(比如标准的Internet服务ftp、telnet、daytime和echo)相关联。接受器进程使inetd超级服务器的功能分解为两个分离的部分:一个用于建立连接,另一个用于接收和处理来自对端的的请求。当服务请求在被监控的端口上到达时,接受器进程接受请求,并分派适当的预登记的处理器来执行服务。
CORBA ORB:许多CORBA实现中的ORB核心层使用接受器-连接器来在客户请求ORB服务时被动地初始化服务器对象实现。[17]描述接受器-连接器模式怎样被用于实现The ACE
ORB(TAO)[18];TAO是CORBA一种实时实现。
WWW浏览器:像Netscape和Internet Explorer这样的WWW浏览器中的HTML解析组件使用connector组件的异步版本来建立与服务器的连接;这些服务器与HTML页面中嵌入的图像相关联。这样的行为是特别重要的,于是多个HTTP连接就可被异步地发起,以避免阻塞浏览器主事件循环。
Ericsson EOS呼叫中心管理系统:该系统使用接受器-连接器模式来使应用级呼叫中心管理器事件服务器[19]主动地与分布式中央管理系统中的被动的超级用户建立连接。
Spectrum项目:Spectrum项目的高速医学图像传输子系统[20]使用接受器-连接器模式来为存储大型医学图像被动地建立连接,并初始化应用服务。一旦连接被建立,应用就发送数兆字节的医学图像给图像仓库;或从图像仓库中进行接收。
ACE 构架:在本论文中描述的Service Handler、Connector和Acceptor类的实现在ACE面向对象网络编程构架[6]中作为可复用组件提供。
接受器-连接器模式提供以下好处:
增强面向对象软件的可复用性、可移植性和可扩展性:通过使服务初始化机制与后续服务处理去耦合来实现。例如,Acceptor和Connector中的应用无关的机制是可复用的组件,它们知道怎样(1)分别主动和被动地建立连接,以及(2)一旦连接被建立,初始化相关联的Service Handler。相反,Service Handler知道怎样执行应用特有的服务处理。
这样的事务分离是通过使初始化策略与服务处理策略去耦合来完成的。因而,每种策略都可以独立地发展。用于主动初始化的策略可以只编写一次,放进类库或构架中,并通过继承、对象合成或模板实例化来复用。因而,不需要为每个应用都重写同样的主动初始化代码。相反,服务可以根据不同的应用需求进行变化。通过使用Service Handler来参数化Acceptor和Connector,可以使这样的变化的影响被局限在软件的少量组件中。
改善应用健壮性:应用健壮性是通过彻底地使Service Handler和Acceptor去耦合来改善的。这样的去耦合确保了被动模式传输端点工厂peer_acceptor_不会偶然地被用于读写数据。这消除了在使用像socket或TLI[11]这样的弱类型网络编程接口时,可能发生的一类常见错误。
接受器-连接器模式有以下缺点:
额外的间接性:与直接使用底层的网络编程接口相比较,接受器-连接器模式可能带来额外的间接性。但是,支持参数化类型的语言(比如C++、Ada或Eiffel)可以较小的代价代价实现这些模式,因为编译器可以内联用于实现这些模式的方法调用。
额外的复杂性:对于简单客户应用(使用单个网络编程接口与单个服务器相连,并执行单项服务)来说,该模式可能会增加不必要的复杂性。
接受器-连接器模式使用模板方法(Template Method)和工厂方法(Factory Method)模式[10]。Acceptor的accept和Connector的connect及complete函数是模板方法,它们在连接建立时为连接到远地对端并初始化Service Handler而实现通用的服务策略。模板方法的使用使子类可以对创建、连接和启用Concrete Service Handler的特定细节进行修改。工厂方法被用于使Service Handler的创建与它的后续使用去耦合。
接受器-连接器模式有与客户-分派器-服务器(Client-Dispatcher-Service)模式(在[21]中描述)类似的意图。它们都关心使主动连接建立与后续服务去耦合。主要的区别是接受器-连接器模式同时致力于同步和异步连接的被动和主动服务初始化,而客户-分派器-服务器)模式只专注于同步连接建立。
感谢Frank Buschmann和Hans Rohnert对本论文提出的有益意见。
[1] W. R.
Stevens, TCP/IP Illustrated, Volume 1. Reading, Massachusetts: Addison
Wesley, 1993.
[2] G.
Booch, Object Oriented Analysis and Design with Applications (2nd
Edition). Redwood City, California: Benjamin/Cummings, 1993.
[3] D. C.
Schmidt, “Reactor: An Object Behavioral Pattern for Concurrent Event
Demultiplexing and Event Handler Dispatching,” in Pattern Languages of
Program Design (J. O. Coplien and D. C. Schmidt, eds.), pp. 529–545,
Reading, MA: Addison-Wesley, 1995.
[4] T.
Harrison, I. Pyarali, D. C. Schmidt, and T. Jordan, “Proactor – An Object
Behavioral Pattern for Dispatching Asynchronous Event Handlers,” in The 4th
Pattern Languages of Programming Conference (Washington University technical
report #WUCS-97-34), September 1997.
[5] R. G.
Lavender and D. C. Schmidt, “Active Object: an Object Behavioral Pattern for
Concurrent Programming,” in Pattern Languages of Program Design (J. O.
Coplien, J. Vlissides, and N. Kerth, eds.), Reading, MA: Addison-Wesley, 1996.
[6] D. C.
Schmidt, “ACE: an Object-Oriented Framework for Developing Distributed
Applications,” in Proceedings of the 6th USENIX C++ Technical
Conference, (Cambridge, Massachusetts), USENIX Association, April 1994.
[7] W.
Pree, Design Patterns for Object-Oriented Software Development. Reading,
MA: Addison-Wesley, 1994.
[8]
W.R.Stevens,UNIX Network Programming, Second Edition. Englewood Cliffs,
NJ: Prentice Hall, 1997.
[9] H.
Custer, Inside Windows NT. Redmond, Washington: Microsoft Press, 1993.
[10] E.
Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Patterns: Elements of
Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1995.
[11] D. C.
Schmidt, T. H. Harrison, and E. Al-Shaer, “Object-Oriented Components for
High-speed Network Programming,” in Proceedings of the 1st
Conference on Object-Oriented Technologies and Systems,(Monterey,CA),
USENIX, June 1995.
[12] D. C.
Schmidt, “IPC SAP: An Object-Oriented Interface to Interprocess Communication
Services,” C++ Report,vol.4, November/December 1992.
[13] W. R.
Stevens, UNIX Network Programming, First Edition. Englewood Cliffs, NJ:
Prentice Hall, 1990.
[14] S.
Rago, UNIX System V Network Programming. Reading, MA: Addison-Wesley,
1993.
[15] D. L.
Presotto and D. M. Ritchie, “Interprocess Communication in the Ninth Edition
UNIX System,” UNIX Research System Papers, Tenth Edition, vol. 2, no. 8,
pp. 523–530, 1990.
[16]
Object Management Group, The Common Object Request Broker: Architecture and
Specification, 2.0 ed., July 1995.
[17] D. C.
Schmidt and C. Cleeland, “Applying Patterns to Develop Extensible ORB
Middleware,” Submitted to the IEEE Communications Magazine, 1998.
[18] D. C.
Schmidt, D. L. Levine, and S. Mungee, “The Design and Performance of Real-Time
Object Request Brokers,” Computer Communications, vol. 21, pp. 294–324,
Apr. 1998.
[19] D. C.
Schmidt and T. Suda, “An Object-Oriented Framework for Dynamically Configuring
Extensible Distributed Communication Systems,” IEE/BCS Distributed Systems
Engineering Journal (Special Issue on Configurable Distributed Systems),
vol. 2, pp. 280–293, December 1994.
[20] G.
Blaine, M. Boyd, and S. Crider, “Project Spectrum: Scalable Bandwidth for the
BJC Health System,” HIMSS, Health Care Communications, pp. 71–81, 1994.
[21] F.
Buschmann, R. Meunier, H. Rohnert, P. Sommerlad, and M. Stal, Pattern-Oriented
Software Architecture - A System of Patterns. Wiley and Sons, 1996.