Professional Documents
Culture Documents
i hsbo
2001/
page i
i
Stephen D. Huston
i
i
i
i
i hsbo
2001/
page i
i
i
i
i hsb
2001
page
i
Contents
vii
.
.
.
.
.
.
.
.
.
.
.
.
1
1
4
7
10
16
18
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
23
23
24
24
26
27
30
31
33
33
33
35
37
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
iii
i
i
i hsbo
2001/
page i
i
iv
CONTENTS
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
39
39
42
44
54
61
73
81
84
90
. . . .
Class
. . . .
. . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
121
121
123
139
150
i
i
i hsbo
2001/
page v
i
List of Figures
1.1
1.2
1.3
1.4
1.5
1.6
1.7
1.8
3
5
8
9
11
14
14
18
2.1
2.2
2.3
2.4
2.5
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
25
27
30
32
34
3.1
3.2
3.3
3.4
3.5
3.6
3.7
3.8
3.9
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
40
43
46
55
62
68
70
75
78
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
i
i
i hsbo
2001/
page v
i
vi
LIST OF FIGURES
4.1
4.2
4.3
4.4
4.5
4.6
4.7
4.8
5.1
5.2
5.3
5.4
5.5
5.6
5.7
5.8
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
92
95
99
102
111
116
118
119
. 122
. 125
. 126
. 132
. 140
. 142
. 142
. 144
i
i
i hsbo
2001/
page v
i
Writing high-quality networked applications is hardits expensive, complicated, and error-prone. The patterns, C++ features, and object-oriented
design principles presented in [SH02], help to minimize complexity and
mistakes in concurrent networked applications by re-factoring [FBB+ 99]
common structure and functionality into reusable wrapper facade class libraries. If these class libraries are rewritten for each new project, however,
many benefits of reuse will be lost.
Historically, many network software projects began by designing and
implementing demultiplexing and dispatching infrastructure mechanisms
that handle timed events and I/O on multiple socket handles. Next, they
added service instantiation and handling mechanisms atop the demultiplexing and dispatching layer, along with message buffering and queueing
mechanisms. Finally, application-specific behavior was implemented using
this ad hoc host infrastructure middleware.
The development process outlined above has happened many times
in many companies, by many projects in parallel and, worse, by many
projects serially. Regrettably, this continuous rediscovery and reinvention
of core concepts and software has kept costs unnecessarily high throughout the development lifecycle. This problem is exacerbated by the inherent
diversity of todays hardware, operating systems, compilers, and communication platforms, which keep shifting the foundations of networked application software.
Object-oriented frameworks [FJS99a, FJS99b] are one of the most flexible and powerful techniques for addressing the problems outlined above. A
framework is a reusable, semi-complete application that can be specialvii
i
i
i hsbo
2001/
page v
i
viii
Intended Audience
This book is intended for hands-on C++ developers or advanced students interested in understanding how to design and apply object-oriented
frameworks to program concurrent networked applications. We show you
how to enhance your design skills and take advantage of C++, frameworks,
and patterns to produce flexible and efficient object-oriented networked
applications quickly and easily. The code examples we use to reinforce the
design discussions illustrate how to use the frameworks in ACE. These examples help you begin to apply key object-oriented design principles and
patterns to your concurrent networked applications right away.
i
i
i hsbo
2001/
page i
i
ix
Show concretely how ACE frameworks can help achieve efficient, predictable, and scalable networked applications and
Demonstrate key design and implementation considerations and solutions that will arise when you develop your own concurrent objectoriented networked applications.
i
i
i hsbo
2001/
page x
i
tern [SSRB00] to allow an application to link/unlink its component implementations at run-time without having to modify, recompile, or relink
the application statically.
Chapter 5 describes the design and effective use of the ACE Task framework, which can be used to implement key concurrency patterns, such as
Active Object and Half-Sync/Half-Async [SSRB00].
Chapter ?? describes the design and effective use of the ACE AcceptorConnector framework, which implements the Acceptor-Connector pattern
[SSRB00] to decouple the connection and initialization of cooperating peer
services in a networked system from the processing they perform once
connected and initialized.
Chapter ?? describes the design and use of the Proactor framework,
which implements the Proactor pattern [SSRB00] to allow event-driven applications to efficiently demultiplex and dispatch service requests triggered
by the completion of asynchronous operations.
Chapter ?? describes the design and use of the ACE Streams framework, which implements the Pipes and Filters pattern [BMR+ 96] to provide
a structure for systems that process a stream of data.
Chapter ?? describes the design and use of the ACE Logging Service
framework, which uses the ACE Reactor, Service Configurator, Task, and
Acceptor-Connector frameworks to implement and configure the various
processes that constitute the networked logging service.
Appendix ?? describes the design rules to follow when using the ACE
frameworks. Appendix ?? summarizes the lessons weve learned during
the past decade developing the reusable object-oriented networked application software in the ACE toolkit and deploying ACE successfully in a wide
range of commercial applications across many domains. Appendix ?? provides a synopsis of all the ACE classes and frameworks presented in the
two volumes of C++ Network Programming.
The book concludes with a glossary of technical terms, an extensive list
of references for further research, and a general subject index.
Related Material
This book focuses on abstracting commonly recurring object structures,
behaviors, and usage patterns into reusable frameworks to make it easier
to develop networked applications more efficiently and robustly. The first
i
i
i hsbo
2001/
page x
i
xi
You must supply emailaddress@domain only if your messages From address is not the address you wish to subscribe.
Archives of postings to the ACE mailing list are available at http://
groups.yahoo.com/group/ace-users. Postings to the ACE mailing list
are also forwarded to the USENET newsgroup comp.soft-sys.ace.
Acknowledgements
Champion reviewing honors go to ..., who reviewed the entire book and
provided extensive comments that improved its form and content substantially. Naturally, we are responsible for any remaining problems.
Many other ACE users from around the world provided feedback on
drafts of this book, including Vi Thuan Banh, Kevin Bailey, Alain Decamps,
Dave Findlay, Don Hinton, Martin Johnson, Nick Pratt, Eamonn Saunders,
Michael Searles, Kalvinder Singh, Henny Sipma, Leo Stutzmann, Tommy
Svensson, Dominic Williams, Johnny Willemsen, and Vadim Zaliva.
i
i
i hsbo
2001/
page x
i
xii
We are deeply indebted to all the members, past and present, of the
DOC groups at Washington University in St. Louis and the University of
California, Irvine, as well as the team members at Object Computing Inc.
and Riverace Corporation, who developed, refined, and optimized many of
the ACE capabilities presented in this book. This group includes...
We also want to thank the thousands of C++ developers from over fifty
countries whove contributed to ACE during the past decade. ACEs excellence and success is a testament to the skills and generosity of many
talented developers and the forward looking companies that have had the
vision to contribute their work to ACEs open-source code base. Without their support, constant feedback, and encouragement we would never
have written this book. In recognition of the efforts of the ACE opensource community, we maintain a list of all contributors thats available
at http://ace.ece.uci.edu/ACE-members.html.
We are also grateful for the support from colleagues and sponsors of
our research on patterns and development of the ACE toolkit, notably the
contributions of ...
Very special thanks go to....
Finally, we would also like to express our gratitude and indebtedness to
the late W. Richard Stevens, the father of network programming literature.
His books brought a previously-unknown level of clarity to the art and
science of network programming. We endeavor to stand on his virtual
shoulders, and extend the understanding that Richards books brought
into the world of object-oriented design and C++ programming.
Steves Acknowledgements
Dougs Acknowledgements
i
i
i hsbo
2001/
page 1
i
C HAPTER 1
C HAPTER S YNOPSIS
Object-oriented frameworks help reduce the cost and improve the quality
of networked applications by reifying software designs and pattern languages that have proven effective in particular application domains. This
chapter illustrates what frameworks are and shows how they compare and
contrast with other popular reuse techniques, such as class libraries, components, and patterns. We then outline the frameworks in ACE that are
the focus of this book. These frameworks are based on a pattern language
that has been applied to thousands of production networked applications
and middleware systems world-wide.
1.1
Although computing power and network bandwidth have increased dramatically in recent years, the design and implementation of networked application software remains expensive, time-consuming, and error-prone.
The cost and effort stems from the increasing demands placed on networked software and the continual rediscovery and reinvention of core
software design and implementation artifacts throughout the software industry. Moreover, the heterogeneity of hardware architectures, diversity of
OS and network platforms, and stiff global competition are making it increasingly hard to build networked application software from scratch while
1
i
i
i hsbo
2001/
page 2
i
i
i
i hsbo
2001/
page 3
i
and software artifacts that can significantly enhance the systematic reuse
of networked application software.
At the heart of this body of work are object-oriented frameworks [FJS99a,
FJS99b], which are a powerful technology for achieving systematic reuse
of networked application software.1 Figure 1.1 illustrates the following
NETWORKING
EVENT
LOOP
CALLBACKS
APPLICATIONSPECIFIC
GUI
CALL
EVENT
FUNCTIONALITY
DOMAIN-SPECIFIC
FRAMEWORK
BACKS
LOOP
CAPABILITIES
CALLBACKS
DATABASE
EVENT
LOOP
i
i
i hsbo
2001/
page 4
i
i
i
i hsbo
2001/
page 5
i
Application programming interfaces (APIs) and tools have evolved over the
years to simplify the development of networked applications and middleware. Figure 1.2 illustrates the IPC APIs available on many OS platforms,
such as UNIX and many real-time operating systems. This figure shows
HI
LEVEL OF
ABSTRACTION
& TLI
open()/close()/putmsg()/getmsg()
STREAMS
LO
FRAMEWORK
TPI
NPI
DLPI
USER
SPACE
KERNEL
SPACE
i
i
i hsbo
2001/
page 6
i
Description
Manage the lifetime of local and remote communication endpoints.
Enable applications to establish connections actively or passively with remote peers and to shutdown all or part of the
connections when transmissions are complete.
i
i
i hsbo
2001/
page 7
i
1.3
The ADAPTIVE Communication Environment (ACE) is a highly portable, widelyused, open-source host infrastructure middleware that can be downloaded
from http://ace.ece.uci.edu/ or http://www.riverace.com. The ACE
library contains 240,000 lines of C++ code and 500 classes. To separate
concerns, reduce complexity, and permit functional subsetting, ACE is designed using a layered architecture [BMR+ 96], shown in Figure 1.3. The
foundation of the ACE toolkit is its combination of an OS adaptation layer
and C++ wrapper facades [SSRB00], which together encapsulate core OS
concurrent network programming mechanisms. The higher layers of ACE
build upon this foundation to provide reusable frameworks, networked service components, and standards-based middleware. Together, these middleware layers simplify the creation, composition, configuration, and porting of networked applications without incurring significant performance
overhead.
The ACE wrapper facades for native OS IPC and concurrency mechanisms and the ACE standards-based middleware based upon and bundled with ACE were described in [SH02]. This book focuses on the ACE
frameworks that help developers produce portable, scalable, efficient, and
robust networked applications. These frameworks have also been used
to build higher-level standards-based middleware, such as The ACE ORB
i
i
i hsbo
2001/
page 8
i
NETWORKED
SERVICE
COMPONENTS
LAYER
TOKEN
SERVER
LOGGING
SERVER
C++
WRAPPER
FACADE
LAYER
C
APIS
GATEWAY
SERVER
NAME
SERVER
PROCESS/
THREAD
MANAGERS
SYNCH
WRAPPERS
(TAO)
TIME
SERVER
SERVICE
HANDLER
FRAMEWORK
LAYER
ACCEPTOR
STREAMS
LOG
MSG
SPIPE
SAP
SOCK SAP/
TLI SAP
FIFO
SAP
OS
PROCESSES/
THREADS
CORBA
HANDLER
CONNECTOR
WIN32 NAMED
PIPES & UNIX
STREAM PIPES
PROCESS/THREAD
SUBSYSTEM
SOCKETS/
TLI
REACTOR/
PROACTOR
SERVICE
CONFIGURATOR
SHARED
MALLOC
MEM
MAP
FILE
SAP
ADAPTATION LAYER
UNIX
FIFOS
COMMUNICATION
SUBSYSTEM
SELECT/
IO COMP
DYNAMIC
LINKING
SHARED
MEMORY
FILE SYS
APIS
i
i
i hsbo
2001/
page 9
i
Active
Object
AcceptorConnector
Reactor
Streams
Half-Sync
Half-Async
Proactor
Service
Configurator
Monitor
Object
Active Object is a design pattern that decouples the thread that executes a method from the thread that invoked it. Its purpose is to enhance concurrency and simplify synchronized access to objects that
reside in their own threads of control.
Half-Sync/Half-Async is an architectural pattern that decouples asynchronous and synchronous processing in concurrent systems, to simplify programming without reducing performance unduly. This pattern introduces two intercommunicating layers, one for asynchronous
and one for synchronous service processing. A queueing layer mediates communication between services in the asynchronous and synchronous layers.
i
i
i hsbo
2001/
page 1
i
10
i
i
i hsbo
2001/
page 1
i
11
LOCAL
SPECIFIC
INVOCATIONS
FUNCTIONALITY
MATH
CLASSES
ADT
CLASSES
DATABASE
CLASSES
EVENT
LOOP
GLUE
CODE
GUI
CLASSES
NETWORKING
NETWORK
IPC
CLASSES
EVENT
LOOP
CALLBACKS
GUI
CALL
BACKS
EVENT
LOOP
CALLBACKS
DATABASE
EVENT
LOOP
i
i
i hsbo
2001/
page 1
i
12
logic, as shown in Figure 1.5 (1). As a result, the total amount of reuse
is relatively small, compared with the amount of application-defined code
that must be rewritten for each application.
In contrast, classes in a framework collaborate to provide a reusable
architecture for a family of related applications. Frameworks can be classified by the techniques used to extend them, which range along a continuum from whitebox frameworks to blackbox frameworks [HJE95], as
described below:
Whitebox frameworksIn this type of framework, extensibility is
achieved via object-oriented language features, such as inheritance
and dynamic binding. Existing functionality can be reused and customized by inheriting from framework base classes and overriding
pre-defined hook methods [Pre94] using patterns such as Template
Method [GHJV95], which defines an algorithm with some steps supplied by a derived class. Application developers must have some
knowledge of a whitebox frameworks internal structure in order to
extend it.
Blackbox frameworksIn this type of framework, extensibility is
achieved by defining interfaces that allow objects to be plugged into
the framework via composition and delegation. Existing functionality can be reused by defining classes that conform to a particular
interface and then integrating these classes into the framework using patterns such as Bridge and Strategy [GHJV95], which provide
a blackbox abstraction for selecting one of many algorithms. Blackbox frameworks may be easier to use than whitebox frameworks since
application developers neednt have as much knowledge of the frameworks internal structure. Blackbox frameworks can be harder to design, however, since framework developers must define crisp interfaces that anticipate a broad range of potential use-cases.
ACE supports both whitebox and blackbox frameworks. For example,
its Acceptor-Connector framework described in Chapter ?? defines two
types of factories that initialize a new endpoint of communication in response to a connection request from a peer connector:
The ACE_Acceptor uses the Template Method pattern, which provides a whitebox approach to extensibility, whereas
The ACE_Strategy_Acceptor uses the Bridge and Strategy patterns,
which provide a blackbox approach to extensibility [Sch00b].
i
i
i hsbo
2001/
page 1
i
13
Inversion of Control
Call back to application-supplied event handlers to perform processing when events occur synchronously and
asynchronously, respectively.
Calls back to application-supplied service objects to initialize, suspend, resume, and finalize them.
Calls back to an application-supplied hook method to
perform processing in one or more threads of control.
Calls back to service handlers in order to initialize them
after theyve been connected.
Calls back to initialize and finalize tasks when they are
pushed and popped from a stream.
In practice, frameworks and class libraries are complementary technologies. As shown in Figure 1.6 for instance, the ACE toolkit simplifies the
implementation of its frameworks via its class libraries of containers, such
as queues, hash tables, and other ADTs. Likewise, application-defined
code invoked by event handlers in the ACE Reactor framework can use
the ACE wrapper facades presented in [SH02] and the C++ standard library classes [Jos99] to perform IPC, synchronization, file management,
and string processing operations.
i
i
i hsbo
2001/
page 1
i
14
ACE Task
LOOP
CALLBACKS
CLASSES
APPLICATION-
LOCAL
INVOCATIONS
IPC
CALL
ACE
Streams
ADT
EVENT
EVENT
CALLBACKS
CLASSES
ACE Reactor
LOOP
EVENT
LOOP
Figure 1.6: Applying Class Libraries to Develop and Use ACE Frameworks
Comparing Frameworks and Components
A component is an encapsulated part of a software system that implements
a specific service or set of services. A component has one or more interfaces
that provide access to its services. Components serve as building blocks for
the structure of a system and can be reused based solely upon knowledge
of their interface protocols. Components can also be plugged in and/or
scripted together to form complete applications, as shown in Figure 1.7.
Common examples of components include ActiveX controls, CORBA object
APPLICATION-
REMOTE OR
SPECIFIC
INVOCATIONS
FUNCTIONALITY
LOCAL
TIME
NAMING
LOCKING
EVENT
LOOP
GLUE
CODE
TRADING
LOGGING
i
i
i hsbo
2001/
page 1
i
15
i
i
i hsbo
2001/
page 1
i
16
Preserve important design information for programmers who enhance and maintain existing software. If this information isnt documented explicitly itll be lost over time. In turn, this increases software
entropy and decreases software maintainability and quality since substantial effort may likewise be necessary to reverse engineer the patterns from existing source code.
Guide design choices for developers who are building new networked
applications. Since patterns document the common traps and pitfalls
in their domain, they help developers select suitable architectures,
protocols, algorithms, and platform features without wasting time
and effort (re)implementing solutions that are known to be inefficient
or error-prone.
Knowledge of patterns and pattern languages helps to reduce development effort and maintenance costs. Reuse of patterns alone, however, is
not sufficient to create flexible and efficient networked application software. Although patterns enable reuse of abstract design and architecture
knowledge, software abstractions documented as patterns dont yield reusable code directly. Its therefore essential to augment the study of patterns with the creation and use of frameworks. Frameworks help developers avoid costly reinvention of standard software artifacts by implementing
common pattern languages and refactoring common implementation roles.
i
i
i hsbo
2001/
page 1
i
17
i
i
i hsbo
2001/
page 1
i
18
STORAGE DEVICE
CONSOLE
LOCAL
P2
IPC
ER
INT
PR
SERVER LOGGING
CLIENT
TCP
LOGGING
DAEMON
CONNECTION
HOST A
TANGO
DAEMON
P3
HOST B
MAMBO
if (Options::instance ()->debug())
ACE_DEBUG ((LM_DEBUG,
"sending request to server %s",
server_host));
CONNECTION
P1
SERVER
TCP
CLIENT
TANGO
P1
CLIENT
NETWORK
CLIENT
MAMBO
P2
LOGGING
DAEMON
LOCAL
IPC
P3
1.6 Summary
The traditional method of continually re-discovering and re-inventing core
concepts and capabilities in networked application software has kept the
costs of engineering these systems unnecessarily high for too long. Objectoriented frameworks are crucial to improving networked application development processes by reducing engineering cycle time and enhancing software quality and performance. A framework is a reusable, semi-complete
application that can be specialized to produce custom applications [JF88].
Frameworks can be applied together with patterns and components
to improve the quality of networked applications by capturing successful software development strategies. Patterns systematically capture abstract designs and software architectures in a format thats intended to be
comprehensible to developers. Frameworks and components reify concrete
i
i
i hsbo
2001/
page 1
i
19
i
i
i hsbo
2001/
page 2
i
20
take advantage of the expertise available from the core ACE development
team and experienced programmers throughout the ACE community. This
leveraging of expertise is one of the key benefits of open-source projects.
Developers can become more proficient with ACE by
i
i
i hsbo
2001/
page 2
i
21
benefits of employing the ACE framework usually offset any minor performance overheads.
i
i
i
i
i hsbo
2001/
page 2
i
i
i
i hsbo
2001/
page 2
i
C HAPTER 2
C HAPTER S YNOPSIS
A service is a set of functionality offered to a client by a service provider
or server. A networked application can be created by configuring its constituent services together at various points of time, such as compile-time,
static link-time, installation/boot-time, or even at run-time. This chapter
presents a domain analysis of service and configuration design dimensions
that address key networked application properties, including duration and
structure, how networked services are identified, and the time at which
they are bound together to form complete applications.
2.1
i
i
i hsbo
2001/
page 2
i
24
i
i
i hsbo
2001/
page 2
i
25
SVC2
DISPATCHER PROCESS
select()
SVC3
select()
SVC1
SVC2
Internal services usually have lower initialization latency, but also may
reduce application robustness since separate functions within a process
arent protected from one another. For instance, one faulty service can
corrupt data shared with other internal services in the process, which may
produce incorrect results, crash the process, or cause the process to hang
i
i
i hsbo
2001/
page 2
i
26
i
i
i hsbo
2001/
page 2
i
27
previous or possible future request. The need for any possible request
ordering is not a factor since TCP/IP is used, providing an ordered, reliable
communication stream.
SVC1R
MODULE1
GLOBAL DATA
MSG
SVC2W
SVC2R
MODULE2
MSG
SVC3W
SVC4
SVC3R
MODULE3
SVC4R
MODULE4
MSG
SVC4W
SVC1
(1) LAYERED/MODULAR
SERVICES
SVC2
SVC3
(2) MONOLITHIC
SERVICES
i
i
i hsbo
2001/
page 2
i
28
Layering enhances reuse since multiple higher-layer application components can share lower-layer services
Implementing applications via an inter-connected series of layered
services enables transparent, incremental enhancement of their functionality
A layered/modular architecture facilitates macro-level performance
improvements by allowing the selective omission of unnecessary service functionality and
Modular designs generally improve the implementation, testing, and
maintenance of networked applications and services.
1
After you become proficient with the ACE toolkit itll be much faster to build a properly
layered prototype than to hack together a monolithic one.
i
i
i hsbo
2001/
page 2
i
29
i
i
i hsbo
2001/
page 3
i
30
objects that configure applications, process events, establish connections, exchange data, and perform logging-specific processing. The
remaining chapters in this book illustrate how to implement these
application-level capabilities using the ACE frameworks.
INTERNAL
SERVICE
EXTERNAL
SERVICE
EXTERNAL
SERVICE
INTERNAL
SERVICE
EXTERNAL
SERVICE
EXTERNAL
SERVICE
SLAVES
INTERNAL
SERVICE
INTERNAL
SERVICE
EXTERNAL
SERVICE
EXTERNAL
SERVICE
The RWHO daemon (RWHOD), which reports the identity and number
of active users, as well as host workloads and host availability.
Early versions of UNIX ran standard network services, such as FTP
and TELNET, which ran as distinct single-service daemons that were
initiated at OS boot-time [Ste98].
Each instance of these single-service servers execute externally in a separate process. As the number of system servers increases, however, this
statically configured, single-service per-process approach incurred the following limitations:
i
i
i hsbo
2001/
page 3
i
31
It consumed excessive amounts of OS resources, such as virtual memory and process table slots
It caused redundant initialization and networking code to be written
separately for each service program
It required running processes to be shutdown and restarted manually
to install new service implementations and
It led to ad hoc and non-uniform administrative mechanisms being
used to control different types of services.
i
i
i hsbo
2001/
page 3
i
32
SUPER-SERVER PROCESS
LOCAL
SVC1
IPC
SVC2
SVC3
SVC4
SVC1
SVC2
SVC3
i
i
i hsbo
2001/
page 3
i
2.2
33
i
i
i hsbo
2001/
page 3
i
34
OBJECT
CODE
MODULES
(1)
STATIC LINKING
OBJECT
CODE
STUBS
(2)
SHARED
OBJECT
OBJECTS
IN
DLLS
CODE
STUBS
DYNAMIC LINKING
Implicit dynamic linking defers most address resolution and relocation operations until a function is first referenced. This lazy evaluation strategy minimizes link editing overhead during server initialization. Implicit dynamic linking is used to implement shared libraries,
also known as dynamic-link libraries (DLLs) [Sol98]. Ideally, only one
copy of DLL code exists, regardless of the number of processes that
execute library code simultaneously. DLLs can therefore reduce the
memory consumption of both a process in memory and its program
image stored on disk.
Explicit dynamic linking allows an application to obtain, use, and/or
remove the run-time address bindings of certain function- or datarelated symbols defined in DLLs. Common explicit dynamic linking
mechanisms include
The POSIX/UNIX dlopen(), dlsym(), and dlclose() functions
and
The Win32 LoadLibrary(), GetProcAddress(), and FreeLibrary()
functions.
Developers must consider tradeoffs between flexibility, time and space
efficiency, security, and robustness carefully when choosing between
dynamic and static linking [SSRB00].
i
i
i hsbo
2001/
page 3
i
35
Web browsing and content retrieval services, e.g., Alta Vista, Apache,
and Netscapes HTTP server
Software distribution services, e.g., Castanet
Electronic mail and network news transfer services, e.g., sendmail
and nntpd
File access on remote machines, e.g., ftpd
Network time protocols, e.g., ntpd
Payment processing services, e.g., Cybercash and
Streaming audio/video services, e.g., RealAudio, RealSystem, and RealPlayer.
i
i
i hsbo
2001/
page 3
i
36
Dynamic configuration refers to the process of initializing an application that offers dynamically named services. When combined with explicit dynamic linking and process/thread creation mechanisms, the services offered by dynamically configured applications can be extended at
installation/boot-time or even during run-time. This degree of extensibility
helps facilitate the following configuration-related activities:
i
i
i hsbo
2001/
page 3
i
37
Starting with Chapter ??, all our examples are configured dynamically.
2.3
Summary
i
i
i
i
i hsbo
2001/
page 3
i
i
i
i hsbo
2001/
page 3
i
C HAPTER 3
C HAPTER S YNOPSIS
This chapter describes the design and use of the ACE Reactor framework. This framework implements the Reactor pattern [SSRB00], which
allows event-driven applications to process events delivered from one or
more clients. In this chapter, we show how to implement a logging server
with a reactor that detects and demultiplexes different types of connection
and data events from various event sources and dispatches the events to
application-defined handlers that process the events.
3.1
Overview
i
i
i hsbo
2001/
page 4
i
40
ACE Class
ACE_Time_Value
ACE_Event_Handler
ACE_Timer_Heap
ACE_Timer_List
ACE_Timer_Wheel
ACE_Timer_Hash
ACE_Reactor
ACE_Select_Reactor
ACE_TP_Reactor
ACE_WFMO_Reactor
Description
Provides a portable representation of time that uses
C++ operator overloading to simplify the time-related
arithmetic and relational operations.
Defines an abstract interface for processing various
types of I/O, timer, and signal events.
Implementations of various ACE timer queues.
i
i
i hsbo
2001/
page 4
i
41
This design allows the ACE Reactor framework to be extended transparently without modifying or recompiling existing application code.
Increase reuse and minimize errorsDevelopers who write programs using native OS synchronous event demultiplexing operations
must reimplement, debug, and optimize the same low-level code for
each application. In contrast, the ACE Reactor frameworks event
detection, demultiplexing, and dispatching mechanisms are generic
and can therefore be reused by many networked applications, which
allows developers to focus on higher-level application-defined event
handler policies, rather than wrestling repeatedly with low-level mechanisms.
Efficient demultiplexingThe framework performs its event demultiplexing and dispatching logic efficiently. For instance, the ACE_
Select_Reactor presented in Section 3.6 uses the ACE_Handle_Set_
Iterator class described in Chapter 7 of [SH02], which uses an opti-
i
i
i hsbo
2001/
page 4
i
42
mized implementation of the Iterator pattern [GHJV95] to avoid examining fd_set bitmasks one bit at a time. This optimization is based on
a sophisticated algorithm that uses the C++ exclusive-or operator to
reduce run-time complexity from O(number of total bits) to O(number of
enabled bits), which can substantially improve run-time performance
for large-scale applications.
The remainder of this chapter motivates and describes the capabilities
of each class in the ACE Reactor framework. We illustrate how this framework can be used to enhance the design of our networked logging server.
Section ?? describes design rules to follow when applying the ACE Reactor
framework.
i
i
i hsbo
2001/
page 4
i
43
The interface for the ACE_Time_Value class is shown in Figure 3.2 and
its key methods are outlined in the following table:
Method
ACE_Time_Value
set()
sec()
usec()
operator +=
operator -=
operator *=
Description
Constructors and methods that convert from various time
formats, such as timeval or long, to a normalized ACE_
Time_Value.
Return the number of seconds in an ACE_Time_Value.
Return the number of microseconds in an ACE_Time_
Value.
Arithmetic methods that add, subtract, and multiply an
ACE_Time_Value.
In addition to the methods shown above, the following binary operators are
friends of the ACE_Time_Value class that define arithmetic and relational
operations:
Method
operator +
operator operator ==
operator !=
operator <
operator >
operator <=
operator >=
Description
Arithmetic methods that add and subtract two ACE_Time_
Values.
Methods that compare two ACE_Time_Values for equality and
inequality.
Methods that determine relationships between two ACE_Time_
Values.
All ACE_Time_Value constructors and methods normalize the time values they operate upon. For example, after normalization, the quantity
i
i
i hsbo
2001/
page 4
i
44
This program behaves identically on all OS platforms where ACE has been
ported. Sidebar 2 describes how to build the ACE library so that you can
experiment with the examples we present in this book.
i
i
i hsbo
2001/
page 4
i
45
ACE should be installed into an empty directory. The top-level directory in the distribution is named ACE_wrappers. We refer to this toplevel directory as $ACE_ROOT. You should create an environment
variable by that name containing the full path to the top-level ACE
directory.
The ACE source and header files reside in $ACE_ROOT/ace.
This books networked logging service example source and header
files reside in $ACE_ROOT/examples/C++NPv2.
When compiling your programs, the $ACE_ROOT directory must be
added to your compilers file include path, which is often designated
by the -I or /I compiler option.
The $ACE_ROOT/ACE-INSTALL.html file contains instructions on
building and installing ACE and programs that use ACE.
You can also purchase a prebuilt version of ACE from Riverace at a nominal cost. A list of the prebuilt compiler and OS platforms supported by
Riverace is available at http://www.riverace.com.
function for each type of event. This approach can become unwieldy, however, since application programmers are responsible for explicitly
1. Keeping track of which functions correspond to which events and
2. Associating data with the functions.
To alleviate both problems, the ACE Reactor framework defines the ACE_
Event_Handler base class.
Class Capabilities
The ACE_Event_Handler base class is the root of all event handlers in
ACE. This class provides the following capabilities:
i
i
i hsbo
2001/
page 4
i
46
handle_exception()
handle_timeout()
handle_signal()
handle_close()
get_handle()
reactor()
Description
Hook method called when input events occur, e.g., connection or data events.
Hook method called when output events are possible,
e.g., when flow control abates or a non-blocking connection completes.
Hook method called when an exceptional event occurs,
e.g., a SIGURG signal.
Hook method called when a timer expires.
Hook method called when signaled by the OS, either via
POSIX signals or when a Win32 synchronization object
transitions to the signaled state.
Hook method that performs user-defined termination
activities when one of the handle_*() hook methods outlined above returns ,1 or when a remove_
handler() method is called explicitly to unregister an
event handler.
Returns the underlying I/O handle.
Accessors to get/set the ACE_Reactor associated with
an ACE_Event_Handler.
i
i
i hsbo
2001/
page 4
i
47
WRITE MASK
EXCEPT MASK
ACCEPT MASK
CONNECT MASK
Description
Indicates input events, such as data on a socket or file handle. A reactor dispatches the handle_input() hook method
to process input events.
Indicates output events, such as when flow control abates.
A reactor dispatches the handle_output() hook method to
process output events.
Indicates exceptional events, such as urgent data on a
socket. A reactor dispatches the handle_except() hook
method to process exceptional events.
Indicates passive-mode connection events. A reactor dispatches the handle_input() hook method to process connection events.
Indicates a non-blocking connection completion. A reactor
dispatches the handle_output() hook method to process
non-blocking connection completion events.
All these * MASK enumeration values are defined as powers of two so their
bits can be ord together efficiently to designate a set of events.
Concrete event handlers used for I/O events provide a handle, such
as a socket handle, that can be retrieved via the get_handle() hook
method. When an application registers a concrete event handler with a
reactor, the reactor calls back to the handlers get_handle() method to
retrieve the underlying handle. This method can be left as a no-op if a concrete event handler only handles time-driven events. For example, the ACE
i
i
i hsbo
2001/
page 4
i
48
timer queue classes described in Section 3.4 use concrete event handlers
to process time-driven events. When a timer managed by this mechanism
expires, the handle_timeout() method of the associated event handler is
invoked by the reactor.
The reactor interprets the return values of the handle_*() hook methods as follows:
Return value of 0When a handle_*() method returns 0 this informs the reactor that the event handler wishes to remain registered
with the reactor. The reactor will therefore continue to include the
handle of this event handler next time its handle_events() method
is invoked. This behavior is common for event handlers whose lifetime
extends beyond a single handle_*() method dispatch.
Return greater than 0When a handle_*() method returns a value
greater than 0 this informs the reactor that the event handler wishes
to be dispatched again before the reactor blocks on its event demultiplexer. This feature is useful for cooperative event handlers to enhance overall system fairness since it allows one event handler to perform a limited amount of computation, relinquish control, and then
allow other event handlers to be dispatched before it retains control
again.
Return less than 0When a handle_*() method returns a value
less than 0 this informs the reactor that the event handler wants to be
removed from the reactors internal tables. In this case, the reactor invokes the event handlers handle_close() hook method and passes
it the ACE_Reactor_Mask value corresponding to the handle_*()
hook method that returned ,1. In addition to the event types shown
in the table on page 47, the reactor can pass the following enumeration values defined in ACE_Event_Handler:
Event Type
TIMER MASK
SIGNAL MASK
Description
Indicates time-driven events. A reactor dispatches the
handle_timeout() hook method to process timeout
events.
Indicates signal-based events (or synchronizationbased events on Win32). A reactor dispatches the
handle_signal() hook method to process signal
events.
i
i
i hs_
2001/
page
i
49
"ace/Event_Handler.h"
"ace/INET_Addr.h"
"ace/Log_Record.h"
"ace/Reactor.h"
"ace/FILE.h"
"ace/SOCK_Acceptor.h"
"ace/SOCK_Stream.h"
"Logging_Handler.h"
i
i
i hsbo
2001/
page 5
i
50
code in this example, a production implementation should take appropriate corrective action if failures occur.
class Logging_Acceptor : public ACE_Event_Handler
{
public:
Logging_Acceptor (ACE_Reactor *r = ACE_Reactor::instance ())
: ACE_Event_Handler (r) {}
// Initialize a passive-mode acceptor socket. The <local_addr>
// is the address that were going to listen for connections on.
virtual int open (const ACE_INET_Addr &local_addr) {
return peer_acceptor_.open (local_addr);
}
// Return the connected sockets I/O handle.
virtual ACE_HANDLE get_handle () const
{ return peer_acceptor_.get_handle (); }
// Called by a reactor when theres a new connection to accept.
virtual int handle_input (ACE_HANDLE h = ACE_INVALID_HANDLE);
// Called when object is destroyed, e.g., when its removed
// from a reactor.
virtual int handle_close (ACE_HANDLE = ACE_INVALID_HANDLE,
ACE_Reactor_Mask = 0) {
peer_acceptor_.close ();
}
// Returns a reference to the underlying <peer_acceptor_>.
ACE_SOCK_Acceptor &acceptor () const {
return peer_acceptor_;
}
private:
// Factory that connects <ACE_SOCK_Stream>s passively.
ACE_SOCK_Acceptor peer_acceptor_;
};
i
i
i hs_
2001/
page
i
51
i
i
i hs_
2001/
page
i
52
i
i
i hsbo
2001/
page 5
i
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 }
53
Lines 38 Determine the host name of the connected client and use this
as the filename of the log file.
Lines 1017 Create or open the file thats used to store log records from
a connected client.
Lines 1920 Use the ACE_Reactor::register_handler() method to
register this event handler for READ events with the same reactor used
by the Logging_Acceptor. This method is described on page 62 in Section 3.5.
When log records arrive from clients, the reactor will dispatch Logging_
Handler_Adapter::handle_input() automatically. This method processes
a single log record by calling Logging_Handler::log_record(), which
reads the record from the socket and writes it to the log file associated with
the client connection, as shown below:
int Logging_Handler_Adapter::handle_input (ACE_HANDLE)
{
return logging_handler_.log_record ();
}
i
i
i hsbo
2001/
page 5
i
54
i
i
i hsbo
2001/
page 5
i
55
A void pointer thats stored internally by the timer queue and passed
back unchanged when the handle_timeout() method is dispatched.
This pointer can be used as an asynchronous completion token (ACT)
[SSRB00], which allows an application to efficiently demultiplex and
process the responses of asynchronous operations it invokes on services. This capability allows the same event handler to be registered
with a timer queue at multiple dispatching times in the future.
i
i
i hsbo
2001/
page 5
i
56
The ACE timer queue classes combine design patterns, hook methods,
and template arguments to provide the following timer queue implementations:
ACE_Timer_Heap, which is a partially-ordered, almost-complete binary tree implemented in an array [Rob99]. Its average- and worstcase performance for scheduling, canceling, and expiring a concrete
event handler is O (lg n). Heap-based timer queues are therefore useful for applications [BL88] and middleware [SLM98] that require predictable and low-latency time-driven dispatching, which is why its
the default timer queue mechanism in the ACE Reactor framework.
ACE_Timer_Wheel, which uses timing wheels [VL97] that contain a
circular buffer designed to schedule, cancel, and dispatch timers in
O(1) time in the average case, but O(n) in the worst-case.
ACE_Timer_Hash, which uses a hash table to manage the queue. Like
the timing wheel implementation, the average-case time required to
schedule, cancel, and expire timers is O (1) and its worst-case is O (n).
i
i
i hsbo
2001/
page 5
i
57
The ACE Reactor framework allows developers to use any of these timer
queue implementations to achieve the functionality needed by their applications, without burdening them with a one size fits all implementation.
Since the methods in the ACE_Timer_Queue base class are virtual, applications can provide different implementations of other timer queue mechanisms, such as delta lists [Com84]. Like the ACE_Timer_List, a delta list
stores event handlers in a list ordered by increasing deadline. Rather than
being absolute time, however, the delay for each event is computed from
expiration of previous event. The first element of the list can therefore be
checked/dispatched in O (1) time, though insertion and deletion of a timer
may require O(n) time.
Another example of flexibility in the ACE Reactor framework is the time
source used by an ACE timer queue. Most ACE timer queues internally
use the absolute time of day. While this implementation is fine for many
applications, ACE also provides a hook method that allows applications
to configure a different time source for a timer queue. For example, the
performance counter on Win32 bases the time on system uptime rather
than on wallclock time. System uptime is useful in situations where the
systems time-of-day can be adjusted, but timers must not be affected by
changes to the time-of-day clock.
Example
Although the Logging_Acceptor and Logging_Handler_Adapter event
handlers in Section 3.3 implement the logging server functionality correctly, they may consume system resources unnecessarily. For example,
clients can connect to a server and then not send log records for long periods of time. In the example below, we illustrate how to apply the ACE
timer queue mechanisms to reclaim resources from those event handlers
whose clients log records infrequently. Our design is based on the Evictor
pattern [HV99], which describes how and when to release resources, such
as memory and I/O handles, to optimize system resource management.
We use the Evictor pattern in conjunction with the ACE Reactor frame-
i
i
i hs_
2001/
page
i
58
i
i
i hs_
2001/
page
i
59
class Logging_Handler_Adapter_Ex
: public Logging_Handler_Adapter
{
private:
// Time when a client last sent a log record.
ACE_Time_Value time_of_last_log_record_;
// Maximum timeout.
const ACE_Time_Value max_client_timeout_;
// Max timeout is an hour.
enum { MAX_CLIENT_TIMEOUT = 3600 };
// Private destructor ensures dynamic allocation.
Logging_Handler_Adapter () {}
i
i
i hs_
2001/
page
i
60
The handle_input() method notes the time when a log record is received from the connected client and then forwards to its parents handle_
input() method to process the log record, as shown below:
int Logging_Handler_Adapter_Ex::handle_input (ACE_HANDLE h)
{
time_of_last_log_record_ = ACE_OS::gettimeofday ();
return Logging_Handler_Adapter::handle_input (h);
}
This method first forwards to its parents open() method, which is defined
on page 52. It then calls the ACE_Reactor::schedule_timer() method
(described on page 65) to schedule this event handler to be dispatched
periodically to check whether its client sent it a log record recently. We
schedule the initial timer to expire in max_client_timeout_ seconds and
also request that it re-expire periodically every max_client_timeout_ seconds. When a timer expires, the reactor uses its timer queue mechanism
to dispatch the following handle_timeout() hook method automatically:
int Logging_Handler_Adapter_Ex::handle_timeout
(const ACE_Time_Value &tv, const void *act)
{
if (ACE_OS::gettimeofday () - time_of_last_log_record_
> max_client_timeout_)
i
i
i hsbo
2001/
page 6
i
61
This method checks if the time elapsed between the current time and
when the last log record was received by this event handler is greater than
designated max_client_timeout_ threshold. If so, it calls the remove_
handler() method, which triggers the reactor to call the following handle_
close() hook to remove the event handler from the reactor:
int Logging_Handler_Adapter_Ex::handle_close (ACE_HANDLE,
ACE_Reactor_Mask)
{
reactor ()->cancel_timer (this);
return Logging_Handler_Adapter::handle_close ();
}
This method cancels the timer for this event handler and then calls its
parents handle_close() method, which closes the socket to the client
and the log file, and then deletes itself.
3.5
Motivation
Event-driven networked applications have historically been programmed
using native OS mechanisms, such as the Socket API and the select()
synchronous event demultiplexer. These implementations are often inflexible, however, since they tightly couple low-level event detection, demultiplexing, and dispatching code with application processing code. Developers must therefore rewrite all this code for each new networked application.
This approach is tedious, expensive, error-prone, and unnecessary, however, since event detection, demultiplexing, and dispatching can be performed in an application-independent manner. To decouple this reusable
code from the application-defined event processing code, the ACE Reactor
framework defines the ACE_Reactor class.
Class Capabilities
The ACE_Reactor class implements the Facade pattern [GHJV95] to define
an interface that applications can use to access the various capabilities of
i
i
i hsbo
2001/
page 6
i
62
the ACE Reactor framework. This class provides the following capabilities:
Description
These methods create and initialize instances of a reactor.
These methods clean up the resources allocated when a reactor was initialized.
2. Event handler management methods. The following methods register and remove concrete event handlers from an ACE_Reactor:
i
i
i hsbo
2001/
page 6
i
Method
register_handler()
remove_handler()
mask_ops()
schedule_wakeup()
cancel_wakeup()
63
Description
Methods that register concrete event handlers with a
reactor.
Methods that remove concrete event handlers from a
reactor.
Perform operations that get, set, add, and clear the
event type(s) associated with an event handler and its
dispatch mask.
Add the designed masks to an event handlers entry, which must already have been registered via
register_handler().
Clears the designated masks from an event handlers
entry, but doesnt not remove the handler from the reactor.
The register_handler() methods can be used with either of the following signatures:
Both ACE_Reactor::remove_handler() methods remove an event handler from a reactor so that its no longer registered for one or more types
of events. The signatures of these methods can be passed either an event
handler and/or a handle, just like the two register_handler() method
variants described above. When removing a handler, applications also pass
a bit-mask consisting of the enumeration literals defined in the table on
i
i
i hsbo
2001/
page 6
i
64
page 47 to indicate which event types are no longer desired. The event
handlers handle_close() method is called soon afterwards to notify it of
the removal.2 After handle_close() returns and the concrete event handler is no longer registered to handle any events, the ACE_Reactor removes
the event handler from its internal data structures.
The mask_ops() method performs operations that get, set, add, and
clear the event type(s) associated with an event handler and its dispatch
mask. Since mask_ops() assumes that an event handler is already present
and doesnt try to remove it, its more efficient than using register_
handler() and remove_handler(). The schedule_wakeup() and cancel_
wakeup() methods are simply syntactic sugar for common operations involving mask_ops().
The mask_ops(), schedule_wakeup(), and cancel_wakeup() methods dont cause the reactor to re-examine its set of handlers, i.e., the new
masks will only be noticed the next time the reactors handle_events()
method is called. If theres no other activity expected, or you need immediate re-examination of the wait masks, youll need to call ACE_Reactor::
notify() after calling one of these methods or use the ACE_Reactors
register_handler() or remove_handler() methods instead.
3. Event-loop management methods. After registering its initial concrete event handlers, an application can manage its event loop via the
following methods:
i
i
i hsbo
2001/
page 6
i
Method
handle_events()
run_reactor_event_loop()
end_reactor_event_loop()
reactor_event_loop_done()
65
Description
Wait for events to occur and then dispatch the
event handlers associated with these events.
A timeout parameter can bound the time
spent waiting for events, so that it wont block
indefinitely if events never arrive.
Run the event loop continuously until the
handle_events() method returns ,1 or the
end_reactor_event_loop() method is invoked.
Instruct a reactor to terminate its event loop
so that it can shut down gracefully.
Returns 1 when the reactors event loop has
been ended, e.g., via a call to end_reactor_
event_loop().
Description
Register a concrete event handler that will be executed
at a user-specified time in the future.
Cancel one or more event handlers that were registered
previously.
i
i
i hsbo
2001/
page 6
i
66
Time-driven events
Notifications
Output I/O events
Exception I/O events
Input I/O events
Its generally a good idea to design applications whose behavior is independent of the order in which the different types of events are dispatched.
There are situations, however, where knowledge of the dispatching order
of events is necessary.
of timers with the dispatching of other types of events, whereas the ACE
timer queue classes just dispatch time-driven events. Moreover, unlike
the schedule() methods described in Section 3.4, the ACE_Reactor::
schedule_timer() uses relative time, not absolute time. As a result, we
use slightly different names for the ACE_Reactor timer management methods.
5. Notification methods. The following methods manage the notification
queue that application threads can use to communicate with and control
a reactor:
Method
notify()
max_notify_iterations()
purge_pending_notifications()
Description
Pass an event handler to a reactor and
designate which handle_*() method
will be dispatched in the context of the
reactor. By default, the event handlers handle_except() method is dispatched.
Set the maximum number of event handlers that a reactor will dispatch from
its notification queue.
Purge a specified event handler or all
event handlers that are in the reactors
notification queue.
i
i
i hsbo
2001/
page 6
i
67
Some reactor implementations, such as the ACE_Select_Reactor described in Section 3.6, only allow one thread to run their handle_events()
method. The owner() method changes the identity of the thread that owns
the reactor to allow this thread to run the reactors event loop.
The ACE Reactor framework uses the Bridge pattern [GHJV95] to decouple the interface of a class from its various implementations. The
ACE_Reactor defines the interface and all the actual event detection, demultiplexing, and dispatching is performed by implementations of this interface. The Bridge pattern allows applications to choose different reactors
without changing their source code, as shown in Figure 3.6.
i
i
i hsbo
2001/
page 6
i
68
i
i
i hsbo
2001/
page 6
i
69
Example
Now that weve described the classes in the ACE Reactor framework, we
show how they can be integrated to implement a complete version of the
networked logging server. This server listens on a TCP port number defined
in the OS network services file as ace_logger, which is a practice used by
many networked servers. For example, the following line might appear in
the UNIX /etc/services file:
ace_logger
9700/tcp
Client applications can optionally specify the TCP port and the IP address where the client application and logging server should rendezvous to
exchange log records. If this information is not specified, however, the port
number is located in the services database, and the hostname is assumed
to be the ACE DEFAULT SERVER HOST, which is defined as localhost on
most OS platforms.
The version of the logging server shown below offers the same capabilities as the Reactive_Logging_Server_Ex version in Chapter 7 of [SH02].
Both logging servers run in a single thread of control in a single process,
handling log records from multiple clients reactively. The main difference is
that this version uses the Reactor pattern to factor out the event detection,
demultiplexing, and dispatching code from the logging server application
into the ACE Reactor framework, which dispatches incoming log records
received from clients in a round-robin fashion. This refactoring helps address limitations with the original Reactive_Logging_Server_Ex implementation, which contained the classes and functionality outlined below
that had to be re-written for each new networked application:
Managing various mappings: The Reactive_Logging_Server_Ex server
contained two data structures that performed the following mappings:
An ACE_Handle_Set mapped socket handles to Logging_Handler
objects, which encapsulate the I/O and processing of log records in
accordance with the logging services message framing protocol.
An ACE_Hash_Map_Manager mapped socket handles to their associated ACE_FILE_IO objects, which write log records to the appropriate
output file.
Weve removed all the mapping code from this chapters logging server application and refactored it to reuse the capabilities available in the ACE
Reactor framework. Since the framework now provides and maintains this
code, you neednt write it for this or any other networked application.
i
i
i hsbo
2001/
page 7
i
70
Event detection, demultiplexing, and dispatching: To detect connection and data events, the Reactive_Logging_Server_Ex server used the
ACE::select() synchronous event demultiplexer method. This design has
the following drawbacks:
The version of the logging server shown below uses the ACE Reactor framework to detect, demultiplex, and dispatch I/O- and time-based
events. This framework also supports the integration of signal handling in
the future as the need arises. In general, applications use the following
steps to integrate themselves into the ACE Reactor framework:
1. Create concrete event handlers by inheriting from the ACE_Event_
Handler base class and overriding its virtual methods to handle various types of events, such as I/O events, timer events, and signals
2. Register concrete event handlers with an instance of ACE_Reactor
and
3. Run an event loop that demultiplexes and dispatches events to the
concrete event handlers at run-time.
Figure 3.7 illustrates the Reactor-based logging server architecture that
builds upon and enhances our earlier logging server implementations from
[SH02]. To enhance reuse and extensibility, the classes in this figure are
Figure 3.7: Architecture of Reactor-based Logging Server
designed to decouple the following aspects of the logging servers architecture, which are described from the bottom to the top of Figure 3.7:
Reactor framework classes. The classes in the Reactor framework encapsulate the lower-level mechanisms that perform event detection and
the demultiplexing and dispatching of events to concrete event handler
hook methods.
Connection-oriented ACE Socket wrapper facade classes. The ACE_
SOCK_Acceptor and ACE_SOCK_Stream classes presented in Chapter 3
i
i
i hs_
2001/
page
i
71
of [SH02] are used in this version of the logging server. As in previous versions, the ACE_SOCK_Acceptor accepts network connections from remote
clients and initializes ACE_SOCK_Stream objects. An initialized ACE_SOCK_
Stream object then processes data exchanged with its connected client.
Concrete logging event handler classes. These classes implement the
capabilities specific to the networked logging service. As shown in the Example portion of Section 3.4, The Logging_Acceptor_Ex factory uses an
ACE_SOCK_Acceptor to accept client connections. Likewise, the Logging_
Handler_Adapter_Ex uses an ACE_SOCK_Stream to receive log records
from connected clients. Both classes are ancestors of ACE_Event_Handler,
so their handle_input() methods receive callbacks from an ACE_Reactor.
Our implementation resides in a header file called Reactor_Logging_
Server.h, which includes several header files that provide the various capabilities well use in our logging server.
#include "ace/ACE.h"
#include "ace/Reactor.h"
#include "Logging_Acceptor_Ex.h"
This class inherits from its ACCEPTOR template parameter. To vary certain
aspects of Reactor_Logging_Servers connection establishment and logging behavior, subsequent examples will instantiate it with various types of
acceptors, such as the Logging_Acceptor_Ex on page 58. The Reactor_
Logging_Server also contains a pointer to the ACE_Reactor that it uses
to detect, demultiplex, and dispatch I/O- and time-based events to their
event handlers.
The signatures of the public methods in Reactor_Logging_Server class
are shown below.
public:
// Constructor uses the singleton reactor by default.
Reactor_Logging_Server
i
i
i hsbo
2001/
page 7
i
72
(int argc,
char *argv[],
ACE_Reactor *r = ACE_Reactor::instance ());
// Destructor.
virtual Reactor_Logging_Server ();
};
This interface differs from the Logging_Server class defined in Chapter 4 of [SH02]. In particular, the Reactor_Logging_Server uses the
ACE_Reactor::handle_events() method to drive application processing
via upcalls to instances of Logging_Acceptor and Logging_Handler_
Adapter. We therefore dont need the wait_for_multiple_events(),
handle_connections(), and handle_data() hook methods that were used
in the reactive logging servers from [SH02].
The Reactor_Logging_Servers constructor performs the steps necessary to initialize the Reactor-based logging server:
1 template <class ACCEPTOR>
2 Reactor_Logging_Server<ACCEPTOR>::Reactor_Logging_Server
3
(int argc, char *argv[], ACE_Reactor *reactor)
4
: reactor_ (reactor) {
5
u_short logger_port = argc > 1 ? atoi (argv[1]) : 0;
6
ACE::set_handle_limit ();
7
8
typename ACCEPTOR::PEER_ADDR server_addr;
9
int result;
10
11
if (logger_port != 0)
12
result = server_addr.set (logger_port, INADDR_ANY);
13
else
14
result = server_addr.set ("ace_logger", INADDR_ANY);
15
if (result == -1) return -1;
16
17
open (server_addr); // Calls ACCEPTOR::open();
18
19
reactor_->register_handler
20
(this, ACE_Event_Handler::ACCEPT_MASK);
21 }
Line 5 Set the port number thatll be used to listen for client connections.
Line 6 Raise the number of available socket handles to the maximum
supported by the OS platform.
Lines 817 Set the local server address and use this to initialize the
passive-mode logging acceptor endpoint.
i
i
i hsbo
2001/
page 7
i
73
ACCEPT
events.
3.6
Motivation
At the heart of a reactive server is a synchronous event demultiplexer that
detects and reacts to events from clients continuously. The select() function is the most common synchronous event demultiplexer. This system
i
i
i hsbo
2001/
page 7
i
74
function waits for specified events to occur on a set of I/O handles.3 When
one or more of the I/O handles become active, or after a designated period
of time elapses, select() returns to its caller. The caller can then process
the events indicated by information returned from select(). Additional
coverage of select() is available in Chapter 6 of [SH02] and in [Ste98].
Although select() is available on most OS platforms, programming to
the native OS select() C API requires developers to wrestle with many
low-level details, such as:
i
i
i hsbo
2001/
page 7
i
75
Non-synchronizedThis version is designed to minimize the overhead of event demultiplexing and dispatching for single-threaded applications. It can be configured by parameterizing the ACE_Select_
Reactor_T template with an ACE_Null_Mutex.
SynchronizedThis version allows multiple threads to invoke methods on a single ACE_Reactor thats shared by all the threads. It
also allows multiple ACE_Reactors to run in separate threads within
a process. The ACE_Select_Reactor is an instantiation of ACE_
Select_Reactor_T that uses a recursive locking mechanism called
an ACE_Token to prevent race conditions and intra-class method deadlock. Sidebar 6 outlines the ACE_Token class.
i
i
i hsbo
2001/
page 7
i
76
i
i
i hsbo
2001/
page 7
i
77
Example
The reactive logging server on page 73 is designed to run continuously.
Theres no way to shut it down gracefully, however, other than to terminate
it abruptly, e.g., by sending its process a kill -9 from a UNIX login console
or ending the process via the Win2K process manager. In this example,
we show how to use the ACE_Select_Reactor::notify() mechanism to
shut down the logging server gracefully and portably.
The ACE_Select_Reactor implements its notification mechanism via
an ACE_Pipe, which is a bi-directional IPC mechanism described in Sidebar 7. The two ends of a pipe play the following roles:
The writer roleThe ACE_Select_Reactor::notify() method exposes this end of the pipe to applications, which use the notify()
method to pass event handler pointers to an ACE_Select_Reactor
via its notification pipe.
The reader roleThe ACE_Select_Reactor registers this end of the
pipe internally with a READ MASK. When the reactor detects an event
in the read-side of its notification pipe it wakes up and dispatches
a user-configurable number of event handlers from the pipe. Unlike
other concrete event handlers registered with a reactor, these handlers neednt be associated with I/O-based or timer-based events,
which helps improve the ACE_Select_Reactors dispatching scalability. In particular, theres no requirement that a handler whose
pointer you give to ACE_Reactor::notify() has ever been, or ever
will be, registered with that reactor.
Figure 3.9 illustrates the structure of the reader and writer roles within
an ACE_Select_Reactor.
We can use the ACE_Select_Reactors ACE_Pipe-based notification
mechanism to shut down our Reactor_Logging_Server gracefully via the
following steps:
1. Well spawn a controller thread that waits for an administrator to pass
it commands via its standard input.
2. When the quit command is received, the controller thread passes a
special concrete event handler to the singleton reactor via its notify()
method and then exits the thread.
3. The reactor dispatches this event handler by invoking its handle_
except() method, which calls end_reactor_event_loop() and then
deletes itself.
i
i
i hs_
2001/
page
i
78
#include "ace/Auto_Ptr.h"
// Forward declarations.
void *controller (void *);
void *event_loop (void *);
typedef Reactor_Logging_Server<Logging_Acceptor_Ex>
Server_Logging_Daemon;
int main (int argc, char *argv[])
{
auto_ptr <ACE_Select_Reactor> holder (new ACE_Select_Reactor);
auto_ptr <ACE_Reactor> reactor (new ACE_Reactor (holder.get ()));
ACE_Reactor::instance (reactor.get ());
Server_Logging_Daemon server (argc, argv, reactor.get ());
ACE_Thread_Manager::instance ()->spawn
(event_loop, ACE_static_cast (void *, reactor.get ()));
i
i
i hs_
2001/
page
i
79
19
20
ACE_Thread_Manager::instance ()->spawn
21
(controller, ACE_static_cast (void *, reactor.get ()));
22
23
return ACE_Thread_Manager::instance ()->wait ();
24 }
Lines 17 We include the relevant ACE header file, define some forward
declarations, and instantiate the Reactor_Logging_Server template with
the Logging_Acceptor_Ex class from page 58 to create a Server_Logging_
Daemon type definition.
Lines 1113 We set the implementation of the singleton ACE_Reactor to
be an ACE_Select_Reactor.
Lines 1518 We then create an instance of Server_Logging_Daemon and
use the ACE_Thread_Manager singleton described in Chapter 9 of [SH02]
to spawn a thread that runs the following event_loop() function:
void *event_loop (void *arg)
{
ACE_Reactor *reactor = ACE_static_cast (ACE_Reactor *, arg);
reactor->owner (ACE_OS::thr_self ());
return reactor->run_reactor_event_loop ();
}
Note how we set the owner of the reactor to the id of the thread that runs
the event loop. Section ?? explains the design rule governing the use of
thread ownership for the ACE_Select_Reactor.
Lines 2021 We spawn a thread to run the controller() function, which
is the next function shown below.
Line 23 We wait for the other threads to exit before returning from the
main() function.
The controller function is implemented as follows:
1 void *controller (void *arg)
2 {
3
ACE_Reactor *reactor = ACE_static_cast (ACE_Reactor *, arg);
4
5
Quit_Handler *quit_handler = new Quit_Handler (reactor);
6
7
for (;;) {
8
std::string user_input;
i
i
i hsbo
2001/
page 8
i
80
9
getline (cin, user_input, \n);
10
if (user_input == "quit")
11
return reactor->notify (quit_handler);
12
}
13
return 0;
14 }
Lines 36 After casting the void pointer argument back into an ACE_
Reactor pointer, we create a special concrete event handler called Quit_
Handler, which is shown shortly below.
Lines 712 We then go into a loop that waits for an administrator to
type quit on the standard input stream. When this occurs, we pass
the quit_handler to the reactor via its notify() method and exit the
controller thread.
We finally define the Quit_Handler class. Its handle_except() and
handle_close() methods simply shut down the ACE_Select_Reactors
event loop and delete the event handler, respectively, as shown below:
class Quit_Handler : public ACE_Event_Handler {
public:
Quit_Handler (ACE_Reactor *r): reactor_ (r) {}
virtual int handle_except (ACE_HANDLE) {
reactor_->end_reactor_event_loop ();
return -1; // Trigger call to handle_close() method.
}
virtual int handle_close (ACE_HANDLE, ACE_Reactor_Mask) {
delete this;
}
private:
ACE_Reactor *reactor_;
// Private destructor ensures dynamic allocation.
Quit_Handler () {}
};
i
i
i hsbo
2001/
page 8
i
3.7
81
Motivation
Although the ACE_Select_Reactor is quite flexible, its limited since only
its owner thread can call its handle_events() event loop method. The
ACE_Select_Reactor therefore serializes event processing at the demultiplexing layer, which may be overly restrictive and non-scalable for certain networked applications. One way to solve this problem is to spawn
multiple threads and run the event loop of a separate instance of ACE_
Select_Reactor in each of them. This design can be hard to program,
however, since it requires programmers to keep track of tedious bookkeeping information, such as which thread and which reactor an event handler is registered. A more effective way to address the limitations with
ACE_Select_Reactor is to use the ACE_TP_Reactor class provided by the
ACE Reactor framework.
Class Capabilities
The ACE_TP_Reactor class is another implementation of the ACE_Reactor
interface. This class implements the Leader/Followers architectural pattern [SSRB00], which provides an efficient concurrency model where multiple threads take turns calling select() on sets of I/O handles to detect, demultiplex, dispatch, and process service requests that occur. In
addition to supporting all the features of the ACE_Reactor interface, the
ACE_TP_Reactor provides the following capabilities:
i
i
i hsbo
2001/
page 8
i
82
It picks one event and dispatches its associated event handler hook
method.
It suspends the I/O handle so that other threads cant detect events
on that handle. Suspension involves removing the handle from the
handle set the reactor uses to wait on during select().
It releases the ACE_Token.
When the original leader thread returns from its dispatching upcall,
it resumes the suspended handle, which adds the handle back to the
appropriate handle set.
After the leader thread releases the token to process an event, a follower
thread becomes the new leader. The ACE_TP_Reactor can therefore allow
multiple threads to process events on different handles concurrently. In
contrast, the ACE_Select_Reactor holds the token while it dispatches to
all handlers whose handles were active in the handle set, which serializes
the reactors dispatching mechanism.
Given the added capabilities of the ACE_TP_Reactor, you may wonder
why anyone would ever use the ACE_Select_Reactor. There are several
reasons:
i
i
i hs_
2001/
page
i
83
Less overheadAlthough the ACE_Select_Reactor is less powerful than the ACE_TP_Reactor it also incurs less overhead. Moreover, single-threaded applications can instantiate the ACE_Select_
Reactor_T template with an ACE_Null_Mutex to eliminate the overhead of acquiring and releasing tokens.
Implicit serializationThe ACE_Select_Reactor is particularly useful when application-level serialization is undesirable. For example,
applications that have pieces of data coming over different handles
may prefer handling the data using the same event handler for simplicity.
Example
To illustrate the power of the ACE_TP_Reactor, well revise the main()
function from page 78 to use a pool of threads that use the Leader/Followers
pattern to take turns sharing the Reactor_Logging_Servers I/O handles.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "ace/Auto_Ptr.h"
// Forward declarations
void *controller (void *);
void *event_loop (void *);
typedef Reactor_Logging_Server<Logging_Acceptor_Ex>
Server_Logging_Daemon;
const size_t N_THREADS = 4;
int main (int argc, char *argv[])
{
size_t n_threads = argc > 1 ? atoi (argv[1]) : N_THREADS;
auto_ptr <ACE_TP_Reactor> holder (new ACE_TP_Reactor);
auto_ptr <ACE_Reactor> reactor (new ACE_Reactor (holder.get ()));
ACE_Reactor::instance (reactor.get ());
Server_Logging_Daemon server (argc, argv, reactor);
ACE_Thread_Manager::instance ()->spawn_n
(n_threads,
event_loop,
ACE_static_cast (void *, reactor.get ()));
ACE_Thread_Manager::instance ()->spawn
i
i
i hsbo
2001/
page 8
i
84
27
(controller, ACE_static_cast (void *, reactor.get ()));
28
29
return ACE_Thread_Manager::instance ()->wait ();
30 }
Lines 17 We include the relevant ACE header file, define some forward
declarations, and instantiate the Reactor_Logging_Server template with
the Logging_Acceptor_Ex class from page 58 to create a Server_Logging_
Daemon type definition.
Line 13 We determine the number of threads to include in the thread
pool.
Lines 1517 We set the implementation of the singleton ACE_Reactor to
be an ACE_TP_Reactor.
Lines 1924 We create an instance of the Server_Logging_Daemon template and then spawn n_threads, each of which runs the event_loop()
function shown on page 79.
Lines 2627 We spawn a thread to run the controller() function shown
on page 79. Note that the ACE_TP_Reactor ignores the owner() method
thats called in this function.
Line 29 We wait for the other threads to exit before exiting the main()
function.
i
i
i hsbo
2001/
page 8
i
85
i
i
i hsbo
2001/
page 8
i
86
wait for events to occur on a set of event sources. This class is the default
implementation of the ACE_Reactor on Win32 platforms. In addition to
supporting all the features of the ACE_Reactor interface, the ACE_WFMO_
Reactor class provides the following capabilities:
ACE_WFMO_Reactor inherits from ACE_Reactor_Impl, as shown in Figure 3.6. It can therefore serve as a concrete implementation of the ACE_
Reactor interface. Just as the internals of the ACE_Select_Reactor are
designed to leverage the capabilities of select(), ACE_WFMO_Reactor is
designed to leverage the capabilities of WaitForMultipleObjects(). Its
two most significant differences from the ACE_Select_Reactor are:
Description
Creates a Win32 event object whose handle can be passed to WaitForMultipleObjects().
WSAEventSelect()
Associates a set of event types on a given
socket handle with an event handle. The
occurrence of any of the events causes the
event handle to become signaled.
WSAEnumNetworkEvents() Retrieves the set of events that have occurred on a socket handle after its associated event handle is signaled.
i
i
i hsbo
2001/
page 8
i
87
Multiple event loop threadsIts legal for multiple threads to execute WaitForMultipleObjects() concurrently on the same set of
I/O handles. This feature complicates how ACE_WFMO_Reactor registers and unregisters event handlers since multiple threads accessing
a set of registered handlers may be affected by each change. To execute changes to the registered handler set correctly, the ACE_WFMO_
Reactor therefore defers changes until the internal action stablizes
and the changes can be made safely.
The manner in which the ACE_WFMO_Reactor defers changes makes
one aspect of its behavior different from the ACE_Select_Reactor. In
particular, when an event handlers handle_close() hook method is
invoked (e.g., due to one of the handle_*() methods returning ,1
or by calling ACE_Reactor::remove_handler()), the call to handle_
close() is deferred until the ACE_WFMO_Reactors internal records
are updated. This update may not occur until some time after the
point at which the application requests the event handlers removal.
This means that an application cant delete an event handler immediately after requesting its removal from an ACE_WFMO_Reactor since
the handle_close() method may not have been called on the event
handler yet.
The two areas of difference described above show that two dissimilar
event demultiplexing mechanisms can be used effectively and portably by
applying patterns and abstraction principles to decouple the common interface from the divergent implementations.
i
i
i hs_
2001/
page
i
88
Example
This example illustrates how to use the I/O handling capabilities of the
ACE_WFMO_Reactor to shut down the Reactor_Logging_Server without
the need for an additional controller thread. To accomplish this, we define
a different Quit_Handler class than the one shown on page 80.
class Quit_Handler : public ACE_Event_Handler {
private:
// Must be implemented by the <ACE_WFMO_Reactor>.
ACE_Reactor *reactor_;
// Private destructor ensures dynamic allocation.
Quit_Handler () {}
public:
When an event occurs on standard input, the ACE_WFMO_Reactor dispatches the following handle_signal() method, which checks to see if
an administrator wants to shut down the reactors event loop.
i
i
i hs_
2001/
page
i
89
The main() function is similar to the one shown on page 83, with the
main differences being
i
i
i hsbo
2001/
page 9
i
90
ACE_Thread_Manager::instance ()->spawn_n
(n_threads,
event_loop,
ACE_static_cast (void *, reactor.get ()));
return ACE_Thread_Manager::instance ()->wait ();
}
3.9 Summary
This chapter shows how the ACE Reactor framework helps to simplify the
development of event-driven networked applications by applying
The ACE Reactor framework provides reusable classes that perform all the
lower-level event detection, demultiplexing, and event handler dispatching. Only a small amount of application-defined code is therefore required
to implement event-driven applications, such as the logging server shown
in Sections 3.3 through 3.5. For example, the code in Sections 3.3 and 3.4
is concerned with application-defined processing activities, such as receiving log records from clients. All applications that reuse the ACE Reactor
framework can leverage the knowledge and experience of its skilled middleware developers, as well as its future enhancements and optimizations.
The ACE Reactor framework uses dynamic binding extensively since
the dramatic improvements in clarity, extensibility, and modularity provided by the ACE Reactor framework compensate for any decrease in efficiency resulting from indirect virtual table dispatching [HLS97]. The Reactor framework is often used to develop networked applications where
the major sources of overhead result from caching, latency, network/host
interface hardware, presentation-level formatting, dynamic memory allocation and copying, synchronization, and concurrency management. The
additional indirection caused by dynamic binding is therefore usually insignificant by comparison [Koe92]. In addition, good C++ compilers can
optimize virtual method overhead away completely via the use of adjustor
thunks [Sta96].
i
i
i hsbo
2001/
page 9
i
C HAPTER 4
C HAPTER S YNOPSIS
This chapter describes the design and use of the ACE Service Configurator framework. This framework implements the Component Configurator
pattern [SSRB00], which increases system extensibility by decoupling the
behavior of services from the point of time when implementations of these
services are configured into application processes. We illustrate how the
ACE Service Configurator framework can help to improve the extensibility
of our networked logging server.
4.1
Overview
Section 2.2 described the naming and linking design dimensions that developers need to consider when configuring networked applications. An
extensible strategy for addressing these design dimensions is to apply the
Component Configurator design pattern [SSRB00]. This pattern allows an
application to link and unlink its services at run-time without having to
modify, recompile, or relink an application statically. This pattern also
supports the reconfiguration of services in an application process without
having to shut down and restart a running process.
The ACE Service Configurator framework is a portable implementation
of the Component Configuration pattern that allows applications to defer
configuration and/or implementation decisions about their services until
91
i
i
i hsbo
2001/
page 9
i
92
ACE_Service_Repository
ACE_Service_Repository_Iterator
ACE_Service_Config
Description
Defines a uniform interface that the
ACE Service Configurator framework
uses to configure and control the type
of application service or functionality provided by a service implementation. Common control operations include initializing, suspending, resuming, and terminating a service.
Manages all the services offered by
a Service Configurator-based application and allows an administrator to
control the behavior of application services.
Provides a portable mechanism for iterating through all the services in a
repository.
Coordinates the (re)configuration of
services by implementing a mechanism that interprets and executes
scripts specifying which services to
(re)configure into the application, e.g.,
by linking and unlinking dynamically
linked libraries (DLLs), and which services to suspend and resume.
Configuration management layer classes that perform applicationindependent strategies to install, initialize, control, and terminate
service objects. The classes in the configuration management layer
in the ACE Service Configurator framework include ACE_Service_
i
i
i hsbo
2001/
page 9
i
93
UniformityThe framework imposes a uniform interface for managing the (re)configuration of networked services. This uniformity allows services to be treated as building blocks that can be composed
to form complete applications. Enforcing a common interface across
all services also ensures that they support the same management operations, such as initializing, suspending, resuming, and terminating
a service.
Centralized administrationThe framework groups one or more services in an application into a single administrative unit. By default,
the framework configures an application process by reading commands from a file called svc.conf. Alternative configuration files
can be specified by command-line options or by supplying commands
to ACE_Service_Config directly.
Modularity, testability, and reusabilityThe framework improves
application modularity and reusability by decoupling the implementation of services from the manner in which the services are configured
into application processes. This flexibility allows service implementations the freedom to evolve over time largely independent of configuration issues, such as which services should be collocated or what
concurrency model will be used to execute the services. In addition,
each service can be developed and tested independently, which simplifies service composition.
Enhanced dynamism and controlBy decoupling the applicationdefined portions of an object from the underlying platform configuration mechanisms, the framework enables a service to be reconfigured dynamically without modifying, recompiling, or statically relinking existing code. It may also be possible to reconfigure a service
without restarting the service itself or other services with which its
i
i
i hsbo
2001/
page 9
i
94
collocated. Such dynamic reconfiguration capabilities are often required for applications with high availability requirements, such as
mission-critical systems that perform on-line transaction processing
or real-time industrial process automation.
Tuning and optimizationThe framework increases the range of
service configuration alternatives available to developers by decoupling service functionality from service execution mechanisms, which
enables the optimization of certain service implementation or configuration parameters. For instance, depending on the parallelism available on the hardware and operating system, it may be either more or
less efficient to run multiple services in separate threads or separate
processes. The Service Configurator framework enables applications
to select and tune these behaviors flexibly at run-time, when more
information is available to help match client demands and available
system processing resources.
i
i
i hsbo
2001/
page 9
i
95
Description
A hook method used by the Service Configurator framework to instruct a service to initialize itself. A pair of argc/argv-style parameters can be passed to init() and used to control the initialization of a service.
fini()
A hook method used by the Service Configurator framework to
instruct a service to terminate itself. This method typically performs termination operations that release dynamically allocated
resources, such as memory, synchronization locks, or I/O descriptors.
suspend() Hook methods used by the Service Configurator framework to inresume() struct a service to suspend and resume its execution.
info()
A hook method used by the Service Configurator framework to
query a service for certain information about itself, such as its
name, purpose, and network address. Clients can query a server to
retrieve this information and use it to contact a particular service
running in a server.
i
i
i hsbo
2001/
page 9
i
96
i
i
i hs_
2001/
page
i
97
i
i
i hsbo
2001/
page 9
i
98
13
*bufferp = ACE_OS::strnew (buf);
14
ACE_OS::strcpy (*bufferp, buf);
15
return ACE_OS::strlen (buf);
16 }
Lines 15 Obtain the network address object from the instance of ACE_
SOCK_Acceptor thats used by the Reactor_Logging_Server.
Lines 712 Format an informative message that explains what the service does and how to contact it.
Lines 1315 Allocate a new memory buffer dynamically, store the formatted description string into this buffer, and return the buffers length.
The caller is responsible for deleting the buffer.
The suspend() and resume() methods are similar to each other, as
shown below:
template <class ACCEPTOR> int
Reactor_Logging_Server_Adapter<ACCEPTOR>::suspend ()
{
return ACE_Reactor::instance ()->suspend_handler (server_);
}
template <class ACCEPTOR> int
Reactor_Logging_Server_Adapter<ACCEPTOR>::resume ()
{
return ACE_Reactor::instance ()->resume_handler (server_);
}
i
i
i hsbo
2001/
page 9
i
Section 4.3 The ACE Service Repository and ACE Service Repository Iterator Classes
99
4.3
The ACE Service Repository and ACE Service Repository Iterator Classes
Motivation
The ACE Service Configurator framework supports the configuration of
both single-service and multi-service servers. To simplify run-time administration of these servers, its often necessary to individually and/or
collectively access and control the service objects that comprise a servers
currently active services. Rather than expecting application developers to
provide these capabilities in an ad hoc way, the ACE Service Configurator framework defines the ACE_Service_Repository and ACE_Service_
Repository_Iterator classes.
Class Capabilities
The ACE_Service_Repository implements the Manager pattern [Som97]
to control the life-cycle ofand the access toservice objects configured by
the ACE Service Configurator framework. This class provides the following
capabilities:
The interface for the ACE_Service_Repository class is shown in Figure 4.3 and its key methods are outlined in the following table:
i
i
i hsbo
2001/
page 1
i
100
Section 4.3 The ACE Service Repository and ACE Service Repository Iterator Classes
Method
ACE_Service_Repository()
open()
ACE_Service_Repository()
close()
insert()
find()
remove()
suspend()
resume()
instance()
Description
Initialize the repository and allocate its dynamic resources.
Close down the repository and release its dynamically allocated resources.
Add a new service into the repository.
Locate an entry in the repository.
Remove an existing service from the repository.
Suspend a service in the repository.
Resume a suspended service in the repository.
A static method that returns a pointer to a singleton ACE_Service_Repository.
i
i
i hsbo
2001/
page 1
i
Section 4.3 The ACE Service Repository and ACE Service Repository Iterator Classes
101
The ACE_Service_Repository_Iterator implements the Iterator pattern [GHJV95] to provide a way to access the ACE_Service_Type items
in an ACE_Service_Repository sequentially without exposing its internal
representation. The interface for the ACE_Service_Repository_Iterator
class is shown in Figure 4.4 and its key methods are outlined in the following table:
i
i
i hs_
2001/
page
i
102
Section 4.3 The ACE Service Repository and ACE Service Repository Iterator Classes
Method
ACE_Service_Repository_Iterator()
next()
done()
advance()
Description
Initialize the iterator.
Passes back the next unseen ACE_
Service_Type in the repository.
Returns 1 when all items have been
seen, else 0.
Move forward by one item in the
repository.
i
i
i hsbo
2001/
page 1
i
Section 4.3 The ACE Service Repository and ACE Service Repository Iterator Classes
103
Report servicesIf the command help is sent, a list of all services configured into an application via the ACE Service Configurator
framework is returned to the client.
ReconfigureIf the command reconfigure is sent, reconfiguration is triggered that will reread the local service configuration file.
Process directiveIf neither help nor reconfigure is sent,
the clients command is passed to the ACE_Service_Config::
process_directive() method, which is described on page 112.
This feature enables remote configuration of servers via commandline instructions like
% echo "suspend My_Service" | telnet hostname 9411
// Hook
virtual
virtual
virtual
virtual
virtual
private:
// Acceptor instance.
ACE_SOCK_Acceptor acceptor_;
enum { DEFAULT_PORT = 9411 };
};
i
i
i hs_
2001/
page
i
104
Section 4.3 The ACE Service Repository and ACE Service Repository Iterator Classes
Line 2 Initialize the local_addr to the default TCP port number used by
Service_Reporter.
Lines 512 Parse the service configuration options using the ACE_Get_
Opt class described in Sidebar 12. If the -p option is passed into init()
then the local_addr port number is reset to that value.
Lines 1418 Initialize the ACE_SOCK_Acceptor to listen on the local_
addr port number and then register the instance of Service_Reporter
with the singleton reactor.
When a connection request arrives from a client, the singleton reactor dispatches the following Service_Reporter::handle_input() hook
method:
1 int Service_Reporter::handle_input (ACE_HANDLE) {
2
// Connection to the client (we only support
3
// one client connection at a time).
i
i
i hsbo
2001/
page 1
i
Section 4.3 The ACE Service Repository and ACE Service Repository Iterator Classes
105
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 }
ACE_SOCK_Stream peer_stream;
acceptor_.accept (peer_stream);
ACE_Service_Repository_Iterator iterator
(*ACE_Service_Repository::instance (), 0);
for (const ACE_Service_Type *st;
iterator.next (st) != 0;
iterator.advance ()) {
iovec iov[3];
iov[0].iov_base = st->name ();
iov[0].iov_len = strlen (iov[0].iov_base);
iov[1].iov_base =
st->active () ? " (active) " : " (paused) ";
iov[1].iov_len = strlen (" (active) ");
iov[2].iov_len = st->type ()->info (&iov[2].iov_base);
peer_stream.sendv_n (iov, 3);
delete [] iov[2].iov_base;
}
peer_stream.close ();
return 0;
i
i
i hs_
2001/
page
i
106
Section 4.3 The ACE Service Repository and ACE Service Repository Iterator Classes
Lines 1022 Iterate through each service, invoke their info() method
to obtain a descriptive synopsis of the service, and send this information
back to the client via the connected socket.
Line 24 Close down the connection to the client and release the socket
handle.
The Service_Reporter::info() hook method passes back a string
that explains what the service does and how to connect to it:
int Service_Reporter::info (ACE_TCHAR **bufferp,
size_t length = 0) const {
ACE_INET_Addr sa;
acceptor_.get_local_addr (sa);
ACE_TCHAR buf[BUFSIZ];
sprintf (buf,
"%d/%s %s",
sa.get_port_number (),
"tcp",
"# lists all services in the daemon\n");
*bufferp = ACE_OS::strnew (buf);
ACE_OS::strcpy (*bufferp, buf);
return ACE_OS::strlen (buf);
}
As with the info() method on page 97, a caller must delete the dynamically allocated buffer.
The Service_Reporters suspend() and resume() hook methods forward to the corresponding methods in the reactor singleton, as follows:
int Service_Reporter::suspend () {
return ACE_Reactor::instance ()->suspend_handler (this);
}
int Service_Reporter::resume () {
return ACE_Reactor::instance ()->resume_handler (this);
}
i
i
i hsbo
2001/
page 1
i
Section 4.3 The ACE Service Repository and ACE Service Repository Iterator Classes
107
ACE_Reactor::instance ()->remove_handler
(this, ACE_Event_Handler::ACCEPT_MASK);
}
ACE_SVC_FACTORY_DEFINE (Service_Reporter);
ACE_STATIC_SVC_DEFINE {
Service_Reporter,
"Service_Reporter",
ACE_SVC_OBJ_T,
&ACE_SVC_NAME (Service_Reporter),
ACE_Service_Types::DELETE_THIS |
ACE_Service_Types::DELETE_OBJ,
0 // This object is not initially active.
};
ACE_STATIC_SVC_REQUIRE (Service_Reporter);
DEFINE
i
i
i hsbo
2001/
page 1
i
108
Section 4.3 The ACE Service Repository and ACE Service Repository Iterator Classes
ACE_SVC_FACTORY_DEFINE(T)
ACE_STATIC_SVC_DECLARE(T)
ACE_STATIC_SVC_DEFINE
(T,
NAME,
TYPE,
FUNC,
FLAGS,
ACTIVE)
ACE_STATIC_SVC_REQUIRE(T)
Description
Used in a header file to declare a factory
method for creating service objects of type
T. In this case, T is the name of a descendant of ACE_Service_Object.
Used in an implementation file to define a factory function that will create
a new object of type T. This function
will match the declaration created by
ACE SVC FACTORY DECLARE () and will also
define an exterminator function for deleting
the object created by the factory.
Used in a header file to declare that type T
will be used as a statically linked service.
Used in an implementation file to define an object of type ACE_Static_Svc_
Descriptor. Parameter T is the class being defined as a static service and the remaining parameters match the attributes
of the ACE_Static_Svc_Descriptor class.
In order to use the function defined by
ACE SVC FACTORY DEFINE () as the allocator
function the value assigned to FUNC should
be &ACE_SVC_NAME(T).
Used in an implementation file to add
the ACE_Static_Svc_Descriptor defined
by the ACE STATIC SVC DEFINE macro to
the ACE_Service_Configs list of static services.
Lines 311 The ACE STATIC SVC DEFINE macro initializes an instance of
ACE_Static_Svc_Descriptor, which is a class that stores the information needed to describe a statically configured service. Service_Reporter
is the service objects class name and "Service_Reporter" is the name
used to identify the service. ACE SVC OBJ T is the type of service ob-
i
i
i hsbo
2001/
page 1
i
109
4.4
Motivation
Before a service can execute, it must be configured into an applications address space. One way to configure the services that comprise a networked
application is to statically link the functionality provided by its various
classes and functions into separate OS processes. We used this approach
in the logging server examples in Chapter 3 and throughout [SH02], where
the logging server program runs in a process that handles log records from
client applications. Although our use of the ACE Reactor framework in the
previous chapter improved the modularity and portability of the networked
logging server, the following drawbacks arose from statically configurating
the Reactor_Logging_Server class with its main() program:
Service configuration decisions are made prematurely in the development cycle, which is undesirable if developers dont know the
best way to collocate or distribute services a priori. Moreover, the
best configuration may change as the computing context changes.
For example, an application may write log records to a local file when
its running on a disconnected laptop computer. When the laptop is
connected to a LAN, however, it may forward log records to a centralized logging server.1 Forcing networked applications to commit
If wait a minuteyou dont need a logging service for local disk file logging! comes
to mind, youre correct. ACE makes the switch easy via the ACE_Logging_Strategy class
thats covered in Appendix ??.
i
i
i hsbo
2001/
page 1
i
110
prematurely to a particular service configuration impedes their flexibility and can reduce performance and functionality, as well as incur
costly redesign and reimplementation later in a projects lifecycle.
Modifying a service may adversely affect other services if the implementation of a service is coupled tightly with its initial configuration. To enhance reuse, for example, a logging server may initially
reside in the same program as other services, such as a name service.
If the name service lookup algorithm is changed, however, all existing code in the server would require modification, recompilation, and
static relinking. Moreover, terminating a running process to change
its name service code would also terminate the collocated logging service. This disruption in service may not be acceptable for highly available systems, such as telecommunication switches or customer care
call centers [SS94].
System performance may scale poorly since associating a separate
process with each service ties up OS resources, such as I/O descriptors, virtual memory, and process table slots. This design is particularly wasteful if services are often idle. Moreover, processes can be
inefficient for many short-lived communication tasks, such as asking
a time service for the current time or resolving a host address request
via the Domain Name Service (DNS).
To address these drawbacks, the ACE Service Configurator framework defines the ACE_Service_Config class.
Class Capabilities
The ACE_Service_Config class implements the Facade pattern [GHJV95]
to integrate the other ACE Service Configurator classes and coordinate the
activities necessary to manage the services in an application process. This
class provides the following capabilities:
It provides mechanisms to dynamically link and unlink service implementations into and out of an application process.
It interprets a simple scripting language that allows applications or
administrators to provide the ACE Service Configurator framework
with commands, called directives, to locate and initialize a services implementation at run-time, as well as to suspend, resume,
re-initialize, and/or terminate a component after its been initialized.
This interpretation can (re)configure an application process either
i
i
i hsbo
2001/
page 1
i
111
ACE Class
ACE_Service_Config()
open()
ACE_Service_Config()
close()
Description
These methods create and initialize the ACE_
Service_Config.
These methods shut down and finalize all the configured services and deletes the resources allocated when the ACE_Service_Config was initialized.
i
i
i hsbo
2001/
page 1
i
112
Option
-b
-d
-f
-n
-s
-S
-y
Description
Turn the application process into a daemon.
Turn on debugging mode, which displays diagnostic information as
directives are processed.
Supply a file containing directives other than the default svc.conf
file. This argument can be repeated to process multiple configuration
files.
Dont process any static directives, which eliminates the need to
initialize the ACE_Service_Repository statically.
Designate the signal to be used to cause the ACE_Service_Config to
reprocess its configuration file. By default, SIGHUP is used.
Supply a directive to the ACE_Service_Config directly. This argument can be repeated to process multiple directives.
Process static directives, which requires the static initialization of
the ACE_Service_Repository.
2. Service configuration methods. After parsing all its argc/argv arguments, the ACE_Service_Config::open() method calls one or both of
the following methods to configure the server:
Method
process_directives()
process_directive()
Description
Process a sequence of directives that are stored
in a designated script file, which defaults to svc.
conf. This method allows multiple directives to
be stored persistently and processed iteratively in
batch-mode. This method executes each service
configuration directive in a svc.conf file in the order in which they are specified.
Process a single directive passed as a string parameter. This method allows directives to be created dynamically and/or processed interactively.
Description
Dynamically link and initialize a service.
Initialize a service that was linked statically.
Remove a service completely, e.g., unlink it from the application
process.
Suspend service without completely removing it.
Resume a service that was suspended earlier.
Initialize an ordered list of hierarchically-related modules.
i
i
i hsbo
2001/
page 1
i
113
We describe the syntax and semantics for the tokens in each of these directives below.
i
i
i hsbo
2001/
page 1
i
114
Description
Opens and dynamically links a designated DLL.
Closes the DLL.
Return a pointer to the designated symbol in the symbol table of the DLL.
Return a string explaining which failure occurred.
are the same as those in the dynamic directive. The syntax is simpler, however, since the service object must be linked into the program
statically, rather than linked dynamically. Static configuration trades
flexibility for increased security, which may be useful for certain types
of servers that must contain only trusted, statically linked services.
Remove a service completely: remove svc_name
The remove directive causes the ACE_Service_Config to query the
i
i
i hsbo
2001/
page 1
i
115
The complete Backus/Naur Format (BNF) syntax for svc.conf files parsed
by the ACE_Service_Config is shown in Figure 4.6. Sidebar 15 describes
how to specify svc.conf files using XML syntax.
3. Utility methods. The key methods are outlined below:
Method
()
Description
.
i
i
i hs_
2001/
page
i
116
Figure 4.6: BNF for the ACE Service Config Scripting Language
Example
This example illustrates how to apply ACE_Service_Config and the other
classes in the ACE Service Configurator framework to configure a server
that behaves as follows:
We then show how to dynamically reconfigure the server to support a different implementation of a reactor-based logging service.
Initial Configuration. The main() program below configures the Service_
Reporter and Reactor_Logging_Server_Adapter services into an application process and then runs the reactors event loop.
i
i
i hsbo
2001/
page 1
i
117
#include "ace/Service_Config.h"
#include "ace/Reactor.h"
int main (int argc, char *argv[])
{
ACE_Service_Config::open (argc, argv);
ACE_Reactor::instance ()->run_reactor_event_loop ();
return 0;
}
Line 1 Initialize the Server_Reporter instance that was linked statically together with the main() program. The ACE STATIC SVC REQUIRE
macro used in the Service_Reporter.cpp file on page 107 ensures the
Service_Reporter object is registered with the ACE_Service_Repository
before the ACE_Service_Config::open() method is called.
Line 34 Dynamically link the SLD DLL into the address space of the
process and use ACE_DLL to extract the make_Server_Logging_Daemon()
factory function from the SLD symbol table. This function is called to obtain
a pointer to a dynamically allocated Server_Logging_Daemon. The framework then calls the Server_Logging_Daemon::init() hook method on
this pointer, passing in the "$SERVER_LOGGING_DAEMON_PORT" string as
its argc/argv argument. This string designates the port number where the
server logging daemon listens for client connection requests. If init() succeeds, the Server_Logging_Daemon pointer is stored in the ACE_Service_
Repository under the name "Server_Logging_Daemon".
The SLD DLL is generated from the following SLD.cpp file:
i
i
i hsbo
2001/
page 1
i
118
typedef Reactor_Logging_Server_Adapter<Logging_Acceptor>
Server_Logging_Daemon;
ACE_SVC_FACTORY_DEFINE (Server_Logging_Daemon);
This file defines a typedef called Server_Logging_Daemon that instantiates the Reactor_Logging_Server_Adapter template with the Logging_
Acceptor shown on page 50 of Section 3.3. The ACE SVC FACTORY DEFINE
macro is then used to generate the make_Server_Logging_Daemon() factory function automatically.
The UML state diagram in Figure 4.7 illustrates the steps involved in
configuring the server logging daemon based on the svc.conf file shown
above. When the OPEN event occurs at run-time, the ACE_Service_Config
Figure 4.7: A State Diagram for Configuring the Server Logging Daemon
class calls process_directives(), which consults the svc.conf file.
When all the configuration activities have been completed, the main()
program invokes the ACE_Reactor::run_reactor_event_loop() method,
which in turn calls the Reactor::handle_events() method continuously.
As shown in Figure 4.7, this method blocks awaiting the occurrence of
events, such as connection requests or data from clients. As these events
occur, the reactor dispatches the handle_input() method of concrete
event handlers automatically to perform the designated services.
Reconfigured Server. The ACE Service Configurator framework can be
programmed to reconfigure a server at run-time in response to external
events, such as the SIGHUP or SIGINT signal. At this point, the framework rereads its configuration file(s) and performs the designated directives, such as inserting or removing service objects into or from a server,
and suspending or resuming existing service objects. We now illustrate
how to use these features to dynamically reconfigure our server logging
daemon.
The initial configuration of the logging server shown above used the
Logging_Acceptor implementation from page 50 of Section 3.3. This implementation didnt timeout logging handlers that remained idle for long
periods of time. To add this capability without affecting existing code or
the Service_Reporter service in the process, we can simply define a new
i
i
i hsbo
2001/
page 1
i
119
svc.conf file and instruct the server to reconfigure itself by sending it the
appropriate signal.
1 remove Server_Logging_Daemon
2
3 dynamic Server_Logging_Daemon Service_Object *
4 SLDex:make_Server_Logging_Daemon_Ex() "$SERVER_LOGGING_DAEMON_PORT"
Line 1 Remove the existing server logging daemon from the ACE_Service_
Repository and unlink it from the applications address space.
Lines 34 Configure a different instantiation of the Reactor_Logging_
Server_Adapter template into the address space of the server logging
daemon. In particular, the make_Server_Logging_Daemon_Ex() factory
function shown in the SLDex.cpp file below instantiates the Reactor_
Logging_Server_Adapter template with the Logging_Acceptor_Ex shown
on page 58 of Section 3.4.
typedef Reactor_Logging_Server_Adapter<Logging_Acceptor_Ex>
Server_Logging_Daemon_Ex;
ACE_SVC_FACTORY_DEFINE (Server_Logging_Daemon_Ex);
The UML state diagram in Figure 4.8 illustrates the steps involved in
reconfiguring the server logging daemon based on the svc.conf file shown
above.
Figure 4.8: A State Diagram for Reconfiguring the Server Logging Daemon
The dynamic reconfiguration mechanism in the ACE Service Configurator framework enables developers to modify server functionality or finetune performance without extensive redevelopment and reinstallation effort. For example, debugging a faulty implementation of the logging service
can simply involve the dynamic reconfiguration of a functionally equivalent service that contains additional instrumentation to help identify the
erroneous behavior. This reconfiguration process may be performed without modifying, recompiling, relinking, or restarting the currently executing
server logging daemon. In particular, this reconfiguration neednt affect
the Service_Reporter that was configured statically.
i
i
i hsbo
2001/
page 1
i
120
4.5 Summary
This chapter described the ACE Service Configurator framework, which allows services to be initiated, suspended, resumed, and terminated dynamically and/or statically. This framework helps to improve the extensibility
and performance of networked application software by deferring service
configuration decisions until late in the design cycle, i.e., at installationtime and/or at run-time, without changing the application or server implementations.
We applied the ACE Service Configurator pattern to enhance the networked logging service example described in previous chapters. The result
is a networked logging service that can be configured and deployed in various ways via the ACE Service Configurator framework. The logging service
provides a good example of why its useful to defer configuration decisions
until run-time. The extensibility afforded by the ACE Service Configurator framework allows operators and administrators to dynamically select
the features and alternative implementation strategies that make the most
sense in a particular context, as well as make localized decisions on how
best to initialize them.
i
i
i hsbo
2001/
page 1
i
C HAPTER 5
C HAPTER S YNOPSIS
This chapter describes the design and use of the ACE Task framework,
which can be used to implement common concurrency patterns [SSRB00],
such as Active Object and Half-Sync/Half-Async. We show how to apply
the ACE Task framework to enhance the concurrency of various parts of
our networked logging service.
5.1
Overview
i
i
i hsbo
2001/
page 1
i
122
This chapter explains how these patternsand the ACE Task framework
that reifies themcan be applied to develop concurrent object-oriented
applications at a level higher than existing C APIs and the ACE_Thread_
Manager C++ wrapper facade. We focus on the following ACE Task framework classes that networked applications can use to spawn, manage, and
communicate between one or more threads within a process:
ACE Class
ACE_Message_Queue
ACE_Task
Description
Provides a powerful message queueing facility that enables applications to pass and queue messages between threads in a process.
Allows applications to create active objects that can
queue and process messages concurrently.
SYNCH
ACE_Thread_Manager
ACE_Task
0..1
SYNCH
ACE_Message_Block
ACE_Message_Queue
*
Improve the consistency of programming style by enabling developers to use C++ and object-oriented techniques throughout their
i
i
i hsbo
2001/
page 1
i
123
5.2
Motivation
Although some operating systems supply intra-process message queues
natively, this capability isnt available on all OS platforms. When it is
offered, moreover, its either:
i
i
i hsbo
2001/
page 1
i
124
Description
Initialize the queue.
Shutdown the queue and release its resources.
Checks if the queue is empty/full.
Insert a message at the back of the queue.
Insert a message at the head of the queue.
Insert a message according to its priority.
Remove and return the message at the front of the
queue.
i
i
i hsbo
2001/
page 1
i
125
SYNCH_STRATEGY
ACE_Message_Queue
#
#
#
#
head_ : ACE_Message_Block *
tail_ : ACE_Message_Block *
high_water_mark_ : size_t
low_water_mark_ : size_t
+
+
+
+
+
+
+
+
int
int
int
int
Figure 5.3 illustrates how simple and composite messages can be linked
together to form an ACE_Message_Queue. To optimize insertion and deletion in a queue, ACE_Message_Block messages are linked bi-directionally
i
i
i hsbo
2001/
page 1
i
126
SYNCH STRATEGY
ACE_Message
_Block
ACE_Message
_Queue
next_
prev_
cont_
head_
tail_
ACE_Data_Block
ACE_Message
_Block
ACE_Message
_Block
next_
prev_
cont_
next_
prev_
cont_
ACE_Data_Block
ACE_Message
_Block
next_
prev_
cont_
ACE_Data_Block
ACE_Data_Block
i
i
i hsbo
2001/
page 1
i
127
cient number of bytes of messages are dequeued. The low water mark indicates the number of message bytes at which a previously flow controlled
ACE_Message_Queue is no longer considered full. The Example portion of
Section 5.3 illustrates the use of high and low water marks to exert flow
control within a multithreaded logging server.
2. Parameterized synchronization strategies. If you look carefully at
the ACE_Message_Queue template in Figure 5.2 youll see that its parameterized by a SYNCH_STRATEGY class. This design is based on the Strategized Locking pattern [SSRB00], which parameterizes the synchronization
mechanisms that a class uses to protect its critical sections from concurrent access. Internally, the ACE_Message_Queue class uses the following
traits from its SYNCH_STRATEGY template parameter:
template <class SYNCH_STRATEGY>
class ACE_Message_Queue
{
// ...
protected:
// C++ traits that coordinate concurrent access.
typename SYNCH_STRATEGY::MUTEX lock_;
typename SYNCH_STRATEGY::CONDITION notempty_;
typename SYNCH_STRATEGY::CONDITION notfull_;
};
i
i
i hsbo
2001/
page 1
i
128
The ACE_NULL_SYNCH class is an example of the Null Object pattern [Woo97], which simplifies applications by defining a no-op placeholder that removes conditional statements in a class implementation. ACE_NULL_SYNCH is often used in single-threaded applications,
or in applications where the need for inter-thread synchronization
has been either eliminated via careful design or implemented via some
other mechanism.
ACE_MT_SYNCHThe traits in this pre-defined class are implemented
in terms of actual locking mechanisms, as shown below:
class ACE_MT_SYNCH
{
public:
typedef ACE_Thread_Mutex MUTEX;
typedef ACE_Null_Mutex NULL_MUTEX;
typedef ACE_Process_Mutex PROCESS_MUTEX;
typedef ACE_Recursive_Thread_Mutex RECURSIVE_MUTEX;
typedef ACE_RW_Thread_Mutex RW_MUTEX;
typedef ACE_Condition_Thread_Mutex CONDITION;
typedef ACE_Thread_Semaphore SEMAPHORE;
typedef ACE_Null_Semaphore NULL_SEMAPHORE;
};
The ACE_MT_SYNCH traits class defines a strategy with portable, efficient synchronization classes suitable for multi-threaded applications.
Parameterizing the ACE_Message_Queue template with a traits class
provides the following benefits:
i
i
i hsbo
2001/
page 1
i
129
both single-threaded or multi-threaded configurations, without requiring changes to the class implementation and
i
i
i hsbo
2001/
page 1
i
130
Value
ACE_Time_Value
pointer
NULL
Non-NULL ACE_Time_Value
pointer whose sec() and
usec() methods return 0
A non-NULL
ACE_Time_Value pointer
whose sec() or usec()
method returns > 0
Behavior
Indicates that the enqueue or dequeue method
should wait indefinitely, i.e., it will block until
the method completes, the queue is closed, or a
signal occurs.
Indicates that enqueue and dequeue methods
should perform a peek, i.e., if the method
doesnt succeed immediately, return ,1 and set
errno to EWOULDBLOCK.
Indicates that enqueue or dequeue method
should wait until the absolute time elapses, returning ,1 with errno set to EWOULDBLOCK if
the method does not complete by this time.
Strategized LockingC++ traits are used to strategize the synchronization mechanism in accordance with the Strategized Locking pattern [SSRB00].
Monitor ObjectWhen ACE_Message_Queue is parameterized by ACE_
MT_SYNCH its methods behave as synchronized methods in accordance with the Monitor Object pattern [SSRB00].
Thread-Safe InterfaceThe public methods acquire locks and delegate to the private implementation methods, which assume locks are
held and actually enqueue/dequeue messages.
Scoped LockingThe ACE_GUARD* macros from Chapter 10 of [SH02]
ensure that any synchronization wrapper facade whose signature conforms to the ACE_LOCK* pseudo-class is acquired and released automatically in accordance with the Scoped Locking idiom [SSRB00].
These and other patterns from the POSA2 book are used to implement key
portions of the ACE Task framework. A message queue implementation
similar to the ACE_Message_Queue is also shown in Chapter 10 of [SH02].
Example
The following example shows how to use the ACE_Message_Queue to implement a client logging daemon. As described in Section 1.5 on page 17,
i
i
i hsbo
2001/
page 1
i
131
"ace/OS.h"
"ace/Message_Queue.h"
"ace/Synch.h"
"ace/Thread_Manager.h"
"ace/SOCK_Connector.h"
"ace/Reactor.h"
"Logging_Acceptor.h"
Since the client logging daemon design uses different logic than the
logging server we cant reuse our Reactor_Logging_Server class from the
i
i
i hs_
2001/
page
i
132
CLIENT
APPLICATIONS
P1
LOCAL
P2
P3
IPC
Logging
Handlers
Logging
Acceptor
Message
Queue
CONSOLE
Logging
Handler
TCP
CONNECTION
LOGGING
SERVER
CLIENT
TANGO
NETWORK
i
i
i hsbo
2001/
page 1
i
133
Lines 28 If the handle argument matches the ACE_SOCK_Acceptor factorys handle, we accept the connection and then use the three parameter
variant of register_handler() to register the Client_Logging_Daemon
i
i
i hs_
2001/
page
i
134
object with the singleton reactor for READ events. This reactor method enables the client logging daemon to reuse a single C++ object for its acceptor
factory and all of its logging handlers.
Lines 1019 If the handle argument is not the ACE_SOCK_Acceptor factory handle then it must be a handle to a connected client application
socket. In this case, we read a log record out of the socket handle parameter, store the record into an ACE_Message_Block, and insert this message
into the synchronized queue serviced by the forwarder thread.
If a client application disconnects or if a communication error occurs,
the handle_input() hook method returns ,1. This value triggers the
reactor to call the following handle_close() hook method that cleans up
all the Client_Logging_Daemons resources:
int Client_Logging_Daemon::handle_close (ACE_HANDLE,
ACE_Reactor_Mask) {
if (acceptor_.get_handle () != ACE_INVALID_HANDLE) {
message_queue_.close ();
acceptor_.close ();
// The close() method sets the handle to ACE_INVALID_HANDLE.
}
return 0;
}
Note that we neednt delete this object in handle_close() since the ACE
Service Configurator framework is responsible for deleting a service object
after calling its fini() hook method.
We next show the Client_Logging_Daemon::forward() method, which
establishes a connection with the server logging daemon and forwards log
records to it, as shown below:
1 int Client_Logging_Daemon::forward () {
2
// Connection establishment and data transfer objects.
i
i
i hsbo
2001/
page 1
i
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 }
135
ACE_SOCK_Stream logging_peer;
ACE_SOCK_Connector connector;
ACE_INET_Addr server_addr;
server_addr.set ("ace_logger", LOGGING_SERVER_HOST);
connector.connect (logging_peer, server_addr);
int bufsiz = ACE_DEFAULT_MAX_SOCKET_BUFSIZ;
logging_peer.set_option (SOL_SOCKET,
SO_SNDBUF,
(void *) &bufsiz,
sizeof (bufsiz));
// Max # of <iov>s OS can send in a gather-write operation.
iovec iov[ACE_IOV_MAX];
int i = 0;
for (ACE_Message_Block *mblk = 0;
msg_queue_->dequeue_head (mblk);
) {
iov[i].iov_base = mblk->rd_ptr ();
iov[i].iov_len = mblk->length ();
// Dont delete the data in the message.
mblk->set_flags (ACE_Message_Block::DONT_DELETE);
mblk->release ();
if (i++ >= ACE_MAX_IOVLEN) {
// Send all buffered log records in one operation.
logging_peer_.sendv_n (iov, i);
// Clean up the buffers.
for (--i; ; --i) {
delete [] iov[i].iov_base;
if (i == 0) break;
}
}
}
if (i > 0) {
// Send all remaining log records in one operation.
logging_peer_.sendv_n (iov, i);
for (--i; i >= 0; --i) delete iov[i].iov_base;
}
return 0;
i
i
i hsbo
2001/
page 1
i
136
Line 2 Initialize the local_addr to the default TCP port number used by
the client logging daemon.
i
i
i hs_
2001/
page
i
137
Lines 512 Parse the service configuration options using the ACE_Get_
Opt class described in Sidebar 12 on page 105. If the -p option is passed
into init() then the local_addr port number is reset to that value.
Lines 1416 Initialize the ACE_SOCK_Acceptor to listen at the local_
addr port number and then register this object with the singleton reactor to accept new connections. The reactor will call back to the following Client_Logging_Daemon::get_handle() method to obtain the acceptors socket handle:
ACE_HANDLE Client_Logging_Daemon::get_handle () const {
return acceptor_.get_handle ();
}
When a connect request arrives, the reactor will dispatch the Client_
Logging_Daemon::handle_input() method shown on page 133.
Lines 1823 We finally use the ACE_Thread_Manager from Chapter 9
of [SH02] to spawn a system-scoped thread that executes the Client_
Logging_Daemon::run_svc() static method concurrently with the main
thread of control. The run_svc() static method casts its void* argument
to a Client_Logging_Daemon pointer and then delegates its processing to
forward() method, as shown below:
void *Client_Logging_Daemon::run_svc (void *arg) {
Client_Logging_Daemon *client_logging_daemon =
ACE_static_cast (Client_Logging_Daemon *, arg);
return client_logging_daemon->forward ();
}
For completeness, the suspend(), resume(), and info() hook methods are shown below:
int Client_Logging_Daemon::info (ACE_TCHAR **bufferp,
size_t length = 0) const {
ACE_INET_Addr sa;
acceptor_.get_local_addr (sa);
ACE_TCHAR buf[BUFSIZ];
sprintf (buf,
"%d/%s %s",
sa.get_port_number (),
"tcp",
i
i
i hs_
2001/
page
i
138
This file configures the client logging daemon by dynamically linking the
CLD DLL into the address space of the process and using ACE_DLL to
extract the make_Client_Logging_Daemon() factory function from the
CLD symbol table. This function is called to obtain a pointer to a dynamically allocated Client_Logging_Daemon. The framework then calls
Client_Logging_Daemon::init() hook method on this pointer, passing
in the "$CLIENT_LOGGING_DAEMON_PORT" string as its argc/argv argument. This string designates the port number where the client logging
daemon listens for client application connection requests. If init() succeeds, the Client_Logging_Daemon pointer is stored in the ACE_Service_
Repository under the name "Client_Logging_Daemon".
Were now ready to show the main() function, which is identical to the
one on page 116 in Section 4.4.
i
i
i hsbo
2001/
page 1
i
139
#include "ace/Service_Config.h"
#include "ace/Reactor.h"
int main (int argc, char *argv[])
{
ACE_Service_Config::open (argc, argv);
ACE_Reactor::instance ()->run_reactor_event_loop ();
return 0;
}
5.3
Motivation
Although the ACE_Thread_Manager class provides a portable threading abstraction, the threads it spawns and manages are not object-oriented, i.e.,
they are C-style functions rather than objects. C-style functions make it
hard to associate data members and methods with a thread. To resolve
these issues, ACE provides the ACE_Task class.
Class Capabilities
The ACE_Task class is the basis of ACEs object-oriented concurrency framework. It provides the following capabilities:
i
i
i hsbo
2001/
page 1
i
140
Our focus in this section is on the ACE_Task capabilities for message processing. It obtains the event handling and dynamic linking/unlinking capabilities by inheriting from the ACE_Service_Object class described in
the previous two chapters.
The interface for the ACE_Task class is shown in Figure 5.5 and its key
methods are shown in the following table:
Method
open()
close()
put()
Description
Hook methods that perform application-defined initialization and
termination activities.
A hook method that can be used to pass a message to a task,
where it can be processed immediately or queued for subsequent
processing in the svc() hook method.
svc()
A hook method run by one or more threads to process messages
that are placed on the queue via put().
getq()
Insert and remove messages from the tasks message queue (only
putq()
visible to subclasses of ACE_Task).
thr_mgr()
Get and set a pointer to the tasks ACE_Thread_Manager.
activate() Uses the ACE_Thread_Manager to convert the task into an active
object that runs the svc() method in one or more threads.
ACE_Task must be customized by subclasses to provide applicationdefined functionality by overriding its hook methods. For example, ACE_
Task subclasses can override its open() and close() hook methods to
perform application-defined ACE_Task initialization and termination activities. These activities include spawning and canceling threads and allocating and freeing resources, such as connection control blocks, I/O handles,
and synchronization locks. ACE_Task subclasses can perform applicationdefined processing on messages by overriding its put() and svc() hook
methods to implement the following two message processing models:
1. Synchronous processing. The put() method is the entry point into
an ACE_Task, i.e., its used to pass messages to a task. To minimize overhead, pointers to ACE_Message_Block objects are passed between tasks to
avoid copying their data. Task processing can be performed synchronously
in the context of the put() method if it executes solely as a passive object,
i.e., if its callers thread is borrowed for the duration of its processing.
i
i
i hs_
2001/
page
i
141
ACE_Task
+ thr_mgr_ : ACE_Thread_Manager *
+ thr_count_ : size_t
+ msg_queue_ : ACE_Message_Queue *
+ open (args : void *) : int
+ close (flags : u_long) : int
+ put (mb : ACE_Message_Block *,
timeout : ACE_Time_Value *) : int
+ svc () : int
+ getq (mb : ACE_Message_Block *&,
timeout : ACE_Time_Value *) : int
+ putq (mb : ACE_Message_Block *,
timeout : ACE_Time_Value *) : int
+ activate (flags : long, threads : int) : int
+ thr_mgr (): ACE_Thread_Manager *
+ thr_mgr (mgr : ACE_Thread_Manager *)
i
i
i hsbo
2001/
page 1
i
142
t1 : SubTask
: Task
State
t3 : SubTask
6 : p u t ( ms g)
1 : p u t ( ms g)
: A C E _ M e s s a ge _ Q u e u e
2 : p u tq ( ms g)
t2 : SubTask
: Task
State
: A C E _ M e s s a ge _ Q u e u e
: Task
State
: A C E _ M e s s a ge _ Q u e u e
3: sv c ( )
4 : ge tq ( ms g)
5 : d o_ w or k( ms g)
i
i
i hsbo
2001/
page 1
i
143
Example
This example shows how to combine the ACE_Task and ACE_Message_
Queue classes with the ACE_Reactor from Chapter 3 and the ACE_Service_
Config from Chapter ?? to implement a concurrent logging server. This
server design is based on the Half-Sync/Half-Async pattern [SSRB00] and
the eager spawning thread pool strategy described in Chapter 5 of [SH02].
As shown in Figure 5.8, a pool of worker threads is pre-spawned when the
logging server is launched. Log records can be processed concurrently until
the number of simultaneous client requests exceeds the number of worker
threads in the pool. At this point, additional requests are buffered in a synchronized ACE_Message_Queue until a worker thread becomes available.
The ACE_Message_Queue plays several roles in our thread pool logging
servers half-sync/half-async concurrency design:
It decouples the main thread from the pool of worker threads
This design allows multiple worker threads to be active simultaneously. It also offloads the responsibility for maintaining the queue
from kernel-space to user-space.
It helps to enforce flow control between clients and the server
When the number of bytes in the queue reaches its high-water mark,
i
i
i hsbo
2001/
page 1
i
144
CLIENT
LOGGING
DAEMONS
worker threads
Logging
Handlers
P1
TCP
CONNECTIONS
Logging
Acceptor
svc()
svc()
svc()
CONSOLE
Message
Queue
P2
P3
LOGGING
SERVER
NETWORK
The following table outlines the classes that well cover in the example
below:
i
i
i hsbo
2001/
page 1
i
145
Class
TP_Logging_Task
TP_Logging_Acceptor
TP_Logging_Handler
TP_Logging_Server
Description
Runs as an active object processing and printing log
records.
A factory that accepts connections and creates TP_
Logging_Handler objects.
Target of upcalls from the ACE_Reactor that receives
log records from clients.
A facade class that integrates the other three classes
together.
i
i
i hs_
2001/
page
i
146
i
i
i hsbo
2001/
page 1
i
147
i
i
i hs_
2001/
page
i
148
Lines 24 Call the getq() method, which blocks until a message block is
available. Each message block is actually a composite message that contains the following three message blocks chained together via their continuation pointers:
1. The ACE_FILE_IO object thats used to write the log record
2. The marshaled log record contents and
3. The hostname of the connected client
Lines 67 Initialize a Logging_Handler with the log_file and then call
its write_log_record() method, which writes the log record to the log file.
The write_log_record() method is responsible for releasing its message
blocks, as shown on in Chapter 4 of [SH02].
Lines 910 After the log record is written, we release the log_blk message block to reclaim the message block allocated to store the ACE_FILE_IO
pointer. We first set the continuation field to NULL so we just release the
mblk. Note that we dont delete the ACE_FILE_IO memory since its borrowed from the TP_Logging_Handler rather than allocated dynamically.
TP Logging Server. This facade class contains an instance of TP_Logging_
Task and Reactor_Logging_Server.
class TP_Logging_Server : public ACE_Service_Object
{
protected:
// Contains the reactor, acceptor, and handlers.
typedef Reactor_Logging_Server<TP_Logging_Acceptor>
LOGGING_DISPATCHER;
LOGGING_DISPATCHER *logging_dispatcher_;
// Contains the pool of worker threads.
TP_Logging_Task *logging_task_;
public:
// Other methods defined below...
};
The TP_Logging_Server::init() hook method enhances the reactorbased logging server implementation in Chapter 3 by pre-spawning a pool
of worker threads that process log records concurrently.
virtual init (int argc, char *argv[]) {
logging_dispatcher_ =
new TP_Logging_Server::LOGGING_DISPATCHER
i
i
i hsbo
2001/
page 1
i
149
This file configures the thread pool logging server by dynamically linking the TPLS DLL into the address space of the process and using ACE_
DLL to extract the make_TP_Logging_Server() factory function from the
TPLS symbol table. This function is called to obtain a pointer to a dynamically allocated TP_Logging_Server. The framework then calls the
TP_Logging_Server::init() hook method on this pointer, passing in
the "$TP_LOGGING_SERVER_PORT" string as its argc/argv argument. This
string designates the port number where the logging server listens for client
connection requests. If init() succeeds, the TP_Logging_Server pointer
i
i
i hsbo
2001/
page 1
i
150
5.4 Summary
The ACE Task framework allows developers to create and configure concurrent networked applications in a powerful and extensible object-oriented
fashion. This framework provides the ACE_Task class that integrates multithreading with object-oriented programming and queueing. The queueing mechanism in the ACE_Task is based on the ACE_Message_Queue class
that transfers messages between tasks efficiently. Since ACE_Task derives
from the ACE_Service_Object class in Section 4.2, its easy to design services that can run as active objects and be dispatched by the ACE Reactor
framework.
This chapter illustrates how the ACE Reactor framework can be combined with the ACE Task framework to implement variants of the HalfSync/Half-Async pattern [SSRB00]. The ACE Task framework classes can
also be combined with the ACE_Future, ACE_Method_Request, and ACE_
Activation_List classes to implement the Active Object pattern [SSRB00],
as shown in the supplemental material at the ACE website http://ace.
ece.uci.edu.
i
i
i hsbo
2001/
page 1
i
Bibliography
Matt Austern. Generic Programming and the STL: Using and Extending
the C++ Standard. Addison-Wesley, 1998.
[BA90]
[BL88]
[BMR+ 96] Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Sommerlad,
and Michael Stal. Pattern-Oriented Software Architecture A System of
Patterns. Wiley and Sons, 1996.
[CB97]
John Crawford and Steve Ball. Monostate Classes: The Power of One.
C++ Report, 9(5), May 1997.
[CL97]
Patrick Chan and Rosanna Lee. The Java Class Libraries: java.applet,
java.awt, java.beans, Volume 2. Addison-Wesley, Reading,
Massachusetts, 1997.
i
i
i hsbo
2001/
page 1
i
152
BIBLIOGRAPHY
[FBB+ 99] Martin Fowler, Kent Beck, John Brant, William Opdyke, and Don
Roberts. Refactoring - Improving the Design of Existing Code.
Addison-Wesley, Reading, Massachusetts, 1999.
[FJS99a] Mohamed Fayad, Ralph Johnson, and Douglas C. Schmidt, editors.
Object-Oriented Application Frameworks: Problems & Perspectives.
Wiley & Sons, New York, NY, 1999.
[FJS99b] Mohamed Fayad, Ralph Johnson, and Douglas C. Schmidt, editors.
Object-Oriented Application Frameworks: Applications & Experiences.
Wiley & Sons, New York, NY, 1999.
[FY99]
Brian Foote and Joe Yoder. Big Ball of Mud. In Brian Foote, Neil
Harrison, and Hans Rohnert, editors, Pattern Languages of Program
Design. Addison-Wesley, Reading, Massachusetts, 1999.
[GHJV95] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides.
Design Patterns: Elements of Reusable Object-Oriented Software.
Addison-Wesley, Reading, Massachusetts, 1995.
[HJE95] Herman Hueni, Ralph Johnson, and Robert Engel. A Framework for
Network Protocol Software. In Proceedings of OOPSLA 95, Austin,
Texas, October 1995. ACM.
[HLS97] Timothy H. Harrison, David L. Levine, and Douglas C. Schmidt. The
Design and Performance of a Real-time CORBA Event Service. In
Proceedings of OOPSLA 97, pages 184199, Atlanta, GA, October 1997.
ACM.
[HP91]
[HV99]
[JF88]
[JKN+ 01] Philippe Joubert, Robert King, Richard Neves, Mark Russinovich, and
John Tracey. High-Performance Memory-Based Web Servers: Kernel
and User-Space Performance. In Proceedings of the USENIX Technical
Conference, Boston, MA, June 2001.
[Joh97]
[Jos99]
i
i
i hsbo
2001/
page 1
i
BIBLIOGRAPHY
153
[KMC+ 00] Eddie Kohler, Robert Morris, Benjie Chen, John Jannotti, and
M. Frans Kaashoek. The Click Modular Router. ACM Transactions on
Computer Systems, 18(3):263297, August 2000.
[Koe92]
Andrew Koenig. When Not to Use Virtual Functions. C++ Journal, 2(2),
1992.
[Kof93]
[Lea99]
[Mey97]
[Obj98]
[Obj01]
[Rag93]
[Ric97]
[Rit84]
[Rob99]
[Sch98]
i
i
i hsbo
2001/
page 1
i
154
BIBLIOGRAPHY
[SS94]
[SS95a]
[SS95b]
i
i
i hsbo
2001/
page 1
i
BIBLIOGRAPHY
155
Concurrent and Networked Objects, Volume 2. Wiley & Sons, New York,
NY, 2000.
[Sta96]
[Ste98]
[Ste99]
[SW93]
[VL97]
[Vli98]
i
i
i
i
i hsbo
2001/
page 1
i
i
i
i hsbo
2001/
page 1
i
Software for networked applications must possess the following all-toorare qualities to be successful in todays competitive, fast-paced computing
industry:
This book describes how frameworks can help developers navigate between
the limitations of
1. Lower-level native operating system APIs, which are inflexible and
non-portable and
2. Higher-level distributed computing middleware, which often lacks the
efficiency and flexibility for networked applications with stringent QoS
and portability requirements.
This book illustrates how to develop networked applications using ACE
frameworks, and shows how key patterns and design principles can be
used to develop and deploy successful object-oriented networked application software.
If youre designing software and systems that must be portable, flexible,
extensible, predictable, reliable, and affordable, this book and the ACE
157
i
i
i hsbo
2001/
page 1
i
158
BIBLIOGRAPHY
frameworks will enable you to be more effective in all of these areas. Youll
get first hand knowledge from Doug Schmidt, the original architect and
developer of ACE, the reasons and thought processes behind the patterns
and ideas that went into key aspects of the ACE frameworks. Co-author
Steve Hustons expertise and knowledge from his extensive background in
hardware and software environments and hands-on involvement in ACE
since 1996 will take you further in understanding how to effectively tap
the rich features embodied in the ACE framework.
Dr. Douglas C. Schmidt is an Associate Professor at the University
of California, Irvine, where he studies patterns and optimizations for distributed real-time and embedded middleware. Doug led the development
of ACE and TAO, which are widely-used open-source middleware containing a rich set of reusable frameworks that implement key concurrency and
networking patterns. Hes also been the editor-in-chief of the C++ Report
magazine and co-authors the Object Interconnections column for the C/C++
Users Journal.
Stephen D. Huston is President and CEO of Riverace Corporation, the
premier provider of technical support and consulting services for companies looking to work smarter and keep software projects on track using
ACE. Mr. Huston has more than twenty years of software development experience, focusing primarily on network protocol and networked application development in a wide range of hardware and software environments.
He has been involved with ACEs development since 1996 and has enjoyed
helping many companies develop clean and efficient networked applications quickly using ACE.
i
i