C++ Network Programming
Systematic Reuse with ACE and Frameworks
Contents
About this Book vii
1 Object-Oriented Frameworks for Network Programming 1
1.1 An Overview of Object-Oriented Frameworks . . . 1
1.2 Applying Frameworks to Network Programming . . . 4
1.3 An Overview of the ACE Frameworks . . . 7
1.4 Comparing Frameworks with Other Reuse Techniques . . 10
1.5 Example: A Networked Logging Service . . . 16
1.6 Summary . . . 18
2 Service and Configuration Design Dimensions 23 2.1 Service Design Dimensions . . . 23
2.1.1 Short- vs. Long-Duration Services . . . 24
2.1.2 Internal vs. External Services . . . 24
2.1.3 Stateful vs. Stateless Services . . . 26
2.1.4 Layered/Modular vs. Monolithic Services . . . 27
2.1.5 Single- vs. Multi-Service Servers . . . 30
2.1.6 One-shot vs. Standing Servers . . . 31
2.2 Configuration Design Dimensions . . . 33
2.2.1 Static vs. Dynamic Naming . . . 33
2.2.2 Static vs. Dynamic Linking . . . 33
2.2.3 Static vs. Dynamic Configuration . . . 35
iv CONTENTS
3 The ACE Reactor Framework 39
3.1 Overview . . . 39
3.2 The ACE Time Value Class . . . 42
3.3 The ACE Event Handler Class . . . 44
3.4 The ACE Timer Queue Classes . . . 54
3.5 The ACE Reactor Class . . . 61
3.6 The ACE Select Reactor Class . . . 73
3.7 The ACE TP Reactor Class . . . 81
3.8 The ACE WFMO Reactor Class . . . 84
3.9 Summary . . . 90
4 The ACE Service Configurator Framework 91 4.1 Overview . . . 91
4.2 The ACE Service Object Class . . . 94
4.3 The ACE Service Repository and ACE Service Repository Iterator Classes . . . 99
4.4 The ACE Service Config Class . . . 109
4.5 Summary . . . 120
5 The ACE Task Framework 121 5.1 Overview . . . 121
5.2 The ACE Message Queue Class . . . 123
5.3 The ACE Task Class . . . 139
List of Figures
1.1 Defining Characteristics of a Framework . . . 3
1.2 Levels of Abstraction for Network Programming . . . 5
1.3 The Layered Architecture of ACE . . . 8
1.4 The Frameworks in ACE . . . 9
1.5 Class Library vs. Framework Architectures . . . 11
1.6 Applying Class Libraries to Develop and Use ACE Frameworks 14 1.7 Component Architecture . . . 14
1.8 Processes and Daemons in the Networked Logging Service . 18 2.1 Internal vs. External Services . . . 25
2.2 Layered/Modular vs. Monolithic Services . . . 27
2.3 Single-service vs. Multi-service Servers . . . 30
2.4 One-shot vs. Standing Servers . . . 32
2.5 Static Linking vs. Dynamic Linking . . . 34
3.1 The ACE Reactor Framework Classes . . . 40
3.2 The ACE Time ValueClass . . . 43
3.3 The ACE Event HandlerClass . . . 46
3.4 The ACE Timer Queue Classes . . . 55
3.5 The ACE ReactorClass . . . 62
3.6 The ACE Reactor Class Hierarchy . . . 68
3.7 Architecture of Reactor-based Logging Server . . . 70
3.8 The ACE Select ReactorFramework Internals . . . 75
vi LIST OF FIGURES
4.1 The ACE Service Configurator Framework Classes . . . 92
4.2 TheACE Service ObjectClass . . . 95
4.3 TheACE Service RepositoryClass . . . 99
4.4 TheACE Service Repository IteratorClass . . . 102
4.5 TheACE Service ConfigClass . . . 111
4.6 BNF for theACE Service ConfigScripting Language . . . . 116
4.7 A State Diagram for Configuring the Server Logging Daemon 118 4.8 A State Diagram for Reconfiguring the Server Logging Daemon 119 5.1 The ACE Task Framework Classes . . . 122
5.2 TheACE Message QueueClass . . . 125
5.3 The Structure of an ACE Message Queue . . . 126
5.4 Multi-threaded Client Logging Daemon . . . 132
5.5 TheACE TaskClass . . . 140
5.6 Task Activate Behavior . . . 142
5.7 Passing Messages Between ACE TaskObjects . . . 142
About this Book
Writing high-quality networked applications is hard—it’s expensive, com-plicated, 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 li-braries. 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 demulti-plexing 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 through-out the development lifecycle. This problem is exacerbated by the inherent diversity of today’s hardware, operating systems, compilers, and commu-nication platforms, which keep shifting the foundations of networked ap-plication software.
Object-oriented frameworks [FJS99a, FJS99b] are one of the most
flexi-ble and powerful techniques for addressing the proflexi-blems outlined above. A framework is a reusable, “semi-complete” application that can be
special-viii About this Book
ized to produce custom applications [JF88]. Frameworks help to reduce the cost and improve the quality of networked applications by reifying proven software designs and patterns into concrete source code. By emphasizing the integration and collaboration of specific and application-independent classes, moreover, frameworks enable larger-scale reuse of software than is possible by reusing individual classes or stand-alone func-tions.
In 1992, Doug Schmidt started the open-source ACE project at the Uni-versity of California, Irvine and Washington UniUni-versity, St. Louis. Over the next decade, the ACE toolkit yielded some of the most powerful and widely used object-oriented frameworks written in C++. By applying reusable soft-ware patterns and a lightweight OS portability layer, the ACE toolkit pro-vides frameworks for synchronous and asynchronous event handling, ser-vice initialization, concurrency, connection management, and hierarchical service integration.
ACE has changed the way complex networked applications and mid-dleware are being designed and implemented on the world’s most popular operating systems, such as AIX, HP/UX, Linux, MacOS X, Solaris, and Windows, as well as real-time embedded operating systems, such as Vx-Works, LynxOS, ChorusOS, QNX, pSOS, and WinCE. ACE is being used by thousands of development teams ranging from large Fortune 500 com-panies to small startups. Its open-source development model and self-supporting culture is similiar in spirit and enthusiasm to Linus Torvald’s popular Linux operating system.
Intended Audience
This book is intended for “hands-on” C++ developers or advanced stu-dents 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 ex-amples help you begin to apply key object-oriented design principles and patterns to your concurrent networked applications right away.
About this Book ix
Structure and Content
This book describes a family of object-oriented network programming frame-works provided by the ACE toolkit. These frameframe-works help reduce the cost and improve the quality of networked applications by reifying proven software designs and implementations. ACE’s framework-based approach expands reuse technology far beyond what can be achieved by reusing in-dividual classes or even class libraries. We describe the design of these frameworks, show how they can be applied to real networked applications, and summarize the design rules and lessons learned that ensure effective use of these frameworks.
This book’s primary application is a networked logging service that transfers log records from client applications to a logging server, which stores the records in a file or database. We use this service as a running example throughout the book to
Show concretely how ACE frameworks can help achieve efficient, pre-dictable, and scalable networked applications and
Demonstrate key design and implementation considerations and so-lutions that will arise when you develop your own concurrent object-oriented networked applications.
Chapter 1 introduces the concept of an object-oriented framework and shows how frameworks differ from other reuse techniques, such as class libraries, components, and patterns. We also outline the frameworks in the ACE toolkit that are covered in subsequent chapters. The ACE frame-works are based on a pattern language [BMR+
96, SSRB00] that has been applied to thousands of production and research networked applications and middleware systems world-wide.
Chapter 2 presents a domain analysis of service and configuration de-sign dimensions that address key networked application properties, such as duration and structure, how networked services are identified, and the time at which they are bound together to form complete applications.
Chapter 3 describes the design and use of the ACE Reactor framework, which implements the Reactor pattern [SSRB00] to allow event-driven ap-plications to demultiplex and dispatch service requests that are delivered to an application from one or more clients.
Chapter 4 describes the design and use of the ACE Service Config-urator framework, which implements the Component ConfigConfig-urator
pat-x About this Book
tern [SSRB00] to allow an application to link/unlink its component im-plementations 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 frame-work, 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 Acceptor-Connector framework, which implements the Acceptor-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 ap-plications to efficiently demultiplex and dispatch service requests triggered by the completion of asynchronous operations.
Chapter ?? describes the design and use of the ACE Streams frame-work, 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 we’ve learned during the past decade developing the reusable object-oriented networked appli-cation software in the ACE toolkit and deploying ACE successfully in a wide range of commercial applications across many domains. Appendix ?? pro-vides 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
About this Book xi
volume in this series—C++ Network Programming: Mastering Complexity
with ACE and Patterns [SH02]—is primarily concerned with using
object-oriented patterns and language features to abstract away the details of programming low-level APIs. We recommend that you read the Volume 1 first before reading this book.
This book is based on ACE version 5.2, released in October, 2001. ACE 5.2 and all the sample applications described in our books are open-source software that can be downloaded athttp://ace.ece.uci.eduandhttp: //www.riverace.com. These sites also contain a wealth of other material on ACE, such as tutorials, technical papers, and an overview of other ACE wrapper facades and frameworks that aren’t covered in this book. We en-courage you to obtain a copy of ACE so you can follow along, see the actual ACE classes and frameworks in complete detail, and run the code examples interactively as you read through the book. Pre-compiled versions of ACE can also be purchased at a nominal cost fromhttp://www.riverace.com. To learn more about ACE, or to report any errors you find in the book, we recommend you subscribe to the ACE mailing list, ace-users@cs. wustl.edu. You can subscribe by sending email to the Majordomo list server at [email protected]. Include the following com-mand in the body of the email (the subject is ignored):
subscribe ace-users [emailaddress@domain]
You must supply emailaddress@domainonly if your message’s From ad-dress is not the adad-dress 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 newsgroupcomp.soft-sys.ace.
Acknowledgements
Champion reviewing honors go to ..., who reviewed the entire book and provided extensive comments that improved its form and content substan-tially. 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.
xii About this Book
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 who’ve contributed to ACE during the past decade. ACE’s ex-cellence 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 ACE’s open-source code base. With-out their support, constant feedback, and encouragement we would never have written this book. In recognition of the efforts of the ACE open-source community, we maintain a list of all contributors that’s available athttp://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 Richard’s books brought into the world of object-oriented design and C++ programming.
Steve’s Acknowledgements
Doug’s Acknowledgements
CHAPTER 1
Object-Oriented Frameworks for
Network Programming
C
HAPTERS
YNOPSISObject-oriented frameworks help reduce the cost and improve the quality of networked applications by reifying software designs and pattern lan-guages 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, com-ponents, 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
An Overview of Object-Oriented Frameworks
Although computing power and network bandwidth have increased dra-matically in recent years, the design and implementation of networked ap-plication software remains expensive, time-consuming, and error-prone. The cost and effort stems from the increasing demands placed on net-worked software and the continual rediscovery and reinvention of core software design and implementation artifacts throughout the software in-dustry. Moreover, the heterogeneity of hardware architectures, diversity of OS and network platforms, and stiff global competition are making it in-creasingly hard to build networked application software from scratch while
2 Section 1.1 An Overview of Object-Oriented Frameworks
also ensuring that it has the following qualities:
Portability, to reduce the effort required to support applications across heterogeneous OS platforms, programming languages, and compilers Flexibility, to support a growing range of multimedia data types,
traf-fic patterns, and end-to-end quality of service (QoS) requirements Extensibility, to support successions of quick updates and additions
to take advantage of new requirements and emerging markets
Predictability and efficiency, to provide low latency to delay-sensitive real-time applications, high performance to bandwidth-intensive ap-plications, and usability over low-bandwidth network, such as wire-less links
Reliability, to ensure that applications are robust, fault tolerant, and highly available and
Affordability, to ensure that the total ownership costs of software acquisition and evolution are not prohibitively high.
Developing application software that achieves these qualities is hard; systematically developing high quality reusable software middleware for networked applications is even harder [GHJV95]. Reusable software is in-herently abstract, which makes it hard to engineer its quality and to man-age its production. Moreover, the skills required to develop, deploy, and support reusable networked application software have traditionally been a “black art,” locked in the heads of expert developers and architects. When these technical reuse impediments are combined with the myriad of non-technical impediments, such as organizational, economical, administra-tive, political, and psychological factors, it’s not surprising that significant levels of software reuse have been slow to materialize in most projects and organizations [Sch00c].
During the past decade, we’ve written hundreds of thousands of lines of C++ code developing widely reusable middleware for networked appli-cations as part of our research and consulting with dozens of telecom-munication, aerospace, medical, and financial service companies. As a result of our experience, we’ve documented many patterns and pattern languages [SSRB00] that guided the design of the middleware and applica-tions. In addition, we’ve taught hundreds of tutorials and courses on reuse, middleware, and patterns for thousands of developers and students. De-spite formidable technical and non-technical challenges, we’ve identified a solid body of work that combines design knowledge, hands-on experience,
Section 1.1 An Overview of Object-Oriented Frameworks 3
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 DATABASE GUI EVENT LOOP EVENT LOOP EVENT LOOP APPLICATION -SPECIFIC FUNCTIONALITY CALL BACKS DOMAIN-SPECIFIC FRAMEWORK CAPABILITIES CALLBACKS CALLBACKS
Figure 1.1: Defining Characteristics of a Framework framework characteristics:
1. A framework provides an integrated set of domain-specific
struc-tures and functionality. Systematic reuse of software depends largely
on how well a framework captures the pressure points of stability and variability in an application domain, such as business data process-ing, telecom call processprocess-ing, graphical user interfaces, or distributed object computing middleware.
2. A framework exhibits “inversion of control” at run-time via
call-backs. A callback is an object registered with a dispatcher that calls
back to a method on the object when a particular event occurs, such as a connection request or data arriving on a socket handle. Inversion of control decouples the canonical detection, demultiplexing, and dis-patching steps within a framework from the application-defined event
handlers managed by the framework. When events occur, the
frame-work calls back to hook methods in the registered event handlers, which then perform application-defined processing on the events. 3. A framework is a “semi-complete” application that programmers
customize to form complete applications by inheriting and
instantiat-1In the remainder of this book we use the term framework to mean object-oriented
4 Section 1.2 Applying Frameworks to Network Programming
ing framework classes. Inheritance enables the features of framework base classes to be shared selectively by subclasses. If a base class provides default implementations of its methods, application develop-ers need only override those virtual methods whose default behavior is inadequate.
These characteristics of a framework yield the following benefits:
Since a framework reifies the key roles and relationships of classes in an application domain, the amount of reusable code is increased and the amount of code rewritten for each application is decreased. Since a framework exhibits inversion of control, it simplifies
applica-tion design since the framework—rather than an applicaapplica-tion—runs the event loop that detects events, demultiplexes events to event han-dlers, and dispatches hook methods on the handlers to process the events.
Since a framework is a semi-complete application, it enables larger-scale reuse of software than reusing individual classes or stand-alone functions because it integrates defined and application-independent classes. A framework abstracts the canonical control flow of applications in a particular domain into families of related classes, which then collaborate to integrate generic and customizable independent code with concrete customized application-defined code.
Appendix ?? evaluates the pros and cons of frameworks in more detail.
1.2
Applying Frameworks to Network Programming
One reason why it’s hard to write robust, extensible, and efficient net-worked applications is because developers must master many complex networking programming concepts and mechanisms, including:
Network addressing and service identification/discovery
Presentation layer conversions, such as encryption, compression, and marshaling/demarshaling, to handle heterogeneous end-systems with alternative processor byte-orderings
Local and remote inter-process communication (IPC) mechanisms Synchronous and asynchronous event demultiplexing and event
Section 1.2 Applying Frameworks to Network Programming 5
Process/thread lifetime management and synchronization.
Application programming interfaces (APIs) and tools have evolved over the years to simplify the development of networked applications and middle-ware. Figure 1.2 illustrates the IPC APIs available on many OS platforms, such as UNIX and many real-time operating systems. This figure shows
LEVEL OF ABSTRACTION USER SPACE KERNEL SPACE
HOST INFRASTRUCTURE MIDDLEWARE SOCKETS
&
TLI
open()/close()/putmsg()/getmsg()
STREAMS
FRAMEWORK TPI NPI DLPI HI LOFigure 1.2: Levels of Abstraction for Network Programming how applications can access networking APIs for local and remote IPC at several levels of abstraction. Below, we outline each level of abstraction, starting from the low-level kernel APIs to the native OS user-level network-ing APIs and host infrastructure middleware.
Kernel-level networking APIs. Lower-level networking APIs are available in an OS kernel’s I/O subsystem. For example, the UNIX putmsg() and
getmsg() system calls can be used to access the transport provider
in-terface (TPI) [OSI92b] and the data-link provider inin-terface (DLPI) [OSI92a]
available in System V STREAMS [Rit84]. It’s also possible to develop net-work services, such as routers [KMC+
00], network file systems [WLS+ 85], or even Web servers [JKN+
01], that reside entirely within an OS kernel. Programming directly to kernel-level networking APIs is rarely portable between different OS platforms. It’s often not even portable across differ-ent versions of the same OS! Since kernel-level programming isn’t used in most networked applications, we won’t discuss it further in this book. See [Rag93] and [SW93] for coverage of these topics in the context of Sys-tem V UNIX and BSD UNIX, respectively.
User-level networking APIs. Networking protocol stacks in most com-mercial operating systems reside within the protected address space of the OS kernel. Applications running in user-space access the kernel-resident protocol stacks via OS IPC APIs, such as the Socket or TLI APIs. These
6 Section 1.2 Applying Frameworks to Network Programming
APIs collaborate with an OS kernel to access the capabilities shown in the following table:
Capability Description
Local context management
Manage the lifetime of local and remote communication end-points. Connection establishment and connection termination
Enable applications to establish connections actively or pas-sively with remote peers and to shutdown all or part of the connections when transmissions are complete.
Options management
Negotiate and enable/disable certain options. Data transfer
mechanisms
Exchange data between connected peer applications. Network
addressing
Convert humanly-readable names to low-level network ad-dresses and vice versa.
These capabilities are described in Chapter 2 of [SH02] in the context of the Socket API.
Many IPC APIs are modeled loosely on the UNIX file I/O API, which defines the open(), read(), write(), close(), ioctl(), lseek(), and
select()functions [Rit84]. Networking APIs provide additional function-ality that’s not supported directly by the standard UNIX file I/O APIs due to syntactic and semantic differences between file I/O and network I/O. For example, the pathnames used to identify files on a UNIX system aren’t globally unique across hosts in a heterogeneous distributed environment. Different naming schemes, such as IP host addresses and TCP/UDP port numbers, have therefore been devised to uniquely identify communication endpoints used by networked applications.
Host infrastructure middleware frameworks. Many networked applica-tions exchange messages with clients using various types of synchronous and asynchronous request/response protocols in conjunction with host
in-frastructure middleware frameworks. Host inin-frastructure middleware
en-capsulates OS concurrency and IPC mechanisms to automate many te-dious and error-prone aspects of networked application development, in-cluding:
Connection management and event handler initialization
Synchronous and asynchronous event detection, demultiplexing, and event handler dispatching
Section 1.3 An Overview of the ACE Frameworks 7
Message framing atop bytestream protocols, such as TCP
Presentation conversion issues involving network byte-ordering and parameter (de)marshaling
Concurrency models and synchronization of concurrent operations Networked application composition from dynamically configured
ser-vices and
Management of QoS properties, such as scheduling access to proces-sors, networks, and memory.
The increasing availability and popularity of high-quality and affordable host infrastructure middleware is helping to raise the level of abstraction at which networked application developers can work effectively. Even so, it’s wise to understand how lower-level IPC mechanisms work to fully com-prehend the challenges that will arise when designing your networked ap-plications.
1.3
An Overview of the ACE Frameworks
The ADAPTIVE Communication Environment (ACE) is a highly portable, widely-used, open-source host infrastructure middleware that can be downloaded fromhttp://ace.ece.uci.edu/orhttp://www.riverace.com. The ACE library contains240,000 lines of C++ code and500 classes. To separate concerns, reduce complexity, and permit functional subsetting, ACE is de-signed 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
ser-vice components, and standards-based middleware. Together, these
mid-dleware layers simplify the creation, composition, configuration, and port-ing of networked applications without incurrport-ing significant performance overhead.
The ACE wrapper facades for native OS IPC and concurrency mech-anisms and the ACE standards-based middleware based upon and bun-dled 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
8 Section 1.3 An Overview of the ACE Frameworks PROCESSES/ THREADS DYNAMIC LINKING SHARED MEMORY SELECT/
IO COMP FILE SYSAPIS WIN32 NAMED
PIPES & UNIX STREAM PIPES
UNIX FIFOS CC
AAPPIISS SOCKETSTLI/
C
COOMMMMUUNNIICCAATTIIOONN
SSUUBBSSYYSSTTEEMM VVIIRRTTUUAALL MMEEMMOORRYY SSUUBBSSYYSSTTEEMM&& FFIILLEE G
GEENNEERRAALL OOPPEERRAATTIINNGG SSYYSSTTEEMM SSEERRVVIICCEESS P PRROOCCEESSSS//TTHHRREEAADD SSUUBBSSYYSSTTEEMM F FRRAAMMEEWWOORRKK LLAAYYEERR A ACCCCEEPPTTOORR CCOONNNNEECCTTOORR N NEETTWWOORRKKEEDD SSEERRVVIICCEE CCOOMMPPOONNEENNTTSS LLAAYYEERR NAME SERVER TOKEN SERVER LOGGING SERVER GATEWAY SERVER SOCK SAP/ TLI SAP FIFOSAP
LOG MSG S SEERRVVIICCEE HHAANNDDLLEERR TIME SERVER C C++++ WWRRAAPPPPEERR FFAACCAADDEE LLAAYYEERR SPIPE SAP C COORRBBAA HHAANNDDLLEERR FILE SAP SHARED MALLOC
THE ACE ORB
(TAO) JAWS ADAPTIVE
WEB SERVER SSTTAANNDDAARRDDSS--BBAASSEEDD MMIIDDDDLLEEWWAARREE
R REEAACCTTOORR// PPRROOAACCTTOORR PROCESS/ THREAD MANAGERS S STTRREEAAMMSS SSEERRVVIICCEE CCOONNFFIIGG --UURRAATTOORR SYNCH
WRAPPERS MEMMAP
O
OSS AADDAAPPTTAATTIIOONN LLAAYYEERR
Figure 1.3: The Layered Architecture of ACE
(TAO) [SLM98], which is a CORBA-compliant [Obj01] Object Request Bro-ker (ORB) implemented using the frameworks in ACE. Figure 1.4 illustrates the ACE frameworks described in this book, which implement a pattern language for programming concurrent object-oriented networked applica-tions.
ACE Reactor and Proactor frameworks. These frameworks implement the Reactor and Proactor patterns [SSRB00], respectively. Reactor is an ar-chitectural pattern that allows event-driven applications to synchronously process requests that are delivered to an application from one or more clients. Proactor is an architectural pattern that allows event-driven appli-cations to process requests triggered by the completion of asynchronous operations to achieve the performance benefits of concurrency without in-curring many of its liabilities. The Reactor and Proactor frameworks auto-mate the detection, demultiplexing, and dispatching of application-defined handlers in response to various types of I/O-based, timer-based, signal-based, and synchronization-based events. Chapter 3 describes the Reactor framework and Chapter ?? describes the Proactor framework.
Section 1.3 An Overview of the ACE Frameworks 9
Service
Configurator
Acceptor-Connector
Half-Sync
Half-Async
Monitor
Object
Proactor
Reactor
Streams
Active
Object
Figure 1.4: The Frameworks in ACE
ACE Service Configurator framework. This framework implements the Component Configurator pattern [SSRB00], which is a design pattern that allows an application to link/unlink its component implementations at run-time without having to modify, recompile, or relink the application statically. The ACE Service Configurator framework supports the config-uration of applications whose services can be assembled dynamically late in the design cycle, i.e., at installation-time and/or run-time. Applications with high availability requirements, such as mission-critical systems that perform on-line transaction processing or real-time industrial process au-tomation, often require such flexible configuration capabilities. Chapter 4 describes this framework in detail.
ACE Task concurrency framework. This framework implements various concurrency patterns [SSRB00], such as Active Object and Half-Sync/Half-Async:
Active Object is a design pattern that decouples the thread that exe-cutes a method from the thread that invoked it. Its purpose is to en-hance 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 asyn-chronous and synasyn-chronous processing in concurrent systems, to sim-plify programming without reducing performance unduly. This pat-tern introduces two intercommunicating layers, one for asynchronous and one for synchronous service processing. A queueing layer medi-ates communication between services in the asynchronous and syn-chronous layers.
10 Section 1.4 Comparing Frameworks with Other Reuse Techniques
Chapter 5 describes the ACE Task framework in detail.
ACE Acceptor-Connector framework. This framework leverages the Re-actor and ProRe-actor frameworks by reifying the Acceptor-Connector pat-tern [SSRB00]. Acceptor-Connector is a design patpat-tern that decouples the connection and initialization of cooperating peer services in a networked system from the processing they perform once connected and initialized. The Acceptor-Connector framework decouples the active and passive ini-tialization roles from application-defined service processing performed by communicating peer services after initialization is complete. Chapter ?? describes this framework in detail.
ACE Streams framework. This framework implements the Pipes and Fil-ters pattern [BMR+
96], which is an architectural pattern that provides a structure for systems that process a stream of data. The ACE Streams framework simplifies the development and composition of hierarchically-layered services, such as user-level protocol stacks and network manage-ment agents [SS94]. Chapter ?? describes this framework in detail.
When used together, these ACE frameworks enable the development of networked applications that can be updated and extended without the need to modify, recompile, relink, or restart running applications. ACE achieves this unprecedented flexibility and extensibility by combining
C++ language features, such as templates, inheritance, and dynamic binding [Bja00]
Patterns, such as Strategy [GHJV95] and Component Configurator [SSRB00] and
OS mechanisms, such as event demultiplexing, IPC, dynamic linking, multithreading, and synchronization [Ste99].
1.4
Comparing Frameworks with Other Reuse
Tech-niques
Object-oriented frameworks don’t exist in isolation. Many other reuse tech-niques are in widespread use, such as class libraries, components, and patterns. In this section, we compare frameworks with these other reuse techniques to illustrate their similarities and differences.
Section 1.4 Comparing Frameworks with Other Reuse Techniques 11
Comparing Frameworks and Class Libraries
Class libraries represent the most common first-generation object-oriented reuse technique [Mey97]. A class provides a general-purpose, reusable building block that can be applied across a wide range of applications. Class libraries support component reuse-in-the-small more effectively than function libraries since classes enhance the cohesion of data and methods that operate on the data. Their scope is somewhat limited, however, since they don’t capture the canonical control flow, collaboration, and variability among families of related software artifacts. Developers who apply class library-based reuse must therefore reinvent and reimplement the overall software architecture and much of the control logic for each new applica-tion.
Frameworks are second-generation reuse techniques that extend the benefits of class libraries in the following two ways:
1. Frameworks are “semi-complete” applications that embody domain-specific object structures and functionality. Class libraries are often domain-independent and provide a fairly limited scope of reuse, e.g., the C++ standard library [Bja00] provides classes for strings, vectors, and var-ious abstract data type (ADT) containers. Although these classes can be reused in most application domains, they are relatively low-level. For ex-ample, application developers are responsible for (re)writing the “glue code”
CALL BACKS DATABASE CLASSES NETWORK IPC CLASSES MATH CLASSES ADT CLASSES GUI CLASSES APPLICATION -SPECIFIC FUNCTIONALITY EVENT LOOP GLUE CODE LOCAL INVOCATIONS NETWORKING DATABASE GUI EVENT LOOP
(1) CLASS LIBRARY ARCHITECTURE
EVENT LOOP
EVENT LOOP
APPLICATION -SPECIFIC EVENT HANDLER
FUNCTIONALITY
(2) FRAMEWORK ARCHITECTURE
CALLBACKS
CALLBACKS
Figure 1.5: Class Library vs. Framework Architectures
12 Section 1.4 Comparing Frameworks with Other Reuse Techniques
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 clas-sified by the techniques used to extend them, which range along a con-tinuum from whitebox frameworks to blackbox frameworks [HJE95], as described below:
Whitebox frameworks—In this type of framework, extensibility is achieved via object-oriented language features, such as inheritance and dynamic binding. Existing functionality can be reused and cus-tomized 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 sup-plied by a derived class. Application developers must have some knowledge of a whitebox framework’s internal structure in order to extend it.
Blackbox frameworks—In this type of framework, extensibility is achieved by defining interfaces that allow objects to be plugged into the framework via composition and delegation. Existing functional-ity can be reused by defining classes that conform to a particular interface and then integrating these classes into the framework us-ing patterns such as Bridge and Strategy [GHJV95], which provide a blackbox abstraction for selecting one of many algorithms. Black-box frameworks may be easier to use than whiteBlack-box frameworks since application developers needn’t have as much knowledge of the frame-work’s internal structure. Blackbox frameworks can be harder to de-sign, however, since framework developers must define crisp inter-faces 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 re-sponse to a connection request from a peer connector:
The ACE_Acceptor uses the Template Method pattern, which pro-vides a whitebox approach to extensibility, whereas
TheACE_Strategy_Acceptoruses the Bridge and Strategy patterns, which provide a blackbox approach to extensibility [Sch00b].
Section 1.4 Comparing Frameworks with Other Reuse Techniques 13
Complete networked applications can be composed by customizing the classes and frameworks in ACE.
2. Frameworks are active and exhibit “inversion of control” at run-time. Classes in a class library are typically passive, i.e., they perform their processing by borrowing the thread of control from self-directed ap-plications that invoke their methods. As a result, developers must continu-ally rewrite much of the control logic needed to “glue” the reusable classes together to form complete networked applications.
In contrast, frameworks are active, i.e., they direct the flow of control within an application via various callback event handling patterns, such as Reactor, Proactor [SSRB00], and Observer [GHJV95]. These patterns invert the application’s flow of control using a design technique known as the Hollywood Principle, i.e., “Don’t call us, we’ll call you” [Vli98]. Since frameworks are active and manage the application’s control flow, they can perform a broader range of activities on behalf of applications than is pos-sible with passive class libraries.
All the ACE frameworks provide inversion of control via callbacks, as shown in the following table:
ACE Framework Inversion of Control
Reactor and Proactor
Call back to application-supplied event handlers to per-form processing when events occur synchronously and asynchronously, respectively.
Service Configurator Calls back to application-supplied service objects to ini-tialize, suspend, resume, and finalize them.
Task Calls back to an application-supplied hook method to perform processing in one or more threads of control. Acceptor-Connector Calls back to service handlers in order to initialize them
after they’ve been connected.
Streams Calls back to initialize and finalize tasks when they are pushed and popped from a stream.
In practice, frameworks and class libraries are complementary tech-nologies. 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 li-brary classes [Jos99] to perform IPC, synchronization, file management, and string processing operations.
14 Section 1.4 Comparing Frameworks with Other Reuse Techniques ACE Streams ACE Reactor EVENT LOOP EVENT LOOP APPLICATION -SPECIFIC EVENT HANDLER
FUNCTIONALITY CALL BACKS LOCAL INVOCATIONS IPC CLASSES ADT CLASSES ACE Task EVENT LOOP CALLBACKS CALLBACKS
Figure 1.6: Applying Class Libraries to Develop and Use ACE
Frame-works
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
NAMING TRADING LOCKING APPLICATION -SPECIFIC FUNCTIONALITY EVENT LOOP GLUE CODE REMOTE OR LOCAL INVOCATIONS LOGGING TIME
Figure 1.7: Component Architecture services [Obj98], and JavaBeans [CL97].
Components are less lexically and spatially coupled than frameworks. For example, applications can reuse components without having to sub-class them from existing base sub-classes. In addition, by applying patterns like Proxy [GHJV95] and Broker [BMR+
dis-Section 1.4 Comparing Frameworks with Other Reuse Techniques 15
tributed to servers throughout a network and accessed by clients remotely. ACE provides naming, event routing [Sch00a], logging, time synchroniza-tion, and network locking components in its networked service components layer outlined in Chapter 0 of [SH02].
The relationship between frameworks and components is highly syner-gistic, with neither subordinate to the other [Joh97]. For example, a mid-dleware framework can be used to develop higher-level application com-ponents, where component interfaces provide a facade for the internal class structure of the framework. Likewise, components can be used as pluggable strategies in blackbox frameworks [HJE95]. Frameworks are often used to simplify the development of middleware component mod-els [Ann98, BEA99], whereas components are often used to simplify the development and configuration of networked application software.
Comparing Frameworks and Patterns
Developers of networked applications must address design challenges re-lated to complex topics like connection management, service initialization, distribution, concurrency control, flow control, error handling, event loop integration, and dependability. These challenges are often independent of application-defined requirements. Successful developers resolve these challenges by applying the following types of patterns [BMR+
96]:
Design patterns, which provide a scheme for refining components of a software system or the relationships between them and describe a commonly-recurring structure of communicating components that solves a general design problem within a particular context.
Architectural patterns, which express fundamental structural or-ganization schemas for software systems and provide a set of prede-fined subsystems, specify their responsibilities, and include rules and guidelines for organizing the relationships between them.
Pattern languages, which define a vocabulary for talking about soft-ware development problems and provide a process for the orderly res-olution of these problems.
Traditionally, patterns and pattern languages have been locked in the heads of expert developers or buried deep within complex system source code. Allowing this valuable information to reside only in these locations is risky and expensive, however. Capturing and documenting patterns for networked applications therefore helps to
16 Section 1.5 Example: A Networked Logging Service
Preserve important design information for programmers who en-hance and maintain existing software. If this information isn’t docu-mented explicitly it’ll be lost over time. In turn, this increases software entropy and decreases software maintainability and quality since sub-stantial effort may likewise be necessary to reverse engineer the pat-terns 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 develop-ment effort and maintenance costs. Reuse of patterns alone, however, is not sufficient to create flexible and efficient networked application soft-ware. Although patterns enable reuse of abstract design and architecture knowledge, software abstractions documented as patterns don’t yield re-usable code directly. It’s therefore essential to augment the study of pat-terns with the creation and use of frameworks. Frameworks help develop-ers avoid costly reinvention of standard software artifacts by implementing common pattern languages and refactoring common implementation roles.
1.5
Example: A Networked Logging Service
Throughout this book, we illustrate key points and ACE capabilities by ex-tending and enhancing the networked logging service example presented in [SH02]. This service collects and records diagnostic information sent from one or more client applications. Unlike the versions in [SH02], which were a subset of the actual networked logging service in ACE, this book illus-trates the full complement of capabilities, patterns, and daemons provided by ACE. Sidebar 1 defines what a daemon is and explains the daemon process.
Figure 1.8 illustrates the application processes and daemons in our networked logging service, which are described below.
Client application processes run on client hosts and generate log records
Section 1.5 Example: A Networked Logging Service 17
Sidebar 1: Daemons and Daemonizing
A daemon is a server process that runs continuously in the background performing various services on behalf of clients [Ste98]. Daemonizing a UNIX process involves the following steps:
1. Dynamically spawning a new server process 2. Closing all unnecessary I/O handles
3. Changing the current filesystem directory away from the initiating user’s
4. Resetting the file access creation mask
5. Disassociating from the controlling process group and the controlling terminal and
6. Ignoring terminal I/O-related events and signals.
An ACE server can convert itself into a daemon on UNIX by invoking the static method ACE::daemonize(). A Win32 Service [Ric97] is a form of daemon and can be programmed in ACE using the ACE_NT_Service
class.
information sent by a client application indicates the following: 1. The time the log record was created
2. The process identifier of the application 3. The priority level of the log record and
4. A string containing the logging message text, which can vary in size from 0 to a maximum length, such as 4 Kbytes.
Client logging daemons run on every host machine participating in the
networked logging service. Each client logging daemon receives log records from client applications via some form of local IPC mechanism, such as shared memory, pipes, or sockets. The client logging daemon converts header fields from the received log records into network-byte order and uses a remote IPC mechanism, such as TCP/IP, to forward each record to a server logging daemon running on a designated host.
Server logging daemons collect and output the incoming log records they
receive from client applications via client logging daemons. A server log-ging daemon can determine which client host sent each message by using addressing information it obtains from the underlying Socket API. There’s
18 Section 1.6 Summary
int spawn (void) { if (ACE_OS::fork () == -1) ACE_ERROR (LM_ERROR, "unable to fork in function spawn");
SERVER CLIENT
TANGO
CLIENT MAMBO LOCAL IPC CLIENT
LOGGING DAEMON P1
P3
P2 TANGOHOST A MAMBOHOST B SERVER LOGGING DAEMON STORAGE DEVICE PRINTER LOCAL IPC CLIENT LOGGING DAEMON P1 P3 P2
Oct 31 14:50:28 [email protected]@18352@2@drwho::sending request to server tango Oct 31 14:48:13 [email protected]@38491@7@client::unable to fork in function spawn
C
COONNSSOOLLEE
NETWORK
if (Options::instance ()->debug()) ACE_DEBUG ((LM_DEBUG, "sending request to server %s", server_host));
TCP CONNECTION
TCP
CONNECTION
Figure 1.8: Processes and Daemons in the Networked Logging Service
generally one server logging daemon per system configuration, though they can be replicated to enhance fault tolerance.
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.
Object-oriented frameworks are crucial to improving networked application
devel-opment processes by reducing engineering cycle time and enhancing soft-ware 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 success-ful software development strategies. Patterns systematically capture ab-stract designs and software architectures in a format that’s intended to be comprehensible to developers. Frameworks and components reify concrete
Section 1.6 Summary 19
patterns, designs, algorithms, and implementations in particular program-ming languages.
The ACE toolkit provides over a half dozen high-quality frameworks that have been developed and refined over scores of person years. As shown in later chapters, ACE serves as an excellent case study of how to reap the benefits of—and reduce your exposure to the risks of—framework usage. As you read the remainder of the book, keep in mind the following points:
The ACE frameworks are much more than a class library. The ACE frameworks are a collection of classes that collaborate to provide semi-complete networked applications. Whereas the ACE library container classes described in [?] and the wrapper facades described in [SH02] are passive, the ACE frameworks are active and exhibit inversion of control at run-time. The ACE toolkit provides both frameworks and a library of classes to help programmers address a broad range of challenges that arise when developing networked applications.
The ACE frameworks provide a number of benefits, including improved reusability, modularity, and extensibility. The inversion of control in the ACE frameworks augments these benefits by separating application-defined concerns, such as event processing, from core framework con-cerns, such as event demultiplexing and event handler dispatching. A less tangible—but no less valuable—benefit of the ACE frameworks is the trans-fer of decades of accumulated knowledge from ACE framework developers to ACE framework users in the form of expertise embodied in well-tested, easily reusable C++ software artifacts.
Framework developers must resolve many design challenges. One of the most critical design challenges is determining which classes in a frame-work should be stable and which should be variable. Insufficient stability makes it hard for users to understand and apply the framework effectively, which makes the framework hard to use and may not satisfy the QoS requirements of performance-sensitive applications. Conversely, insuffi-cient variation makes it hard for users to customize framework classes, which results in a framework that can’t accommodate the functional re-quirements of diverse applications.
Framework development and training can be expensive. It took scores of person years to develop and mature the ACE frameworks. This develop-ment effort has been amortized over thousands of users, however, who can
20 Section 1.6 Summary
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
Incrementally learning enough to complete the tasks at hand via the many examples and tutorials in ACE and
Leveraging the goodwill and knowledge of the ACE user community via the ACE mailing lists and USENET newsgroup, which are de-scribed athttp://ace.ece.uci.edu/ACE/.
Foresight and experience help with integratability. Getting frameworks to play together nicely with other frameworks, class libraries, and legacy systems can be hard. ACE’s layered architecture provides a good example to follow since it enables integration at several levels of abstraction. For example, applications can integrate with ACE at its wrapper facade level, its framework level, or at its standards-based middleware level. Different applications benefit from integrating with ACE at different levels.
Framework maintenance and validation is challenging. A framework’s extensibility features, such as hierarchies of abstract classes and template parameterization, can make a priori validation hard. Moreover, framework evolution requires detailed knowledge of the areas being worked on, as well as how various framework and application classes collaborate and inter-act. Addressing these challenges effectively requires experienced develop-ers and a large set of automated regression tests. ACE’s open-source de-velopment model is also useful since the large ACE user base provides ex-tensive field testing, along with a collective body of experienced and skilled developers to suggest fixes for problems that arise.
Micro-level efficiency concerns are usually offset by other macro frame-work benefits. The ACE framework’s virtual methods and additional lev-els of redirection may incur some micro-level performance degradation on certain OS/compiler platforms. The expertise applied to the ACE frame-work’s design and implementation, however, often makes up for this over-head. For example, it may be possible to substitute completely differ-ent concurrency and synchronization strategies without affecting appli-cation functionality, thereby providing macro-level optimizations. Many ACE frameworks also contain novel optimizations that may not be com-mon knowledge to application developers. Finally, the added productivity
Section 1.6 Summary 21
benefits of employing the ACE framework usually offset any minor perfor-mance overheads.
CHAPTER 2
Service and Configuration Design
Dimensions
C
HAPTERS
YNOPSISA 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 con-stituent 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
Service Design Dimensions
This section covers the following service design dimensions: Short- vs. long-duration services
Internal vs. external services Stateful vs. stateless services
Layered/modular vs. monolithic services Single- vs. multi-service servers
24 Section 2.1 Service Design Dimensions
2.1.1
Short- vs. Long-Duration Services
The services offered by network servers can be classified loosely as
short-duration or long-short-duration. These two different time short-durations help to
deter-mine which protocols to use, as outlined below. The time reflects how long the service holds system resources, and should be evaluated in relation to server startup and shutdown requirements.
Short-duration services execute in brief, often fixed, amounts of time and
often handle only a single request. Examples of relatively short-duration services include computing the current time of day, resolving the Ethernet number of an IP address, and retrieving a disk block from a network file server. To minimize the amount of time spent setting up a reliable connec-tion, these services are often implemented using connectionless protocols, such as UDP/IP.
Long-duration services execute for extended, often variable, lengths of
time and may handle numerous requests during their lifetime. Examples of long-duration services include transferring a large file via FTP,
down-loading streaming video over HTTP, accessing host resources remotely via
TELNET, or performing remote file system backups over a network. To
im-prove efficiency and reliability, these services are often implemented with connection-oriented protocols, such as TCP/IP.
Logging service ) From the standpoint of an individual log record, our server logging daemon appears to be a short-duration service. The size of each log record is bounded by its maximum length (e.g., 4 Kbytes) and most messages are much smaller. The actual time spent handling a log record is relatively short. Since a client may transmit many log records in a session, however, we optimize performance by designing client logging daemons to open connections with their peer server logging daemons and then reusing these connections for many logging requests. It would be wasteful and time-consuming to set up and tear down a socket connection for each logging request, particularly if they are sent frequently. Thus, we model our server logging daemon as a long-duration service.
2.1.2
Internal vs. External Services
Services can be classified as internal or external. The primary tradeoffs in this dimension are service initialization time, safety of one service from
Section 2.1 Service Design Dimensions 25
another, and simplicity.
Internal services execute within the same address space as the server that
receives the request, as shown in Figure 2.1 (1). As described in Section ??,
(2) EXTERNAL SERVICES select()
(1) INTERNAL SERVICES
DISPATCHER PROCESS DISPATCHER PROCESS
SVC1 SVC2 SVC3
SVC1 SVC2
select()
Figure 2.1: Internal vs. External Services
an internal service can run iteratively or concurrently in relation to other internal services.
External services execute in different process address spaces. For
in-stance, Figure 2.1 (2) illustrates a master service process that monitors a set of network ports. When a connection request arrives from a client, the master accepts the connection and then spawns a new process to perform the requested service externally.
Some server frameworks support both internal and external services. For example, system administrators can choose between internal and ex-ternal services in INETD by modifying theinetd.confconfiguration file as follows:
INETD can be configured to execute short-duration services, such as
ECHO andDAYTIME, internally via calls to functions that are statically linked into the INETD program and
INETDcan also be configured to run longer-duration services, such as
FTPand TELNET, externally by spawning separate processes.
Internal services usually have lower initialization latency, but also may reduce application robustness since separate functions within a process aren’t 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
26 Section 2.1 Service Design Dimensions
indefinitely. To increase robustness, therefore, mission-critical application services are often implemented externally in separate processes.
Logging service ) Most of our logging servers examples in this book are designed as an internal service. As long as only one type of service is configured into our logging server we needn’t protect it from harmful side-effects of other services. There are valid reasons to protect the processing of different client sessions from each other, however, so Chapter ?? illustrates how to implement our logging server as an external service.
2.1.3
Stateful vs. Stateless Services
Services can be classified as stateful or stateless. The amount of state, or context, information that a service maintains between requests impacts both service and client complexity and resource consumption.
Stateful services cache certain information, such as session state,
au-thentication keys, identification numbers, and I/O handles, in a server to reduce communication and computation overhead. For instance, Web cookies enable state to be preserved on a Web server across multiple page requests.
Stateless services retain no volatile state within a server. For example, the
Network File System (NFS) provides distributed data storage and retrieval services that don’t maintain volatile state information within a server’s ad-dress space. Each request sent from a client is completely self-contained with the information needed to carry it out such as the file handle, byte count, starting file offset, and user credentials.
Stateful and stateless services trade off efficiency and reliability, with the right choice depending on a variety of factors, such as the probability and impact of host and network failures. Some common network appli-cations, such as FTP andTELNET, don’t require retention of persistent ap-plication state information between consecutive service invocations. These stateless services are generally fairly simple to configure and reconfigure reliably. Conversely, a middleware service like CORBA Naming [Obj98] manages various bindings whose values must be retained even if the server containing the service crashes.
Logging service)Our networked logging service is a stateless service. The logging server processes each record individually without respect to any
Section 2.1 Service Design Dimensions 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.
2.1.4
Layered/Modular vs. Monolithic Services
Service implementations can be classified as layered/modular or
mono-lithic. The primary tradeoffs in this dimension are service reusability,
ex-tensibility, and efficiency.
Layered/modular services decompose into a series of partitioned and
hi-erarchically-related tasks. For instance, application families, such as PBX network management services [SS94], can be specified and implemented as layered/modular services, as illustrated in Figure 2.2 (1). Each layer
(1) LAYERED
/
MODULARSERVICES (2)SERVICES MONOLITHIC
MODULE2 MODULE3 MODULE1 MODULE4 SVC1W SVC1R SVC2W SVC4W SVC2R SVC4R SVC3W SVC3R SVC1 SVC2 SVC4 SVC3 GLOBAL DATA MSG MSG MSG
Figure 2.2: Layered/Modular vs. Monolithic Services
can handle a self-contained portion of the overall service, such as input and output, event analysis and service processing, and event filtering. Inter-connected services can collaborate by exchanging control and data messages for incoming and outgoing communication.
Over the years, many communication frameworks have been devel-oped to simplify and automate the development and configuration of lay-ered/modular services [SS93]. Well-known examples include Systems V STREAMS [Rit84], the x-kernel [HP91], and the ACE Streaming framework covered in Chapter ??. In general, these frameworks decouple the protocol
28 Section 2.1 Service Design Dimensions
and service functionality from the following non-functional service design aspects:
1. Compositional strategies, such as the time and/or order in which services and protocols are composed together
2. Concurrency and synchronization strategies, such as task- vs. message-based architectures (described in Section ??) used to exe-cute services at run-time [SS95b].
Monolithic services are tightly coupled clumps of functionality that aren’t
organized hierarchically. They may contain separate modules of function-ality that vaguely resemble layers, but are most often tightly data coupled via shared, global variables, as shown in Figure 2.2 (2). They are also of-ten tightly functionally coupled, with control flow diagrams that look like spaghetti. Monolithic services are hard to understand, maintain, and ex-tend. While they may sometimes be appropriate in short-lived, ‘throw-away’ prototypes1 [FY99], they are rarely suitable for software that must be maintained and enhanced by multiple developers over time.
Developers can often select either layered or monolithic service archi-tectures to structure their networked applications. There are several ad-vantages to designing layered/modular services:
Layering enhances reuse since multiple higher-layer application com-ponents can share lower-layer services
Implementing applications via an inter-connected series of layered services enables transparent, incremental enhancement of their func-tionality
A layered/modular architecture facilitates macro-level performance improvements by allowing the selective omission of unnecessary ser-vice functionality and
Modular designs generally improve the implementation, testing, and maintenance of networked applications and services.
There can also be some disadvantages with using a layered/modular architecture to develop networked applications:
The modularity of layered implementations can introduce excessive overhead, e.g., layering may cause inefficiencies if buffer sizes don’t
1After you become proficient with the ACE toolkit it’ll be much faster to build a properly