Académique Documents
Professionnel Documents
Culture Documents
Douglas C. Schmidt
schmidt@cs.wustl.edu
Department of Computer Science
Washington University
St. Louis, MO 63130, USA
1 Intent
STATUS INFO
1
Due to the nature of our distributed application, con- may be necessary to extend the Gateway to interoper-
ventional designs that designate connection establishment ate with a directory service that runs over the IPX/SPX
and service initialization roles a priori and hard-code them communication protocol, rather than TCP/IP..
into the Gateway and Peer components are too inflexi- It should be possible to decouple (1) the connection
ble. Such a design overly couples the connection establish- roles, i.e., which process initiates a connection vs. ac-
ment, service initialization, and service processing compo- cepts the connection, from (2) the communication roles,
nents. This tight coupling make it hard to change connection i.e., which service endpoint is the client or the server. In
roles independently of the communication roles. general, the distinction between “client” and “server”
refer to communication roles, which may be orthogo-
3 Context nal to connection roles. For instance, clients often play
the active role when initiating connections with a pas-
A client/server application in a distributed system that uti- sive server. However, these connection roles can be re-
lizes connection-oriented protocols to communicate between versed. For example, a client that plays an active com-
service endpoints. munication role may wait passively for another process
to connect to it. The example in Section 2 illustrates
this latter use-case.
4 Problem It should be possible to write communication software
that is portable to many OS platforms in order to max-
Distributed applications often contain complex code that per-
imize availability and market share. Many low-level
forms connection establishment and service initialization. In
network programming APIs have semantics that are
general, the processing of data exchanged between service
only superficially different. Therefore, it hard to write
endpoints in a distributed application is largely independent
portable application using low-level APIs, such as sock-
of configuration issues such as (1) which endpoint initiated
ets and TLI, due to syntactic incompatibilities.
the connection, i.e., the connection role vs. the communca-
tion role and (2) the connection management protocol vs. the It should be possible to shield programmers from the
network programming API. These issues are outlined below: lack of typesafety in low-level network programming
Connection role vs. communication role: Connection APIs like sockets or TLI. For example, connection es-
establishment roles are inherently asymmetrical, i.e., the pas- tablishment code should be completely decoupled from
sive service endpoint waits and the active service endpoint subsequent data transport code to ensure that endpoints
initiates the connection. Once the connection is established, are used correctly. Without this strong decoupling, for
however, the communication role can be orthogonal to the instance, services may mistakenly read or write data on
connection role. Thus, data can be transferred between ser- passive-mode transport endpoint factories that should
vice endpoints in any manner that obeys the service’s com- only be used to accept connections.
munication protocol. Common communication protocols in- It should be possible to reduce connection latency by
clude peer-to-peer, request-response, and oneway streaming. using OS features like asynchronous connection es-
The connection management protocol vs. the network tablishment. For instance, applications with a large
programming API: Different network programming in- number of peers may need to asynchronously establish
terfaces, such as sockets or TLI, provide different APIs to many connections concurrently. Efficient and scalable
establish connections using various connection management connection establishment is particularly important for
protocols. Regardless of the protocol used to establish a con- applications that run over long-latency WANs.
nection, however, data can be transferred between endpoints It should be possible to reuse as much general-purpose
using uniform message passing operations, e.g., send/recv connection establishment and service initialization soft-
calls. ware as possible in order to leverage prior development
In general, the strategies for connection establishment and effort.
service initialization change much less frequently than ap-
plication service implementations and communication pro-
tocols. Thus, decoupling these aspects so that they can vary 5 Solution
independently is essential for developing and maintaining
distributed applications. The following forces impact the so- For each service offered by a distributed application, use the
lution to the problem of separating the connection and ini- Acceptor-Connector pattern to decouple connection estab-
tialization protocols from the communication protocol: lishment and service initialization from subsequent process-
ing performed by the two endpoints of a service once they
It should be easy to add new types of services, new ser- are connected and initialized.
vice implementations, and new communication proto- Introduce two factories that produce connected and ini-
cols without affecting the existing connection establish- tialized service handlers, which implement the application
ment and service initialization software. For instance, it services. The first factory, called acceptor, creates and ini-
2
Service Handler Service Handler
Service
Connector Handler ACTIVATES Acceptor
connect(sh, addr)
ACTIVATES peer_stream_ n 1 peer_acceptor_
complete() accept()
open()
1 n open()
Dispatcher
tializes a transport endpoint that passively listens at a par- Handler and uses its transport endpoint factory to accept a
ticular address for connection requests from remote connec- new connection into the Service Handler.
tors. The second factory, a connector, actively initiates a con-
nection to a remote acceptor. Acceptor and connectors both Connector: A Connector is a factory that implements
initialize the corresponding service handlers that process the the strategy for actively establishing a connection and ini-
data exchanged on the connection. Once the service handlers tializing its associated Service Handler. It provides a
are connected and initialized they perform the application- method that initiates a connection to a remote Acceptor.
specific processing, and generally do not interact with the Likewise, it provides another method that finishes activating
acceptor and connector any further. Service Handlers whose connections were initiated
either synchronously or asynchronously. The Connector
uses two separate methods to support asynchronous connec-
6 Structure tion establishment transparently.
Both the Acceptor and Connector activate a
The structure of the participants in the Acceptor-Connector Service Handler by calling its activation hook method
pattern is illustrated by the Booch class diagram [2] in Fig- when a connection is established. Once a Service
ure 2.1 Handler is completely initialized by an Acceptor or
Service Handler: A Service Handler implements an Connector factory it typically does not interact with these
application service, typically playing the client role, server components any further.
role, or both roles. It provides a hook method that is called Dispatcher: For the Acceptor, the Dispatcher de-
by an Acceptor or Connector to activate the application multiplexes connection requests received on one or more
service when the connection is established. In addition, the transport endpoints to the appropriate Acceptor. The
Service Handler offers a data-mode transport endpoint Dispatcher allows multiple Acceptors to register with
that encapsulates an I/O handle, such as a socket. Once con- it in order to listen for connections from different peers on
nected and initialized, this endpoint is used by the Service different ports simultaneously.
Handler to exchange data with its connected peer.
For the Connector, the Dispatcher handles the
Acceptor: An Acceptor is a factory that implements completion of connections that were initiated asyn-
the strategy for passively establishing a connection and chronously. In this case, the Dispatcher calls back to
initializing its associated Service Handler. In addi- the Acceptor when an asynchronous connection is es-
tion, the Acceptor contains a passive-mode transport end- tablished. The Dispatcher allows multiple Service
point factory that creates new data-mode endpoints used by Handlers to have their connections initiated and com-
Service Handler’s to transmit data between connected pleted asynchronously by a Connector. Note that the
peers. The Acceptor’s open method initializes its trans- Dispatcher is not necessary for synchronous connection
port endpoint factory once by binding it to a network address, establishment since the thread of control that initiates the
such as the TCP port number the Acceptor is listening on. connection also completes the service handler activation.
Once initialized, the passive-mode transport endpoint fac- A Dispatcher is typically implemented using an event
tory listens for connection requests from peers. When a con- demultiplexing pattern, such those provided by the Re-
nection request arrives, the Acceptor creates a Service actor [3] or Proactor [4], which handle synchronous and
1 In this diagram dashed clouds indicate classes; dashed boxes in the asynchronous demultiplexing, respectively. Likewise, the
clouds indicate template parameters; a solid undirected edge with a hollow Dispatcher can be implemented as a separate thread or
circle at one end indicates a uses relation between two classes. process using the Active Object pattern [5].
3
7 Dynamics threads, opening log files, and/or registering the Service
Handler with a Dispatcher.
The following section describes the collaborations per-
formed by the Acceptor and Connector components 3. Service processing phase: After the connection has
in the Acceptor-Connector pattern. We examine the three been established passively and the Service Handler
canonical scenarios: for the Acceptor, asynchronous has been initialized, the service processing phase begins.
Connector, and synchronous Connector. In this phase, an application-level communication protocol,
such as HTTP or IIOP, is used to exchange data between the
7.1 Acceptor Component Collaborations local Service Handler and its connected remote Peer
via its peer stream endpoint. When this exchange is
Figure 3 illustrates the collaboration between the complete the connection and Service Handlers can be
Acceptor and Service Handler participants. These shut down and resources released.
INITIALIZE PASSIVE
The Connector component can initialize its Service
ENDPOINT
ENDPOINT
PHASE
INSERT IN DISPATCHER register_handler(acc) Handler using two general schemes: synchronous and
handle_events() asynchronous. Synchronous service initialization is useful
START EVENT LOOP
for the following situations:
FOREACH EVENT DO select()
accept()
PROCESSING INITIALIZATION
CONNECTION EVENT
CREATE, sh = new Service_Handler If the latency for establishing a connection is very low,
SERVICE
PHASE
DATA EVENT
PROCESS MSG service() If the services must be initialized in a fixed order and
the client cannot perform useful work until connections
are established.
Figure 3: Collaborations Among Acceptor Participants
Likewise, asynchronous service initialization is useful in
collaborations are divided into three phases: opposite situations:
1. Endpoint initialization phase: To initialize a connec-
tion passively, an application calls the open method of the If connection latency is high and there are many peers to
Acceptor. This method creates a passive-mode trans- connect with, e.g., establishing a large number of con-
port endpoint and binds it to a network address, e.g. the nections over a high-latency WAN; or
local host’s IP name and a TCP port number, and then If only a single thread of control is available, e.g., if the
listens for connection requests from peer Connectors. OS platform does not provide application-level threads;
Next, the open method registers the Acceptor object or
with a Dispatcher so that the dispatcher can call back
to the Acceptor when connection events arrive. Finally, If the order in which services are initialized is not im-
the application initiates the Dispatcher’s event loop, portant and if the client application must perform addi-
which waits for connection requests to arrive from peer tional work, such as refreshing a GUI, while the con-
Connectors. nection is being established.
2. Service initialization phase: When a connection
request arrives, the Dispatcher calls back to the The collaborations among the participants in the syn-
Acceptor’s accept method. The accept method chronous Connector scenario can be divided into the fol-
assembles the resources necessary to (1) create a new lowing three phases:
Service Handler, (2) use its passive-mode transport
endpoint factory to accept the connection into the data- 1. Connection initiation phase: To synchronously ini-
mode transport endpoint of this handler, and (3) activate the tiate a connection between a Service Handler and
Service Handler by calling its open hook. The open its remote Peer, an application calls the Connector’s
hook of the Service Handler can perform service- connect method. This method actively establishes the
specific initialization, such as allocating locks, spawning connection by blocking the thread of control of the calling
thread until the connection completes synchronously.
4
3. Service processing phase: This phase is similar to the
con : sh: : disp other service processing phases described earlier. Once the
CONNECTION INITIATION/
Client
INITIATION
INITIATE CONNECTION
remote Service Handler it is connected to.
open()
ACTIVATE OBJECT
Figure 5 illustrates these three phases of collaboration us-
INSERT IN DISPATHER register_handler(sh) ing asynchronous connection establishment. In the asyn-
CONNECTION
FOREACH EVENT DO
PHASE
DATA ARRIVES
INITIATE CONNECTION connect(sh, addr)
PROCESS DATA service() INSERT IN DISPATCHER
register_handler(con)
handle_events()
START EVENT LOOP
Figure 4: Collaborations Among Connector Participants for
FOREACH EVENT DO select()
SERVICE
Synchronous Connections complete()
PHASE
CONNECTION COMPLETE
SERVICE
complete method to activates the Service Handler. handle_event()
PHASE
DATA ARRIVES
The complete method performs the activation by invok- PROCESS DATA service()
ing the Service Handler’s open hook method, which
performs service-specific initialization.
Connector’s complete method. This method activates dicate inheritance relationships between classes; a dashed directed edge in-
dicates template instantiation; and a solid circle illustrates a composition
the Service Handler by calling its open hook. This relationship between two classes.
open hook performs service-specific initialization.
5
CONNECTOR SERVICE HANDLER ACCEPTOR
ROLE ROLE ROLE
Concrete_Service_Handler
APPLICATION
Concrete_Service_Handler n SOCK Stream
1 SOCK_Acceptor
1 SOCK_Connector
Concrete
LAYER Concrete Service Concrete
Connector Handler Acceptor
SERVICE_HANDLER
SERVICE_HANDLER PEER_ACCEPTOR
A PEER_CONNECTOR A
ACTIVATES Acceptor
Connector PEER_STREAM
make_service_handler()
CONNECTION
activate_service_handler()
complete()
open() open()
connect(sh, addr) A accept()
1: connect_service_handler
(sh, addr); sh = make_service_handler();
accept_service_handler (sh);
activate_service_handler (sh);
2: activate_service_handler
(sh);
REACTIVE
LAYER
Event Initiation
Handler n 1
Dispatcher
classes that establish connections and perform service pro- Initiation Dispatcher: Defines an interface for regis-
cessing. This separation of concerns increases the reusabil- tering, removing, and dispatching Event Handlers.
ity, portability, and extensibility in the implementation of the The Synchronous Event Demultiplexer, such as
Acceptor-Connector pattern. select [8] or WaitForMultipleObjects [9], in-
The following discussion of the Acceptor-Connector pat- forms the Initiation Dispatcher when to call back
tern implementation starts at the bottom with the Reactive application-specific event handlers in response to certain
layer and works upwards through the Connection layer and types of events. Common events include connection ac-
Application layer. ceptance events, data input and output events, and timeout
events.
Note that the Initiation Dispatcher is an imple-
8.1 Reactive Layer mentation of the Dispatcher participant described in Sec-
The Reactive layer handles events that occur on transport tion 6. In general, the Acceptor-Connector Dispatcher
endpoints represented by I/O handles, such as socket end- participant can be reactive, proactive, or multi-threaded. The
points. The two participants in this layer, the Initiation particular Initiation Dispatcher in this implementa-
Dispatcher and Event Handler, are defined by the tion uses the reactive model to demultiplex and dispatch con-
Reactor pattern [3]. This pattern enables efficient demul- crete event handlers in a single thread of control. In our ex-
tiplexing of multiple types of events from multiple sources ample the Initiation Dispatcher is a Singleton [10]
within a single thread of control. since we only need one instance of it for the entire process.
The two main roles in the Reactive layer are:
8.2 Connection Layer
Event Handler: Specifies an interface consisting of hook
methods [7] that abstractly represent the event processing op- The Connection layer
erations that can be provided by an application. For instance, 1. Creates Service Handlers;
these hook methods signify events such as a new connec-
tion request, a completion of a connection request started 2. Passively or actively connects Service Handlers
asynchronously, or the arrival of data from a connected peer. to their remote peers; and
The Acceptor and Connector components are concrete 3. Activates Service Handlers once they are con-
event handlers that derive from Event Handler. nected.
6
All behavior in this layer is completely generic. In particu-
lar, note how the classes in the implementation described be- // Initialization method.
Connector (void);
low delegate to concrete IPC mechanisms and Concrete
Service Handlers, which are instantiated by the Appli- // Actively connecting and activate a service.
int connect (SERVICE_HANDLER *sh,
cation layer described in Section 8.3. const PEER_CONNECTOR::PEER_ADDR &addr,
The manner in which the Application layer delegates Connect_Mode mode);
to the Connection layer is similar to how the Connection
protected:
layer delegates to the Reactive layer. For instance, the // Defines the active connection strategy.
Initiation Dispatcher in the Reactive layer pro- virtual int connect_service_handler
(SERVICE_HANDLER *sh,
cesses initialization-related events, such as establishing con- const PEER_CONNECTOR::PEER_ADDR &addr,
nections asynchronously, on behalf of the Connection layer. Connect_Mode mode);
There are three primary roles in the Connection layer:
// Register the SERVICE_HANDLER so that it can
Service Handler, Acceptor, and Connector. // be activated when the connection completes.
int register_handler (SERVICE_HANDLER *sh,
Service Handler: This abstract class inherits from Connect_Mode mode);
Event Handler and provides a generic interface for pro-
cessing services provided by clients, servers, or components // Defines the handler’s concurrency strategy.
virtual int activate_service_handler
that perform both roles. Applications must customize this (SERVICE_HANDLER *sh);
class via inheritance to perform a particular type of service.
// Activate a SERVICE_HANDLER whose
The Service Handler interface is shown below: // non-blocking connection completed.
virtual int complete (HANDLE handle);
// PEER_STREAM is the type of the
// Concrete IPC mechanism. private:
template <class PEER_STREAM> // IPC mechanism that establishes
class Service_Handler : public Event_Handler // connections actively.
{ PEER_CONNECTOR connector_;
public:
// Pure virtual method (defined by a subclass). // Collection that maps HANDLEs
virtual int open (void) = 0; // to SERVICE_HANDLER *s.
Map_Manager<HANDLE, SERVICE_HANDLER *>
// Accessor method used by Acceptor and handler_map_;
// Connector to obtain the underlying stream.
PEER_STREAM &peer (void) { // Inherited from the Event_Handler -- will be
return peer_stream_; // called back by Eactor when events complete
} // asynchronously.
virtual int handle_event (HANDLE, EVENT_TYPE);
// Return the address that we’re connected to. };
PEER_STREAM::PEER_ADDR &remote_addr (void) {
return peer_stream_.remote_addr (); // Useful "short-hand" macros used below.
} #define SH SERVICE_HANDLER
#define PC PEER_CONNECTOR
protected:
// Concrete IPC mechanism instance.
PEER_STREAM peer_stream_; The Connector is parameterized by a particular type of
};
PEER CONNECTOR and SERVICE HANDLER. The PEER
Once the Acceptor or Connector establishes a con- CONNECTOR provides the transport mechanism used by the
nection, they call the open hook of a Service Handler. Connector to actively establish the connection, either syn-
This pure virtual method must be defined by a Concrete chronously or asynchronously. The SERVICE HANDLER
Service Handler subclass, which performs service- provides the service that processes data exchanged with its
specific initializations and subsequent processing. connected peer. C++ parameterized types are used to decou-
ple (1) the connection establishment strategy from (2) the
Connector: This abstract class implements the generic type of service handler, network programming interface, and
strategy for actively establishing connections and initializing transport layer connection protocol.
Service Handlers. The interface of the Connector Parameterized types are an implementation decision that
is shown below: help improve portability. For instance, they allow the
// The SERVICE_HANDLER is the type of service. wholesale replacement of the IPC mechanisms used by
// The PEER_CONNECTOR is the type of concrete the Connector. This makes the Connector’s connec-
// IPC active connection mechanism.
template <class SERVICE_HANDLER, tion establishment code portable across platforms that con-
class PEER_CONNECTOR> tain different network programming interfaces, e.g., sock-
class Connector : public Event_Handler ets but not TLI, or vice versa. For example, the PEER
{
public: CONNECTOR template argument can be instantiated with ei-
enum Connect_Mode { ther a SOCK Connector or a TLI Connector, depend-
SYNC, // Initiate connection synchronously.
ASYNC // Initiate connection asynchronously. ing on whether the platform supports sockets or TLI [11].
}; Another motivation for using parameterized types is to im-
7
prove run-time efficiency since template instantiation occurs If the value of the Connect Mode parameter is SYNC the
at compile-time. SERVICE HANDLER will be activated once the connection
An even more dynamic type of decoupling could be completes synchronously, as illustrated in Figure 7. This
achieved via inheritance and polymorphism by using the figure is similar to Figure 4, but provides additional imple-
Factory Method and Strategy patterns described in [10]. For mentation details, such as the use of the get handle and
instance, a Connector could store a pointer to a PEER handle event hook methods.
CONNECTOR base class. The connect method of this
PEER CONNECTOR could be dynamically bound at run-
sh:
con : : SOCK
SEVICE INITIALIZATION
Handler Dispatcher
returned from a Factory. In general, the tradeoff between
FOREACH CONNECTION connect(sh, addr, SYNC)
parameterized types and dynamic binding is that parameter-
INITIATE CONNECTION connect_service_handler(sh, addr)
PHASE
ized types can incur additional compile/link-time overhead, SYNC CONNECT connect()
whereas dynamic binding can incur additional run-time over- ACTIVATE OBJECT activate_service_handler(sh)
head. open()
The connect method is the entry point an application INSERT IN REACTOR register_handler(sh)
EXTRACT HANDLE get_handle()
uses to initiate a connection via a Connector. It’s imple-
mentation is shown below.3 START EVENT LOOP
handle_events()
PROCESSING
select()
SERVICE
PHASE
FOREACH EVENT DO
handle_event()
DATA ARRIVES
template <class SH, class PC> int
Connector<SH, PC>::connect PROCESS DATA service()
(SERVICE_HANDLER *service_handler,
const PEER_CONNECTOR::PEER_ADDR &addr,
Connect_Mode mode)
{ Figure 7: Collaborations Among the Connector Participants
connect_service_handler (service_handler, for Synchronous Connections
addr, mode);
}
To connect with multiple Peers efficiently, the
This method uses the Connector also may need to actively establish connec-
Bridge pattern [10] to allow Concrete Connectors to tions asynchronously, i.e., without blocking the caller. Asyn-
transparently modify the connection strategy, without chang- chronous behavior is specified by passing the ASYNC con-
ing the component interface. Therefore, the connect nection mode to Connector::connect, as illustrated in
method delegates to the Connector’s connection strategy, Figure 8. This figure is similar to Figure 5, but it also pro-
connect service handler, which initiates a connec-
tion as shown below:
con : : SOCK sh:
template <class SH, class PC> int Client Service : Initiation
Connector Connector
Connector<SH, PC>::connect_service_handler Handler Dispatcher
CONNECTION
INITIATE CONNECTION
Connect_Mode mode) connect_service_handler(sh, addr)
ASYNC CONNECT connect()
{ register_handler(con)
INSERT IN DISPATCHER
// Delegate to concrete PEER_CONNECTOR
// to establish the connection. handle_events()
START EVENT LOOP
INITIALIZATION
handle_event()
PHASE
8
synchronously or asynchronously. The implementation Connector<SH, PC>::activate_service_handler
of the Connector pattern shown here uses asynchronous (SERVICE_HANDLER *service_handler)
{
connection mechanisms provided by the OS and com- service_handler->open ();
munication protocol stack. For instance, on UNIX or }
Win32 the Connector can set sockets into non-blocking
mode and use an event demultiplexer like select or The Service Handler’s open hook is called when
WaitForMultipleObjects to determine when the con- a connection is established successfully. Note that it is
nection completes. called regardless of whether (1) connections are initiated
To handle asynchronous connections that are still pending synchronously or asynchronously or (2) they are connected
completion, the Connector maintains a map of Service actively or passively. This uniformity makes it possible to
Handlers. Since the Connector inherits from Event write Service Handlers whose processing can be com-
Handler, the Initiation Dispatcher can automat- pletely decoupled from how they are connected and initial-
ically call back to the Connector’s handle event ized.
method when a connection completes. Acceptor: This abstract class implements the generic strat-
The handle event method is an Adapter [10] that egy for passively establishing connections and initializing
transforms the Initiation Dispatcher’s event han- Service Handlers. The interface of the Acceptor is
dling interface to a call to the Connector pattern’s shown below.
complete method.
// The SERVICE_HANDLER is the type of service.
The Connector’s handle event method is shown // The PEER_ACCEPTOR is the type of concrete
below: // IPC passive connection mechanism.
template <class SERVICE_HANDLER,
template <class SH, class PC> int class PEER_ACCEPTOR>
Connector<SH, PC>::handle_event (HANDLE handle, class Acceptor : public Event_Handler
EVENT_TYPE type) {
{ public:
// Adapt the Initiation_Dispatcher’s event // Initialize local_addr transport endpoint factory
// handling API to the Connector’s API. // and register with Initiation_Dispatcher Singleton.
complete (handle); virtual int open
} (const PEER_ACCEPTOR::PEER_ADDR &local_addr);
9
SERVICE HANDLER provides the service that processes Demultiplexer, such as select, is then used to de-
data exchanged with its remote peer. Note that the SERVICE tect and demultiplex incoming connection requests from
HANDLER is a concrete service handler that is provided by clients. Since the Acceptor class inherits from Event
the application layer. Handler, the Initiation Dispatcher can auto-
Parameterized types decouple the Acceptor’s connec- matically call back to the Acceptor’s handle event
tion establishment strategy from the type of service handler, method when a connection arrives from a peer. This
network programming interface, and transport layer connec- method is an Adapter that transforms the Initiation
tion initiation protocol. As with the Connector, the use Dispatcher’s event handling interface to a call to the
of parameterized types helps improve portability by allow- Acceptor’s accept method, as follows:
ing the wholesale replacement of the mechanisms used by
template <class SH, class PA> int
the Acceptor. This makes the connection establishment code Acceptor<SH, PA>::handle_event (HANDLE,
portable across platforms that contain different network pro- EVENT_TYPE)
gramming interfaces, such as sockets but not TLI, or vice {
// Adapt the Initiation_Dispatcher’s event handling
versa. For example, the PEER ACCEPTOR template argu- // API to the Acceptor’s API.
ment can be instantiated with either a SOCK Acceptor or accept ();
}
a TLI Acceptor, depending on whether the platform sup-
ports sockets or TLI more efficiently.
As shown below, the accept method is a Template
The implementation of the Acceptor’s methods is pre-
Method [10] that implements the Acceptor-Connector pat-
sented below. Applications initiatize an Acceptor by call-
tern’s passive initialization strategy for creating a new
ing its open method, as follows:
SERVICE HANDLER, accepting a connection into it, and
template <class SH, class PA> int activating the service:
Acceptor<SH, PA>::open
(const PEER_ACCEPTOR::PEER_ADDR &local_addr) template <class SH, class PA> int
{ Acceptor<SH, PA>::accept (void)
// Forward initialization to the PEER_ACCEPTOR. {
peer_acceptor_.open (local_addr); // Create a new SERVICE_HANDLER.
SH *service_handler = make_service_handler ();
// Register with Initiation_Dispatcher, which
// ‘‘double-dispatches’’ without get_handle() // Accept connection from client.
// method to extract the HANDLE. accept_service_handler (service_handler);
Initiation_Dispatcher::instance
()->register_handler (this, READ_MASK); // Activate SERVICE_HANDLER by calling
} // its open() hook.
activate_service_handler (service_handler);
The open method is passed a local addr. This parame- }
ter contains a network address, e.g., the local host’s IP name
and TCP port number, used to listen for connections. It This method is very concise since it factors all low-level
forwards this address to the passive connection acceptance details into the concrete SERVICE HANDLER and PEER
mechanism defined by the PEER ACCEPTOR. This mecha- ACCEPTOR, which are instantiated via parameterized types
nism initializes the transport endpoint factory, which adver- and can be customized by subclasses of the Acceptor. In
tises its address to clients who are interested in connecting particular, since the accept is a Template Method, sub-
with the Acceptor. classes can extend any or all of the Acceptor’s connec-
The behavior of the transport endpoint factory is deter- tion establishment and initialization strategies. This flexibil-
mined by the type of PEER ACCEPTOR instantiated by a ity makes it possible to write Service Handlers whose
user. For instance, it can be a C++ wrapper [12] for sockets behavior is decoupled from the manner in which they are
[13], TLI [14], STREAM pipes [15], Win32 Named Pipes, passively connected and initialized.
etc. The make service handler factory method defines
After the transport endpoint factory has been initialized, the default strategy an Acceptor uses to create SERVICE
the open method registers itself with the Initiation HANDLERs, as follows:
Dispatcher. The Initiation Dispatcher per- template <class SH, class PA> SH *
forms a “double dispatch” back to the Acceptor’s Acceptor<SH, PA>::make_service_handler (void)
get handle method to obtain the underlying transport {
return new SH;
endpoint factory HANDLE, as follows: }
template <class SH, class PA> HANDLE
Acceptor<SH, PA>::get_handle (void) The default behavior uses a “demand strategy,” which cre-
{ ates a new SERVICE HANDLER for every new connection.
return peer_acceptor_.get_handle ();
} However, subclasses of Acceptor can override this strat-
egy to create SERVICE HANDLERs using other strategies,
The Initiation Dispatcher stores this HANDLE such as creating an individual Singleton [10] or dynamically
internally in a table. A Synchronous Event linking the SERVICE HANDLER from a shared library.
10
The SERVICE HANDLER connection acceptance strat- SOCK Stream classes used in Section 9 are part of the
egy used by the Acceptor is defined below by the ACE C++ socket wrapper library [11]. These wrappers
accept service handler method: encapsulate the stream-oriented semantics of connection-
template <class SH, class PA> int oriented protocols like TCP and SPX with efficient, portable,
Acceptor<SH, PA>::accept_service_handler and type-safe C++ wrappers.
(SH *handler) The three main roles in the Application layer are described
{
peer_acceptor_->accept (handler->peer ()); below.
}
Concrete Service Handler: This class defines the con-
The default behavior delegates to the accept method pro- crete application service activated by a Concrete
vided by the PEER ACCEPTOR. Subclasses can override the Acceptor or a Concrete Connector. A Concrete
accept service handler method to perform more so- Service Handler is instantiated with a specific type of
phisticated behavior such as authenticating the identity of the C++ IPC wrapper that exchanges data with its connected
client to determine whether to accept or reject the connec- peer.
tion. Concrete Connector: This class instantiates the generic
The Acceptor’s SERVICE HANDLER concurrency Connector factory with concrete parameterized type argu-
strategy is defined by the activate service handler ments for SERVICE HANDLER and PEER CONNECTOR.
method:
Concrete Acceptor: This class instantiates the generic
template <class SH, class PA> int Acceptor factory with concrete parameterized type argu-
Acceptor<SH, PA>::activate_service_handler
(SH *handler) ments for SERVICE HANDLER and PEER ACCEPTOR.
{ Concrete Service Handlers can also define a ser-
handler->open ();
}
vice’s concurrency strategy. For example, a Service
Handler may inherit from the Event Handler and em-
The default behavior of this method is to activate the ploy the Reactor [3] pattern to process data from peers in a
SERVICE HANDLER by calling its open hook. This single-thread of control. Conversely, a Service Handler
allows the SERVICE HANDLER to choose its own might use the Active Object pattern [5] to process incoming
concurrency strategy. For instance, if the SERVICE data in a different thread of control than the one used by the
HANDLER inherits from Event Handler it can regis- Acceptor to connect it. Below, we implement Concrete
ter with the Initiation Dispatcher. This allows Service Handlers for our Gateway example, illus-
the Initiation Dispatcher to dispatch the SERVICE trates how several different concurrency strategies can be
HANDLER’s handle event method when events oc- configured flexibly without affecting the structure or behav-
cur on its PEER STREAM endpoint of communication. ior of the Acceptor-Connector pattern.
Concrete Acceptors can override this strategy to do
In the sample code in Section 9, SOCK Connector and
more sophisticated concurrency activations. For instance,
SOCK Acceptor are the IPC mechanisms used to estab-
a subclass could make the SERVICE HANDLER an Active
lish connections actively and passively, respectively. Like-
Object [5] that processes data using multi-threading or multi-
wise, a SOCK Stream is used as the data transport deliv-
processing.
ery mechanism. However, parameterizing the Connector
When an Acceptor terminates, either due to er-
and Acceptor with different mechanisms (such as a TLI
rors or due to the entire application shutting down,
Connector or Named Pipe Acceptor) is straightfor-
the Initiation Dispatcher calls the Acceptor’s
ward since the IPC mechanisms are encapsulated in C++
handle close method, which can release any dynami-
wrapper classes. Likewise, it is easy to vary the data trans-
cally acquired resources. In this case, the handle close
fer mechanism by parameterizing the Concrete Service
method simply forwards the close request to the PEER
Handler with a different PEER STREAM, such as an SVR4
ACCEPTOR’s transport endpoint factory, as follows:
UNIX TLI Stream or a Win32 Named Pipe Stream.
template <class SH, class PA> int Section 9 illustrates how to instantiate a Concrete
Acceptor<SH, PA>::handle_close (void)
{ Service Handler, Concrete Connector, and
peer_acceptor_.close (); Concrete Acceptor that implement the Peers and
} Gateway described in Section 2. This particular example
of the Application layer customizes the generic initializa-
8.3 Application Layer tion strategies provided by the Connector and Acceptor
components in the Connection layer.
The Application Layer supplies concrete interprocess com-
munication (IPC) mechanisms and a concrete Service
Handler. IPC mechanisms are encapsulated in C++ 9 Example Resolved
classes to simplify programming, enhance reuse, and to en-
able wholesale replacement of IPC mechanisms. For ex- The code below illustrates how the Peers and Gateway
ample, the SOCK Acceptor, SOCK Connector, and described in Section 2 can use the Acceptor-Connector pat-
11
terns to simplify connection establishment and service ini- Handler class processes status data sent to and received
tialization. Section 9.1 illustrates how the Peers play a from a Gateway:
passive role and Section 9.2 illustrates how the Gateway
class Status_Handler : public PEER_HANDLER
plays an active role in establishing connections with the pas- {
sive Peers. public:
// Performs handler activation.
virtual int open (void) {
9.1 Concrete Components for Peers // Make handler run in separate thread (note
// that Thread::spawn requires a pointer to
// a static method as the thread entry point).
Figure 9 illustrates how the Concrete Acceptor and
Concrete Service Handler components are struc- Thread::spawn (&Status_Handler::service_run,
tured in a Peer. The Acceptor components in this figure this);
}
SOCK_Stream Bulk_Data_Handler
SOCK_Acceptor
continue;
LAYER
// ...
PEER_STREAM
LAYER
Service SERVICE_HANDLER
PEER_ACCEPTOR
};
Handler
Acceptor The PEER HANDLER also can be subclassed to produce con-
crete service handlers that process bulk data and commands.
Figure 9: Structure of Acceptor Participants for Peers For instance, the Bulk Data Handler class processes
bulk data sent to and received from the Gateway.
complement the Connector components in Figure 11. class Bulk_Data_Handler : public PEER_HANDLER
{
public:
Service Handlers for Communicating with a Gateway: // Performs handler activation.
The classes shown below, Status Handler, Bulk virtual int open (void) {
// Handler runs in separate process.
Data Handler, and Command Handler, process rout- if (fork () == 0) // In child process.
ing messages sent to and received from a Gateway. // This method can block since it
Since these Concrete Service Handler classes in- // runs in its own process.
while (handle_event () != -1)
herit from Service Handler they are capable of being continue;
initialized passively by an Acceptor. // ...
}
To illustrate the flexibility of the Acceptor-Connector pat-
tern, each open routine in the Service Handlers can // Receive and process bulk data from Gateway.
implement a different concurrency strategy. In particular, virtual int handle_event (void) {
char buf[MAX_BULK_DATA];
when the Status Handler is activated it runs in a sep- stream_.recv (buf, sizeof buf);
arate thread; the Bulk Data Handler runs as a sep- // ...
}
arate process; and the Command Handler runs in the
same thread as the Initiation Dispatcher that de- // ...
multiplexes connection requests for the Acceptor facto- };
ries. Note how changes to these concurrency strategies do
The Command Handler class processes bulk data sent to
not affect the implementation of the Acceptor, which is
and received from a Gateway:
generic and thus highly flexible and reusable.
We start by defining a Service Handler that uses class Command_Handler : public PEER_HANDLER
SOCK Stream for socket-based data transfer: {
public:
typedef Service_Handler <SOCK_Stream> // Performs handler activation.
PEER_HANDLER; virtual int open (void) {
// Handler runs in same thread as main
// Initiation_Dispatcher singleton.
The PEER HANDLER typedef forms the basis for all the Initiation_Dispatcher::instance
subsequent service handles. For instance, the Status ()->register_handler (this, READ_MASK);
12
} Gateway. When connections arrive, the Initiation
Dispatcher calls back to the appropriate Acceptor,
// Receive and process command data from Gateway.
virtual int handle_event (void) { which creates the appropriate PEER HANDLER to perform
char buf[MAX_COMMAND_DATA]; the service, accepts the connection into the handler, and ac-
// This method cannot block since it borrows
// the thread of control from the tivates the handler.
// Initiation_Dispatcher. Figure 10 illustrates the relationship between Concrete
stream_.recv (buf, sizeof buf); Acceptor components in the Peer after four connections
// ...
} have been established with the Gateway shown in Figure 12
and four Service Handler have been created and ac-
//...
}; tivated. While the Concrete Service Handlers ex-
13
The PEER ROUTER can be subclassed to produce concrete
Command_Router
SOCK_Stream SOCK_Connector service handlers for routing bulk data and commands. For
Command n 1 Command instance, the Bulk Data Router routes bulk data to or
Router ACTIVATES Connector from Peers:
APPLICATION
Bulk_Data_Router
SOCK_Stream
SOCK_Connector
LAYER
PEER_STREAM // ...
SERVICE_HANDLER
LAYER
Service PEER_CONNECTOR }
Handler Connector // Receive and route bulk data from/to Peers.
virtual int handle_event (void) {
char buf[MAX_BULK_DATA];
peer_stream_.recv (buf, sizeof buf);
REACTIVE
Event }
Handler n 1
Initiation
Dispatcher };
Figure 11: Structure of Connector Participants for the The Command Router class routes Command data to or
Gateway from Peers:
14
The Gateway main function: The main program for the
Gateway is shown below. The get peer addrs func- : Bulk Data : Command
tion creates the Status, Bulk Data, and Command
Routers that route messages through the Gateway. This : Service : Service
Handler Handler
function (whose implementation is not shown) reads a list of : Status
: Status : Status
Peer addresses from a configuration file or naming service. : Service : Status
Each Peer address consists of an IP address and a port num- Handler : Service
: Service Handler : Command
ber. Once the Routers are initialized, the Connector Handler : Service
Handler : Service
factories defined above initiate all the connections asyn-
P
E NDING Handler
chronously by passing the ASYNC flag to the connect CONNECTIONS
method. ACTIVE
HANDLERS : Bulk Data
// Main program for the Gateway. : Connector
: Service
// Obtain an STL vector of Status_Routers,
// Bulk_Data_Routers, and Command_Routers
: Initiation Handler
// from a config file. Dispatcher
// A vector of PEER_ROUTERs that perform established, the Gateway will route and forward messages
// the Gateway’s routing services. sent to it by Peers.
vector<PEER_ROUTER> peers;
15
Ericsson EOS Call Center Management System: this Efficiently utilize the inherent parallelism in the network
system uses the Acceptor-Connector pattern to allow and hosts: By using the asynchronous mechanisms shown
application-level Call Center Manager Event Servers [19] to in Figure 8, the Connector pattern can actively establish con-
actively establish connections with passive Supervisors in a nections with a large number of peers efficiently over long-
distributed center management system. latency WANs. This is an important property since a large
distributed system may have several hundred Peers con-
Project Spectrum: The high-speed medical image trans- nected to a single Gateway. One way to connect all these
fer subsystem of project Spectrum [20] uses the Acceptor- Peers to the Gateway is to use the synchronous mecha-
Connector pattern to passively establish connections and ini- nisms shown in Figure 7. However, the round trip delay for a
tialize application services for storing large medical images. 3-way TCP connection handshake over a long-latency WAN
Once connections are established, applications then send and (such as a geosynchronous satellite or trans-atlantic fiber ca-
receive multi-megabyte medical images to and from these ble) may take several seconds per handshake. In this case,
image stores. synchronous connection mechanisms cause unnecessary de-
lays since the inherent parallelism of the network and com-
ACE Framework: Implementations of the Service puters is underutilized.
Handler, Connector, and Acceptor classes described
in this paper are provided as reusable components in the ACE
object-oriented network programming framework [6]. 11.2 Drawbacks
The Acceptor-Connector pattern has the following draw-
backs:
11 Consequences
Additional indirection: The Acceptor-Connector pattern
11.1 Benefits can incur additional indirection compared with using the un-
derlying network programming interfaces directly. However,
The Acceptor-Connector pattern provides the following ben- languages that support parameterized types (such as C++,
efits: Ada, or Eiffel), can implement these patterns with no signif-
icant overhead since compilers can inline the method calls
Enhances the reusability, portability, and extensibility of used to implement these patterns.
connection-oriented software by decoupling mechanisms
for initializing services from subsequent service process- Additional complexity: This pattern may add unneces-
ing. For instance, the application-independent mechanisms sary complexity for simple client applications that connect
in the Acceptor and Connector are reusable compo- with a single server and perform a single service using a sin-
nents that know how to (1) establish connections passively gle network programming interface.
and actively, respectively and (2) initialize the associated
Service Handler once the connection is established. In
contrast, the Service Handler knows how to perform 12 See Also
application-specific service processing.
This separation of concerns is achieved by decoupling The Acceptor-Connector pattern use the Template Method
the initialization strategy from the service handling strategy. and Factory Method patterns [10]. The Acceptor’s
Thus, each strategy can evolve independently. The strategy accept and the Connector’s connect and complete
for active initialization can be written once, placed into a functions are Template Methods that implements a generic
class library or framework, and reused via inheritance, ob- service strategy for connecting to remote peers and initial-
ject composition, or template instantiation. Thus, the same izing a Service Handler when the connection is estab-
passive initialization code need not be rewritten for each lished. The use of the Template Method pattern allows sub-
application. Services, in contrast, may vary according to classes to modify the specific details of creating, connect-
different application requirements. By parameterizing the ing, and activating Concrete Service Handlers. The
Acceptor and Connector with a Service Handler, Factory Method pattern is used to decouple the creation of a
the impact of this variation is localized to a small number of Service Handler from its subsequent use.
components in the software. The Acceptor-Connector pattern has an intent similar
to the Client-Dispatcher-Server pattern described in [21].
Improves application robustness: Application robust- They both are concerned with separating active connec-
ness is improved by strongly decoupling the Service tion establishment from the subsequent service. The pri-
Handler from the Acceptor. This decoupling en- mary difference is that the Acceptor-Connector pattern ad-
sures that the passive-mode transport endpoint factory dresses passive and active service initialization for both syn-
peer acceptor cannot accidentally be used to read or chronous and asynchronous connections, whereas the Client-
write data. This eliminates a common class of errors that can Dispatcher-Server pattern focuses on synchronous connec-
arise when programming with weakly typed network pro- tion establishment.
gramming interfaces such as sockets or TLI [11].
16
Acknowledgements [18] D. C. Schmidt, D. L. Levine, and S. Mungee, “The Design and
Performance of Real-Time Object Request Brokers,” Com-
Thanks to Frank Buschmann and Hans Rohnert for their puter Communications, vol. 21, pp. 294–324, Apr. 1998.
helpful comments on this paper. [19] D. C. Schmidt and T. Suda, “An Object-Oriented Framework
for Dynamically Configuring Extensible Distributed Commu-
nication Systems,” IEE/BCS Distributed Systems Engineering
References Journal (Special Issue on Configurable Distributed Systems),
vol. 2, pp. 280–293, December 1994.
[1] W. R. Stevens, TCP/IP Illustrated, Volume 1. Reading, Mas- [20] G. Blaine, M. Boyd, and S. Crider, “Project Spectrum: Scal-
sachusetts: Addison Wesley, 1993. able Bandwidth for the BJC Health System,” HIMSS, Health
[2] G. Booch, Object Oriented Analysis and Design with Ap- Care Communications, pp. 71–81, 1994.
plications (2nd Edition). Redwood City, California: Ben- [21] F. Buschmann, R. Meunier, H. Rohnert, P. Sommerlad, and
jamin/Cummings, 1993. M. Stal, Pattern-Oriented Software Architecture - A System of
[3] D. C. Schmidt, “Reactor: An Object Behavioral Pattern for Patterns. Wiley and Sons, 1996.
Concurrent Event Demultiplexing and Event Handler Dis-
patching,” 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, “Proac-
tor – An Object Behavioral Pattern for Dispatching Asyn-
chronous 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, Mas-
sachusetts), USENIX Association, April 1994.
[7] W. Pree, Design Patterns for Object-Oriented Software De-
velopment. 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: Mi-
crosoft Press, 1993.
[10] E. Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Pat-
terns: Elements of Reusable Object-Oriented Software. Read-
ing, MA: Addison-Wesley, 1995.
[11] D. C. Schmidt, T. H. Harrison, and E. Al-Shaer, “Object-
Oriented Components for High-speed Network Program-
ming,” 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 Communica-
tion in the Ninth Edition UNIX System,” UNIX Research Sys-
tem 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 De-
velop Extensible ORB Middleware,” Submitted to the IEEE
Communications Magazine, 1998.
17