Table of Contents
Object-Oriented Network
Overview
Programming with C++
{ Recognizing patterns
{ Typ es of patterns
Design Patterns for Initializing
Passively initiali zi ng network services
Network Services in OO
{ Overview of tactical patterns
Communication Frameworks
{ Overview of strategic patterns
Implementing the Acceptor pattern
Douglas C. Schmidt
{ First implementation
{ Revised implementation
Washington University
Summary
2 1
Identifying Common Behavior and
Structure
Intro duction
\Pattern recognition" o ccurs by observing
Design patterns capture successful solutions
recurring solution strategies
to recurring problems that arise during soft-
ware development
e.g., network servers often lo ok as follows:
Useful design patterns and design pattern
void do_server void {
descriptions, like useful software, evolve over
listener_handle.initialize ;
time
for ;; {
{ Likewise, patterns build up on other patterns to
// Create service to handle connection.
form families of related patterns
service = new Service;
// Wait for and accept connection.
client_handle = listener_handle.accept ;
The following material captures the evolu-
// Activate service.
tion of design patterns that are useful for
service->open client_handle;
managing the lifecycle of services in OO
// Perform service.
communication software
service->run ;
}
}
3 4
Identifying Limitations with the
Strategic and Tactical Patterns
Existing Solution
Strategic design patterns have an extensive
impact on the software architecture
The solution ab ove is limit ed by tightly cou-
pling:
{ Typically oriented to solutions in aparticular do-
main
{ Choice of IPC mechanisms
{ Connection and service handling strategies
{ e.g., Reactor and Acceptor pattern in the domain
of event-driven, connection-oriented communica-
{ Choice of demultiplexing strategy
tion software
{ Choice of advertisement strategy
{ Choice of endp oint listening strategy
Tactical design patterns have a relatively lo-
calized impact on a software architecture
{ Choice of service creation strategy
{ Typically domain-indep endent
{ Choice of connection acceptance strategy
{ e.g.,Wrapp er, Adapter, Bridge, Factory Metho d,
{ Choice of service concurrency strategy
and Strategy
Strategic and tactical design patterns re-
It is imp ortant to understand b oth typ es of
move these limitati ons and decrease cou-
patterns
pling
6 5
Decoupling IPC Mechanisms
Problem
The Wrapp er Pattern
{ IPC mechanisms likesockets, TLI, and STREAM
pip es have non-uniform interfaces, even though
they b ehavior similarly
Intent
{ Encapsulate lower-level functions within typ e-safe,
mo dular, and p ortable class interfaces
Key forces
{ Many IPC mechanisms or subsets of mechanisms
provide logically equivalent services even though
This pattern resolves the following force that
their interfaces are physically di erent
arises when using native C-level OS APIs
. e.g., TLI vs. so ckets
1. How to avoid tedious, error-prone, and non-p ortable
programming of low-level IPC mechanisms
{ Many IPC mechanisms are written in low-level C
co de
2. How to combine multiple related, but indep endent,
functions into a single cohesive abstraction
Solution
{ Use the Wrapp er and Adapter patterns
7 8
Structure of the Wrapp er Pattern
The Adapter Pattern Intent 1: request ()
Wrapper Convert the interface of a class into another inter- client {
request() face client exp ects
. Adapter lets classes work together that couldn't
otherwise b ecause of incompatible interfaces
2: specific_request()
This pattern resolves the following force that
rises when using conventional OS inter-
Wrappee a faces
specific_request()
1. `How to provide an interface that expresses the
similarities of seemingly di erent OS mechanisms
such as networking or lo cking
9 10
Decoupling Demultip lexi ng
Strategy
Structure of the Adapter Pattern
Problem
Servers often must handle events from more than 1: request () { Target one source client request() = 0
A
Key forces
{ It is inecient to rep eatedly \p oll" each event
source and it is incorrect to blo ck inde nitely on
Adapter Adaptee any one source of events specific_request()
request() 2: specific_request()
{ Extensibility is limited if the demultiplexing co de is
coupled with the application event handling co de
Solution
{ Use the Reactor pattern
12 11
Structure
The Reactor Pattern
select (handles) Concrete foreach h in handles loop
APPLICATION Event Intent h->handle_event (type) APPLICATION
end loop Handler
Decouple event demultiplexing and event handler { - SPECIFIC
- rmed in resp onse
dispatching from the services p erfo INDEPENDENT to events Reactor handle_events() 1 n
register_handler(eh, type)
This pattern resolves the following forces
r event-driven software: fo remove_handler(eh, type) Event Handler 1
1 handle_event(type)
How to demultiplex multiple typ es of events from 1. 1
multiple sources of events eciently within a single get_handle()
thread of control n Handles A
2. How to extend application b ehavior without requir-
ing changes to the event demultiplexing/dispatching
framework
Participants in the Reactor pattern
14 13
Collab oratio ns
Decoupling Connection and
Handling Strategies callback : Service main Concrete reactor
program Event_Handler : Reactor Problem Reactor() INITIALIZE
register_handler(callback)
Coupling initialization strategies with the service REGISTER HANDLER {
MODE
es it hard to change either one with- get_handle() handling mak EXTRACT HANDLE handle_events() out a ecting the other INITIALIZATION START EVENT LOOP select() FOREACH EVENT DO
handle_event()
Key forces EVENT OCCURS
MODE
EVENT
Services tend to change more often than connec- REMOVE HANDLER handle_close() {
HANDLING
tion establishment strategies
Solution
Dynamic interaction among participants in
{ Use the Acceptor pattern
the Reactor pattern
16 15
The Acceptor Pattern
Intent
Structure of the Acceptor Pattern
{ Decouple the passive initialization of a service from
the tasks p erformed once the service is initialized
Svc Handler
This pattern resolves the following forces
Svc Handler Acceptor
r network servers using interfaces like so ck-
fo peer_stream_
or TLI: ets peer_acceptor_
open() ACTIVATES handle_event()
1. How to reuse passive connection establishment co de
for each new service
Reactor
2. How to make the connection establishment co de
rtable across platforms that may contain di er-
po handle_event()
ent IPC mechanisms
3. How to ensure that a passive-mo de descriptor is
not accidentally used to read or write data
4. How to enable exible strategies for creation, con-
nection establishment, and concurrency
18 17
Structure of the First Acceptor
Pattern Implementati on
First Implementati on of the
Concrete_Svc_Handler SOCK Stream
n SOCK_Acceptor
r Pattern Accepto 1 Concrete Concrete Svc Handler Acceptor LAYER
LAYER
The following slides illustrate an implemen- ACTIVATES APPLICATION
APPLICATION
tation of the Acceptor pattern
{ This solution relies up on an implementation of the
PEER_STREAM
r pattern, as well as the Adapter pattern Reacto Svc SVC_HANDLER A PEER_ACCEPTOR Handler Acceptor A open()
handle_event()
Subsequent slides will describ e limitati ons LAYER LAYER CONNECTION
CONNECTION
with this rst approach sh = new SVC_HANDLER; peer_acceptor.accept(*sh);
reactor->register_handler (sh);
Other tactical patterns will be used to re-
Event
these limitati ons move Handler 1 Reactor LAYER LAYER handle_event() n REACTIVE
REACTIVE
19 20
Typical Acceptor Use-Case
Acceptor Class Interface
SERVER
A factory for initial i zi ng network services LOGGING : Logging
passively DAEMON Acceptor
template : Logging PEER_ACCEPTOR> // Accepts connections class Handler class Acceptor : public Event_Handler { public: : Logging Initialize and register with Reactor. // Handler : Reactor virtual int open const PEER_ACCEPTOR::PEER_ADDR &, Reactor *; // Creates, accepts, and activates SVC_HANDLER's. LOGGING virtual int handle_event HANDLE; RECORDS Demultiplexing hooks. // LOGGING CONNECTION SERVER HANDLE get_handle void const; virtual RECORDS REQUEST CLIENT private: // Passive connection mechanism. EPTOR peer_acceptor_; PEER_ACC CLIENT CLIENT // Event demultiplexor. Reactor *reactor_; }; 22 21 Acceptor Use-case Co de Distributed logging server Acceptor Class Implementation // Shorthand names. class Logging_Handler : define SH SVC_HANDLER public Svc_Handler define PR_AC PEER_ACCEPTOR { public: // Initialize the Acceptor // Obtain HANDLE. virtual HANDLE get_handle void const; template Acceptor // Called back to process logging records. Reactor *reactor virtual int handle_event HANDLE; { }; this->reactor_ = reactor; typedef Acceptor // Forward to PEER_ACCEPTOR to advertise endpoint. Logging_Acceptor; this->peer_acceptor_.open l_addr; int main void { // Register with Reactor to listen for connections. Reactor reactor; Logging_Acceptor acceptor; this->reactor_->register_handler this, READ_MASK; acceptor.open LOGGER_PORT, &reactor; } for ;; reactor.handle_events ; } 23 24 Collab orati on in the Acceptor Pattern acc : Server sh: reactor : Acceptor Svc_Handler Reactor Factory that create, connects, and activates new // open() INITIALIZE single-threaded SVC_HANDLER objects. // register_handler(acc) REGISTER HANDLER get_handle() template EXTRACT HANDLE ENDPOINT { FOREACH EVENT DO select() INITIALIZATION Create a new SVC_HANDLER. // handle_input() CONNECTION EVENT sh = new SVC_HANDLER LER *svc_handler = new SVC_HANDLER; SVC_HAND ALLOCATE AND accept(*sh) ACTIVATE OBJECT register_handler(sh) Accept connections from clients // REGISTER HANDLER PHASE FOR CLIENT I/O SERVICE get_handle() EXTRACT HANDLE er_acceptor_.accept *svc_handler; this->pe INITIALIZATION handle_input() DATA EVENT // Register SVC_HANDLER with Reactor. PROCESS MSG svc() handle_close() actor_->register_handler this->re CLIENT SHUTDOWN PHASE SERVICE ler, READ_MASK; svc_hand SERVER SHUTDOWN handle_close() PROCESSING } Acceptor factory creates, connects, and ac- tivates a Svc Handler 26 25 Limitations with the First Overcoming Limitations with Solution Patterns Several problems with the solution ab ove: To remove the limit ati ons describ ed ab ove, 1. Advertisement and listener strategies are hardco ded we'll use various patterns to enhance our Acceptor 2. Service handler creation strategy is tightly cou- pled to dynamic allo cation and constructor-based initialization Key patterns are Strategy, Bridge, Factory Metho d, and Abstract Factory 3. Connection acceptance strategy is hard-co ded { These are \tactical" patterns that are widely ap- plicable across most application domains 4. Service handler activation is not extensible and concurrency strategy is overly restrictive Note that the new solution do es not break existing co de This tight coupling makes it hard to extend { This is another imp ortant non-functional \force"::: the Acceptor to work in other contexts 27 28 Structure of the Strategy Pattern The Strategy Pattern Intent STRATEGY Context Strategy { De ne a family of algorithms, encapsulate each context_interface() algorithm_interface() one, and make them interchangeable A . Strategy lets the algorithm vary indep endently from clients that use it Concrete Concrete Strategy A Concrete Strategy C This pattern resolves the following force algorithm_interface() Strategy B algorithm_interface() How to extend the p olicies for adversizing, listen- 1. algorithm_interface() ing, creating, accepting, and executing a service hander without mo difying the core Acceptor algo- rithm 29 30 The Bridge Pattern Using the Strategy Pattern Intent SVC_HANDLER PEER_ACCEPTOR SVC_HANDLER Decouple an abstraction from its implementation Acceptor { wo can vary indep endently Concurrency so that the t handle_event() Strategy 2: activate_svc_handler(sh) activate_svc_handler() A This pattern resolves the following force that ... rises when building extensible software 1: activate_svc_handler(sh) a ... SVC_HANDLER Howtoprovide a stable, uniform interface that is SVC_HANDLER SVC_HANDLER Process 1. , i.e., Reactive Thread Strategy b oth closed and op en Strategy Strategy { Closed to prevent direct co de changes { Op en to allow extensibility 32 31 Using the Bridge Pattern Structure of the Bridge Pattern 3: accept_svc_handler(sh) SVC_HANDLER A Acceptor PEER_ACCEPTOR 1: method_impl() accept_svc_handler() Accept Implementor accept_strategy_ Strategy Abstraction handle_event() method_impl() accept_svc_handler() method() A A A ... 2: accept_strategy_->accept_svc_handler(sh) SVC_HANDLER Concrete ... PEER_ACCEPTOR ImplementorA CLNS Strategy method_impl() Concrete 1: accept_svc_handler() accept_svc_handler() Refined ImplementorB Abstraction SVC_HANDLER method_impl() PEER_ACCEPTOR CONS Strategy accept_svc_handler() 33 34 The Template Metho d The Factory Metho d Pattern Intent { De ne the skeleton of an algorithm in an op era- tion, deferring some steps to sub classes Intent . Template Metho d lets sub classes rede ne cer- { De ne an interface for creating an object, but let tain steps of an algorithm without changing the sub classes decide which class to instantiate algorithm's structure . Factory Metho d lets a class defer instantiation to sub classes This pattern resolves the following force 1. How to extend the strategies for creating the check sort This pattern resolves the following force in algorithm the Acceptor pattern: 1. How to extend each initialization strategy in the Acceptor pattern indep endently and transparently Template Metho d is an alternative for the Strategy/Bridge patterns 36 35 Using the Factory Metho d Structure of the Factory Metho d Pattern Pattern SVC_HANDLER Creator Creation A factory_method() = 0 Strategy make_product() A Svc make_svc_handler() Product A Handler A Product *product = factory_method() return product Concrete Svc Handler Concrete Demand Creator Strategy factory_method() Concrete Concrete Svc Handler make_svc_handler() Product CREATES return new Concrete_Product CREATES return new Concrete_Svc_Handler 37 38 Structure of the Abstract Factory Pattern The Abstract Factory Pattern Intent Abstract Concrete Factory Factory_2 Provide an interface for creating families of related { make_product_A() make_product_A() r dep endent objects without sp ecifying their con- o Abstract make_product_B() make_product_B() crete classes Product_A A A Product_A2 This problem resolves the following forces Product_A1 in the Acceptor pattern: Product_B2 1. How to simplify the interface to the Acceptor and eep it from having a large numb er of individual k Concrete strategies Factory_1 Product_B1 make_product_A() Abstract make_product_B() Product_B 2. How to ensure that the selected strategies actually A work together without con ict 39 40 Using the Abstract Factory Pattern Revised Acceptor Class Mo del SVC_HANDLER PEER_ACCEPTOR SVC_HANDLER sh = make_svc_handler(); PEER_ACCEPTOR Strategy 2: accept_svc_handler (sh); Factory Logging_Handler activate_svc_handler (sh); Acceptor 1: advertise_svc() SOCK Acceptor open() make_listener() handle_event() make_creation_strategy() SVC_HANDLER Logging advertise_svc() make_concurrency_strategy() Creation make_svc_handler() ... Acceptor A Strategy activate_svc_handler() accept_svc_handler() Advertise make_svc_handler() make_listener() Strategy A ... advertise_svc() SVC_HANDLER A SVC_HANDLER PEER_ACCEPTOR Logging_Handler Dynamic Concurrency Listener SOCK Acceptor Accept Strategy Strategy Strategy Strategy Logging make_listener() activate_svc_handler() accept_svc_handler() A Strategies A A make_creation_strategy() Reactive make_concurrency_strategy() Strategy ... 41 42 Advantages of New Design Structure of the Revised Acceptor Pattern Decouple advertisement strategy Concrete_Svc_Handler e.g., use well-known p orts or name server n SOCK Stream { Concrete 1 SOCK_Acceptor Svc Handler Concrete Acceptor LAYER Decouple listening strategy open() APPLICATION { e.g., using Reactoror some other demultiplexor SVC_HANDLER INITS PEER_ACCEPTOR PEER_STREAM A Svc Acceptor Decouple service creation strategy Handler advertise_svc() make_listener() e.g., dynamic allo cation vs singleton or dynamic open() A make_svc_handler() { static linking accept_svc_handler() linking vs. activate_svc_handler() LAYER open() CONNECTION handle_event() Decouple service connection acceptance strat- sh = make_svc_handler(); egy accept_svc_handler (sh); e.g., connection-oriented vs. connectionless activate_svc_handler (sh); { Event Decouple service concurrency strategy Handler 1 Reactor LAYER handle_event() n { e.g., single-threaded reactive vs. multi-threaded REACTIVE vs. multi-pro cess 43 44 Revised Acceptor Class Protected Revised Acceptor Class Public and Private Interface protected: Interface // = Bridge methods default behavior delegates template class PEER_ACCEPTOR> // Accepts connections virtual int advertise_svc const PEER_ACCEPTOR::PEER_ADDR &; class Acceptor : public Event_Handler { virtual int make_listener Event_Handler *; public: virtual SVC_HANDLER *make_svc_handler void; // = Initialization. virtual int accept_svc_handler SVC_HANDLER *; virtual int open virtual int activate_svc_handler SVC_HANDLER *; const PEER_ACCEPTOR::PEER_ADDR &, Strategy_Factory // = Strategy objects. // = Factory that creates, connects, Advertise_Strategy // and activates SVC_HANDLER's. *advertise_strat_; virtual int handle_event HANDLE; Listener_Strategy Creation_Strategy // = Demultiplexing hooks. Accept_Strategy virtual HANDLE get_handle void const; *accept_strat_; virtual int handle_close HANDLE, Reactor_Mask; Concurrency_Strategy *concurrency_strat_; }; 45 46 Acceptor Class Implementation // Shorthand names. define SH SVC_HANDLER define PA_AC PEER_ACCEPTOR Strategy Factory Interface define PA_AD PEER_ACCEPTOR::PEER_ADDR // Initialize the Acceptor. template class PEER_ACCEPTOR> // Accepts connections template class Strategy_Factory { Acceptor public: const PR_AC::PEER_ADDR &local_addr, Strategy_Factory Strategy_Factory Advertise_Strategy { Listener_Strategy // Initialize the strategies. Creation_Strategy this->create_strat_ = Accept_Strategy strat_fact->make_creation_strategy ; Concurrency_Strategy this->accept_strat_ = strat_fact->make_accept_strategy ; // Factory methods called by Acceptor::open. this->concurrency_strat_ = Advertise_Strategy *make_advertise_strategy void; strat_fact->make_concurrency_strategy ; Listener_Strategy *make_listener_strategy void; this->listen_strat_ = Creation_Strategy strat_fact->make_listener_strategy ; *make_create_strategy void; this->advertise_strat_ = Accept_Strategy strat_fact->make_advertise_strategy ; *make_accept_strategy void; Concurrency_Strategy // Advertise the service. *make_concurrency_strategy void; this->advertise_svc local_addr; // Make a listener. this->make_listener this; } 48 47 // Bridge method for creating a service handler. template Acceptor // Implements the strategy for creating, connecting, { // and activating new SVC_HANDLER objects. return this->creation_strategy_->make_svc_handler ; } template Acceptor // Accept connections from clients can be overridden. { // Create a new SVC_HANDLER. template Acceptor SH *svc_handler = this->make_svc_handler ; { return this->accept_strategy_-> // Accept connection from client. accept_svc_handler svc_handler; } this->accept_svc_handler svc_handler; // Activate the service handler can be overridden. // Activate SVC_HANDLER. template this->activate_svc_handler svc_handler; Acceptor } { return this->concurrency_strategy_-> activate_svc_handler svc_handler; } 49 50 Advertisement Strategies Advertisement Strategy ADDR Implementation s Advertise A template wn_Address Strategy Well_Kno const PA_AD &local_addr advertise_svc() { // Advertise the IP port and IP address. vertise_endpoint local_addr; ADDR ADDR this->ad } Well-known X.500 Address Strategy template per Strategy ADDR Port_Map const PA_AD &local_addr Portmapper { Advertise using the portmapper Strategy // // ... } 52 51 Listener Strategy Listener Strategies Implementation s // Cache a Reactor. PEER ACCEPTOR template _Listener_Strategy Listener Reactive *r A Reactor reactor_ r Strategy : { make_listener() } // Register with a Reactor int Reactive_Listener_Strategy::make_listener Event_Handler *h PEER ACCEPTOR PEER ACCEPTOR { this->handle_ = h->get_handle ; actor_->register_handler h, READ_MASK; Reactive Threaded this->re Listener Listener } Strategy Remove from Reactor Strategy // Reactive_Listener_Strategy::~Reactive_Listener_Strategy void { this->reactor_->remove_handler this->handle_; } 53 54 Creation Strategy Implementation s SVC HANDLER Creation // Make a Singleton SVC_HANDLER. Strategies template Singleton_Strategy if this->svc_handler_ == 0 this->svc_handler_ = new SH; SVC_HANDLER return this->svc_handler_; // Pre-cached... A Creation } Strategy // Make a new SVC_HANDLER on-demand. make_svc_handler() template Demand_Strategy return new SH; SVC_HANDLER } Make a SVC_HANDLER by dynamically linking it. SVC_HANDLER Dynamic // Strategy template Strategy SVC_HANDLER Open the shared library. Strategy // *handle = void * dlopen this->shared_library_; Singleton void Strategy // Extract the factory function. SH **factoryvoid = SH **void dlsym handle, this->factory_function_; // Call factory function to get new SVC_Handler. return *factory; } 55 56 SVC HANDLER Connection Acceptance Strategies Connection Acceptance Strategy Implementation SVC_HANDLER PEER_ACCEPTOR // Delegate to the accept method of the PEER_ACCEPTOR. A trategy Strategy Accept_S SH *svc_handler accept_svc_handler() { return this->peer_acceptor_.accept *svc_handler; } // Implement "stateless connection" strategy SVC_HANDLER PEER_ACCEPTOR SVC_HANDLER template ategy PEER_ACCEPTOR CLNS_Str *svc_handler CONS SH Strategy CLNS { ... Strategy // } 57 58 Concurrency Strategy SVC HANDLER Concurrency Implementation s Strategies // Activate the SVC_HANDLER by calling it's open method. template _Strategy SVC_HANDLER Reactive SH *svc_handler, void *arg Concurrency { Delegate control to the application-specific service Strategy A // // handler. activate_svc_handler() return svc_handler->open arg; SVC_HANDLER } SVC_HANDLER Activate the SVC_HANDLER by first calling its open Process // method and then calling its activate method to turn Reactive Strategy // it into an active object. Strategy // template Thread_Strategy SVC_HANDLER SVC_HANDLER SH *svc_handler, void *arg Thread Thread { Initialize SVC_HANDLER. Strategy Pool // ler->open arg; Strategy svc_hand // Turn the svc_handler into an active object if it isn't // already one as a result of the first activation... return svc_handler->activate ; } 60 59 New Acceptor Use-Case class Logging_Handler : public Svc_Handler Concluding Remarks { public: // Initialize handler. virtual int open void *; Design patterns can alleviate coupling and unnecessary complexity in communication // Obtain HANDLE. virtual HANDLE get_handle void const; software // Called back to process logging records. virtual int handle_event HANDLE; Patterns do not exist in isolation }; typedef Acceptor { Instead, they form \families of patterns" that build Logging_Acceptor; up on each other int main void { Reactor reactor; Both strategic and tactical patterns are nec- Logging_Acceptor acceptor; Logging_Strategy strategy /* ... */, &reactor; essary to build exible and extensible solu- acceptor.open LOGGER_PORT, &strategy; tions for ;; reactor.handle_events ; } 62 61