Вы находитесь на странице: 1из 159

Developed by HUGHES NETWORK

SYSTEMS (HNS) for the benefit of the


ACE community at large

A Tutorial Introduction to the


ADAPTIVE Communication
Environment (ACE)

Umar Syyid
(usyyid@hotmail.com)

Acknowledgments
I would like to thank the following people for their assistance in making this tutorial possible,

AmbreenIlyasambreen@bitsmart.com
JamesCEJohnsonjcej@lads.com
AaronValdiviaavaldivia@hns.com
DouglasC.Schmidtschmidt@cs.wustl.edu
ThomasJordanace@programmer.net
ErikKoerbererik.koerber@siemens.at
MartinKrumpoleckrumpo@pobox.sk
FredKuhnsfredk@tango.cs.wustl.edu
SusanLiebeskindshl@cc.gatech.edu
AndyBellafaireamba@callisto.eciesyst.com
Marinamarina@cs.wustl.edu
JeanPaulGentyjpgenty@sesinsud.com
MikeCurtismccurry@mydeja.com
PhilippePerrinperrin@enseirb.fr
GunnarBasona98gunbu@student.his.se
JohnHarrisjohn.harris@tradingtechnologies.com

TABLE OF CONTENTS
Acknowledgments......................................................................................................................................0
TABLE OF CONTENTS................................................................................................................................I
THE ADAPTIVE COMMUNICATION ENVIRONMENT..........................................................................1
THE ACE ARCHITECTURE...............................................................................................................................2
The OS Adaptation Layer...........................................................................................................................2
The C++ wrappers layer...........................................................................................................................3
The ACE Framework Components.............................................................................................................4
IPC SAP...........................................................................................................................................................6
CATEGORIES OF CLASSES IN IPC SAP.............................................................................................................6
THE SOCKETS CLASS CATEGORY (ACE_SOCK).............................................................................................7
Using Streams in ACE................................................................................................................................8
Using Datagrams in ACE.........................................................................................................................12
Using Multicast with ACE........................................................................................................................15
MEMORY MANAGEMENT........................................................................................................................19
ALLOCATORS................................................................................................................................................20
Using the Cached Allocator......................................................................................................................20
ACE_MALLOC.............................................................................................................................................23
How ACE_Malloc works..........................................................................................................................24
Using ACE_Malloc..................................................................................................................................25
USING THE MALLOC CLASSES WITH THE ALLOCATOR INTERFACE...................................................................28
THREAD MANAGEMENT.........................................................................................................................29
CREATING AND CANCELING THREADS............................................................................................................29
SYNCHRONIZATION PRIMITIVES IN ACE.........................................................................................................32
The ACE Locks Category.........................................................................................................................32
Using the Mutex classes.......................................................................................................................................33
Using the Lock and Lock Adapter for dynamic binding..........................................................................................35
Using Tokens...................................................................................................................................................... 37

The ACE Guards Category.......................................................................................................................38


The ACE Conditions Category..................................................................................................................40
Miscellaneous Synchronization Classes....................................................................................................42
Barriers in ACE...................................................................................................................................................43
Atomic Op.......................................................................................................................................................... 44

THREAD MANAGEMENT WITH THE ACE_THREAD_MANAGER.........................................................................46


THREAD SPECIFIC STORAGE..........................................................................................................................49
TASKS AND ACTIVE OBJECTS.................................................................................................................52
ACTIVE OBJECTS..........................................................................................................................................52
ACE_TASK..................................................................................................................................................53
Structure of a Task....................................................................................................................................53
Creating and using a Task........................................................................................................................54
Communication between tasks..................................................................................................................55
THE ACTIVE OBJECT PATTERN......................................................................................................................58
How the Active Object Pattern Works.......................................................................................................58
THE REACTOR...........................................................................................................................................66
I

..
..
..
..
.

REACTOR COMPONENTS...............................................................................................................................66
EVENT HANDLERS........................................................................................................................................67
Registration of Event Handlers.................................................................................................................70
Removal and lifetime management of Event Handlers...............................................................................70
Implicit Removal of Event Handlers from the Reactors Internal dispatch tables.......................................................71
Explicit removal of Event Handlers from the Reactors Internal Dispatch Tables.......................................................71

EVENT HANDLING WITH THE REACTOR.........................................................................................................72


I/O Event De-multiplexing........................................................................................................................72
TIMERS........................................................................................................................................................76
ACE_Time_Value.....................................................................................................................................76
Setting and Removing Timers...................................................................................................................77
Using different Timer Queues...................................................................................................................78
HANDLING SIGNALS.....................................................................................................................................79
USING NOTIFICATIONS..................................................................................................................................79
THE ACCEPTOR AND CONNECTOR.......................................................................................................83
THE ACCEPTOR PATTERN.......................................................................................................................84
COMPONENTS.......................................................................................................................................85
USAGE....................................................................................................................................................86
THE CONNECTOR....................................................................................................................................90
USING THE ACCEPTOR AND CONNECTOR TOGETHER....................................................................91
ADVANCED SECTIONS...................................................................................................................................93
THE ACE_SVC_HANDLER CLASS.........................................................................................................94
ACE_Task.......................................................................................................................................................... 94
An Architecture: Communicating Tasks................................................................................................................94
Creating an ACE_ Svc_Handler...........................................................................................................................95
Creating multiple threads in the Service Handler....................................................................................................95
Using the message queue facilities in the Service Handler......................................................................................99

HOW THE ACCEPTOR AND CONNECTOR PATTERNS WORK..........................................................103


Endpoint or connection initialization phase.............................................................................................103
Service Initialization Phase for the Acceptor...........................................................................................104
Service Initialization Phase for the Connector.........................................................................................105
Service Processing.................................................................................................................................106
TUNING THE ACCEPTOR AND CONNECTOR POLICIES...................................................................106
The ACE_Strategy_Connector and ACE_Strategy_Acceptor classes........................................................106
Using the Strategy Acceptor and Connector.........................................................................................................107
Using the ACE_Cached_Connect_Strategy for Connection caching......................................................................109

USING SIMPLE EVENT HANDLERS WITH THE ACCEPTOR AND CONNECTOR PATTERNS...................................114
THE SERVICE CONFIGURATOR............................................................................................................116
FRAMEWORK COMPONENTS........................................................................................................................116
SPECIFYING THE CONFIGURATION FILE.........................................................................................................118
Starting a service...............................................................................................................................................118
Suspending or resuming a service.......................................................................................................................118
Stopping a service..............................................................................................................................................119

WRITING SERVICES.....................................................................................................................................119
USING THE SERVICE MANAGER...................................................................................................................123
MESSAGE QUEUES...................................................................................................................................127
MESSAGE BLOCKS......................................................................................................................................127
Constructing Message Blocks.................................................................................................................128
Inserting and manipulating data in a message block................................................................................130
MESSAGE QUEUES IN ACE.........................................................................................................................131
WATER MARKS..........................................................................................................................................135
USING MESSAGE QUEUE ITERATORS...........................................................................................................135
DYNAMIC OR REAL-TIME MESSAGE QUEUES..............................................................................................138

II

..
..
..
.. CLASSES..............................................................................................................145
APPENDIX: UTILITY
.
ADDRESS WRAPPER CLASSES.....................................................................................................................145
ACE_INET_Addr...................................................................................................................................145
ACE_UNIX_Addr..................................................................................................................................145
TIME WRAPPER CLASSES.............................................................................................................................145
ACE_Time_Value...................................................................................................................................145
LOGGING WITH ACE_DEBUG AND ACE_ERROR.....................................................................................145
OBTAINING COMMAND LINE ARGUMENTS....................................................................................................147
ACE_Get_Opt........................................................................................................................................147
ACE_Arg_Shifter...................................................................................................................................148

REFERENCES............................................................................................................................................151

III

Chapter

1
The Adaptive Communication Environment
An introduction
TheAdaptiveCommunicationEnvironment(ACE)isawidelyused,opensourceobject
oriented toolkit written in C++ that implements core concurrency and networking
patternsforcommunicationsoftware.ACEincludesmanycomponentsthatsimplifythe
development of communication software, thereby enhancing flexibility, efficiency,
reliability and portability. Components in the ACE framework provide the following
capabilities:

Concurrency and synchronization.

Interprocess communication (IPC)

Memory management.

Timers

Signals

File system management

Thread management

Event demultiplexing and handler dispatching.

Connection establishment and service initialization.

Static and dynamic configuration and reconfiguration of software.

Layered protocol construction and stream-based frameworks.

Distributed communication services naming, logging, time synchronization,


event routing and network locking. etc.

The framework components provided by ACE are based on a family of patterns that have
been applied successfully to thousands of commercial systems over the past decade.
Additional information on these patterns is available in the book Pattern-Oriented Software
Architecture: Patterns for Concurrent and Networked Objects, written by Douglas C.
Schmidt, Michael Stal, Hans Rohnert, and Frank Buschmann and published in 2000 by Wiley
and Sons.
1

..
..
..
..
.
The ACE Architecture
ACEhasalayereddesign,withthefollowingthreebasiclayersinitsarchitecture:

The operating system (OS) adaptation layer

The C++ wrapper faade layer

The frameworks and patterns layer

Each of these layers is shown in the figure below and described in the following sections.

The OS Adaptation Layer


TheOSAdaptationisathinlayerofC++codethatsitsbetweenthenativeOSAPIsand
therestofACE.ThislayershieldsthehigherlayersofACEfromplatformdependencies,
whichmakescodewrittenwithACErelativelyplatformindependent.Thus,withlittleor
noeffortdeveloperscanmoveanACEapplicationfromplatformtoplatform.
TheOSadaptationlayerisalsothereasonwhytheACEframeworkisavailableonso
manyplatforms.AfewoftheOSplatformsonwhichACEisavailablecurrently,include;
realtime operating systems, (VxWorks, Chorus, LynxOS, RTEMS, OS/9, QNX
Neutrion,andpSoS),mostversionsofUNIX(SunOS4.xand5.x;SGIIRIX5.xand6.x;
HPUX9.x,10.xand11.x;DECUNIX3.xand4.x;AIX3.xand4.x;DG/UX;Linux;
SCO;UnixWare;NetBSDandFreeBSD),Win32(WinNT3.5.x,4.x,Win95andWinCE
usingMSVC++andBorlandC++),MVSOpenEdition,andCrayUNICOS.

..
..
..
..
.
The C++ Wrapper
Facade Layer
TheC++wrapperfacadelayerincludesC++classesthatcanbeusedtobuildhighly
portableandtypesafeC++applications.ThisisthelargestpartoftheACEtoolkitand
includesapproximately50%ofthetotalsourcecode.C++wrapperclassesareavailable
for:

Concurrency and synchronization ACE provides several concurrency and


synchronization wrapper faade classes that abstract the native OS multi-threading and
multi-processing API. These wrapper facades encapsulate synchronization primitives,
such as semaphores, file locks, barriers, and dondition variables. Higher-level
synchronization utilities, such as Guards, are also available. All these primitives share
similar interfaces and thus are easy to use and substitute for one another.
IPC components ACE provides several C++ wrapper faade classes that
encapsulate different inter-process communication (IPC) interfaces that are available on
different operating systems. For example, wrapper faade classes are provided to
encapsulate IPC mechanisms, such as BSD Sockets, TLI, UNIX FIFOs, STREAM Pipes,
Win32 Named Pipes. ACE also provides message queue classes, and wrapper facades for
certain real-time OS-specific message queues.
Memory management components ACE includes classes to allocate and
deallocate memory dynamically, as well as pre-allocation of dynamic memory. This
memory is then managed locally with the help of management classes provided in ACE.
Fine-grain memory management is necessary in most real-time and embedded systems.
There are also classes to flexibly manage inter-process shared memory.
Timer classes Various classes are available to handle scheduling and
canceling of timers. Different varieties of timers in ACE use different underlying
mechanisms (e.g., heaps, timer wheels, or ordered lists) to provide varying performance
characteristics. Regardless of which underlying mechanism is used, however, the
interface to these classes remains the same, which makes it easy to use any timer
implementations. In addition to these timer classes, wrapper faade classes are available
for high-resolution timers (which are available on some platforms, such as VxWorks,
Win32/Pentium, AIX and Solaris) and Profile Timers.
Container classes ACE also includes several portable STL-type container
classes, such as Map, Hash_Map, Set, List, and Array.
Signal handling ACE provides wrapper faade classes that encapsulate the
OS-specific signal handling interface. These classes simplify the installation and removal
of signal handlers and allow the installation of several handlers for one signal. Also
available are signal guard classes that can be used to selectively disable some or all signals
in the scope of the guard.
Filesystem components ACE contains classes that wrap the filesystem API.
These classes include wrappers for file I/O, asynchronous file I/O, file locking, file
streams, file connection, etc.

..
..
..
.. Thread management ACE provides wrapper facades classes to create and
.
manage threads. These wrappers also encapsulate the OS-specific threading API and can
be used to provide advanced functionality, such as thread-specific storage.

The ACE Framework Components


TheACEframeworkcomponentsarethehighestlevelbuildingblocksavailableinACE.
These framework components are based on several design patterns specific to the
communicationsoftwaredomain.Adesignercanusetheseframeworkcomponentsto
buildsystemsatamuchhigherlevelthanthenativeOSAPIcalls.Theseframework
componentsarethereforenotonlyusefulintheimplementationstageofdevelopment,
butalsoatthedesignstage,sincetheyprovideasetofmicroarchitecturesandpattern
langauges for the system being built. This layer of ACE contains the following
frameworkcomponents:

Event handling framework Most communication software includes a large


amount of code to handle various types of events, such as I/O-based, timer-based, signalbased, and synchronization-based events. These events must be efficiently de-multiplexed,
dispatched and handled by the software. Unfortunately, developers historically end up reinventing the wheel by writing this code repeatedly since their event de-multiplexing,
dispatching, and handling code were tightly coupled and could not be used independent of
one another. ACE includes a framework component called the Reactor to solve this
problem. The Reactor provides code for efficient event de-multiplexing and dispatching,
which de-couples the event demultiplexing and dispatch code from the handling code,
thereby enhancing re-usability and flexibility.
Connection and service initialization components ACE includes
Connector and Acceptor components that decouple the initiation of a connection from
the service performed by the application after the connection has been established. This
component is useful in application servers that receive a large number of connection
requests. The connections are initialized first in an application-specific manner and then
each connection can be handled differently via the appropriate handling routine. This
decoupling allows developers to focus on the handling and initialization of connections
separately. Therefore, if at a later stage developers determine the number of connection
requests are different than they estimated, they can chose to use a different set of
initialization policies (ACE includes a variety of default policies) to achieve the required
level of performance.
Stream framework The ACE Streams framework simplifies the
development of software that is intrinsically layered or hierarchic. A good example is the
development of user-level protocol stacks that are composed of several interconnected
layers. These layers can largely be developed independently from each other. Each layer
processes and changes the data as it passes through the stream and then passes it along to
the next layer for further processing. Since layer can be designed and configured
independently of each other they are more easily re-used and replaced.
Service Configuration framework Another problem faced by
communication software developers is that software services often must be configured at
installation time and then be reconfigured at run-time. The implementation of a certain
4

..
..
..
.
service in.. an application may require change and thus the application must be
reconfigured with the update service. The ACE Service Configurator framework
supports dynamic initialization, suspend, resumption, reconfiguration, and termination of
services provided by an application.
Although there have been rapid advances in the field of computer networks, the
developmentofcommunicationsoftwarehasbecomemoreharder.Muchoftheeffort
expended on developing communication software involves reinventing the wheel,
wherecomponentsthatareknowntobecommonacrossapplicationsarerewrittenrather
thenreused.ACEaddressesthisproblembyintegratingcommoncomponents,micro
architectures, andinstances ofpattern languges that areknowntobe reusable in the
networkandsystemsprogrammingdomains.Thus,applicationdeveloperscandownload
andlearnACE,pickandchoosethecomponentsneededtouseintheirapplications,and
buildandintegrateconcurrentnetworkingapplicationsquickly.Inadditiontocapturing
simplebuildingblocksinitsC++wrapperfacadelayer,ACEincludeslargerframework
componentsthatcaptureprovenmicroarchitecturesandpatternlanguagesthatareuseful
intherealmofcommunicationsoftware.

IPC SAP
Chapter

Interprocess communication Service Access Point wrappers


Sockets, TLI, STREAM pipes and FIFOs provide a wide range of interfaces for
accessingbothlocalandglobalIPCmechanisms.However,therearemanyproblems
associatedwiththesenonuniforminterfaces.Problemssuchaslackoftypesafetyand
multipledimensionsofcomplexityleadtoproblematicanderrorproneprogramming.
TheIPCSAPclasscategoryinACEprovidesauniformhierarchiccategoryofclasses
thatencapsulatethesetediousanderrorproneinterfaces.IPCSAPisdesignedtoimprove
thecorrectness,easeoflearning,portabilityandreusabilityofcommunicationsoftware
whilemaintaininghighperformance.

Categories of classes in IPC SAP


ACE_IPC_SAP

ACE_SOCK

ACE_TLI

ACE_SPIPE

ACE_FIFO

The IPC SAP classes are divided into four major categories based on the different
underlyingIPCinterfacetheyareusing.Theclassdiagramaboveillustratesthisdivision.
TheACE_IPC_SAPclassprovidesafewfunctionsthatarecommontoallIPCinterfaces.
Fromthisclass,fourdifferentclassesarederived.EachclassrepresentsacategoryofIPC
SAPwrapperclassesthatACEcontains.Theseclassesencapsulatefunctionalitythatis
common to a particular IPC interface. For example, the ACE_SOCK class contains
functionsthatarecommontotheBSDsocketsprogramminginterfacewhereasACE_TLI
wrapstheTLIprogramminginterface.
6

Underneath eachofthesefourclasses lies awholehierarchy ofwrapperclassesthat


completelywraptheunderlyinginterfaceandprovidehighlyreusable,modular,safeand
easytousewrapperclasses.

The Sockets Class Category (ACE_SOCK)


TheclassesinthiscategoryalllieundertheACE_SOCKclass.Thiscategoryprovidesan
interface to the Internet domain and UNIX domain protocol families using the BSD
sockets programming interface. The family of classes in this category are further
subdividedas:

Dgram Classes and Stream Classes: The Dgram classes are based on the UDP
datagram protocol and provide unreliable connectionless messaging functionality.
The Stream Classes, on the other hand, are based on the TCP protocol and provide
connection-oriented messaging.
Acceptor, Connector Classes and Stream Classes: The Acceptor and
Connector classes are used to passively and actively establish connections,
respectively. The Acceptor classes encapsulates the BSD accept() call and the
Connector encapsulates the BSD connect() call. The Stream classes are used
AFTER a connection has been established to provide bi-directional data flow and
contain send and receive methods.

TheTablebelowdetailstheclassesinthiscategoryandwhattheirresponsibilitiesare:
Class Name

Responsibility

ACE_SOCK_Acceptor

UsedforpassiveconnectionestablishmentbasedontheBSD
accept()andlisten()calls.

ACE_SOCK_Connector

UsedforactiveconnectionestablishmentbasedontheBSD
connect()call.

ACE_SOCK_Dgram

Used to provide UDP (User Datagram Protocol) based


connectionlessmessagingservices.Encapsulatescallssuchas
sendto()andreceivefrom()andprovidesasimplesend()and
recv()interface.

ACE_SOCK_IO

Used to provide a connectionoriented messaging service.


Encapsulates calls such as send(), recv() and write(). This
class is the base class for the ACE_SOCK_Stream and
ACE_SOCK_CODgramclasses.

ACE_SOCK_Stream

UsedtoprovideTCP(TransmissionControlProtocol)based
connectionoriented messaging service. Derives from
ACE_SOCK_IOandprovidesfurtherwrappermethods.

ACE_SOCK_CODgram

Used to provide a connected datagram abstraction. Derives


fromACE_SOCK_IOandincludesanopen()method,which
causesabind()tothelocaladdressspecifiedandconnectsto
theremoteaddressusingUDP.
7

ACE_SOCK_Dgram_Mcast

Used to provide a datagrambased multicast abstraction.


Includesmethodsforsubscribingtoamulticastgroupaswell
assendingandreceivingmessages.

ACE_SOCK_Dgram_Bcast

Used to provide a datagrambased broadcast abstraction.


Includes methods to broadcast datagram message to all
interfacesinasubnet.

Inthefollowingsections,wewillillustratehowtheIPC_SAPwrapperclasssesareused
directlytohandleinterprocesscommunication.Rememberthatthisisjustthetipofthe
iceberginACE.Allthegoodpatternorientedtoolscomeinlaterchaptersofthistutorial.

Using Streams in ACE


TheStreamswrappersinACEprovideconnectionorientedcommunication.TheStreams
data transfer wrapper classes include ACE_SOCK_Stream and ACE_LSOCK_Stream,
whichwraptheTCP/IPandUNIXdomainsocketsprotocolsdatatransferfunctionality,
respectively.Theconnectionestablishmentclassesinclude ACE_SOCK_Connectorand
ACE_SOCK_Acceptor
for TCP/IP, and
ACE_LSOCK_Connector
and
ACE_LSOCK_AcceptorforUNIXdomainsockets.
TheAcceptorclassisusedtopassivelyacceptconnections(usingtheBSDaccept()call)
and the Connector class is used to actively establish connections (using the BSD
connect()call).
Thefollowingexampleillustrateshowacceptorsandconnectorsareusedtoestablisha
connection.Thisconnectionisthenusedtotransferdatausingthestreamdatatransfer
classes.
Example1
#include"ace/SOCK_Acceptor.h"
#include"ace/SOCK_Stream.h"
#defineSIZE_DATA18
#defineSIZE_BUF1024
#defineNO_ITERATIONS5
classServer{
public:
Server(intport):
server_addr_(port),peer_acceptor_(server_addr_)
{
data_buf_=newchar[SIZE_BUF];
}
//Handletheconnectiononceithasbeenestablished.Herethe
//connectionishandledbyreadingSIZE_DATAamountofdatafromthe
//remoteandthenclosingtheconnectionstreamdown.
inthandle_connection()

{
//Readdatafromclient
for(inti=0;i<NO_ITERATIONS;i++){
intbyte_count=0;
if((byte_count=new_stream_.recv_n(data_buf_,SIZE_DATA,0))==1)
ACE_ERROR((LM_ERROR,"%p\n","Errorinrecv"));
else{
data_buf_[byte_count]=0;
ACE_DEBUG((LM_DEBUG,"Serverreceived%s\n",data_buf_));
}
}
//Closenewendpoint
if(new_stream_.close()==1)
ACE_ERROR((LM_ERROR,"%p\n","close"));
return0;
}
//Usetheacceptorcomponentpeer_acceptor_toaccepttheconnection
//intotheunderlyingstreamnew_stream_.Aftertheconnectionhasbeen
//establishedcallthehandle_connection()method.
intaccept_connections()
{
if(peer_acceptor_.get_local_addr(server_addr_)==1)
ACE_ERROR_RETURN((LM_ERROR,"%p\n","Erroringet_local_addr"),1);
ACE_DEBUG((LM_DEBUG,"Startingserveratport%d\n",
server_addr_.get_port_number()));

//Performstheiterativeserveractivities.
while(1){
ACE_Time_Valuetimeout(ACE_DEFAULT_TIMEOUT);
if(peer_acceptor_.accept(new_stream_,&client_addr_,&timeout)==1){
ACE_ERROR((LM_ERROR,"%p\n","accept"));
continue;
}
else{
ACE_DEBUG((LM_DEBUG,
"Connectionestablishedwithremote%s:%d\n",
client_addr_.get_host_name(),client_addr_.get_port_number()));
//Handletheconnection
handle_connection();
}
}
private:
char*data_buf_;
ACE_INET_Addrserver_addr_;
ACE_INET_Addrclient_addr_;
ACE_SOCK_Acceptorpeer_acceptor_;
ACE_SOCK_Streamnew_stream_;
};

intmain(intargc,char*argv[])
{
if(argc<2){
ACE_ERROR((LM_ERROR,"Usage%s<port_num>",argv[0]));
ACE_OS::exit(1);
}
Serverserver(ACE_OS::atoi(argv[1]));
server.accept_connections();
}

In the example above, a passive server is created which listens for incoming client
connections.Afteraconnectionisestablishedtheserverreceivesdatafromtheclientand
closestheconnectiondown.TheServerclassrepresentsthisserver.
The Server classcontainsamethod accept_connections() whichusesanacceptor,i.e.
ACE_SOCK_Acceptor, to accept the connection into the ACE_SOCK_Stream
new_stream_. Thisisdonebycalling accept() ontheacceptorandpassinginthe
streamwhichwewantittoaccepttheconnectioninto. Onceaconnectionhasbeen
establishedintoastream,thenthestreamwrappers, send() and recv() methodscanbe
usedtosendandreceivedataoverthenewlyestablishedlink.Theaccept()methodfor
theacceptorisalsopassedinablankACE_INET_Addr,whichitsetstotheaddressofthe
remotemachinethathadinitiatedtheconnection.
After the connection has been established, the server calls the handle_connection()
method,whichproceedstoreadapreknownwordfromtheclientandthenclosesdown
thestream.This maybeanonrealistic scenarioforaserverwhichhandles multiple
clients. What wouldprobablyhappeninarealworldsituation is thattheconnection
wouldbehandledineitheraseparatethreadorprocess.Howsuchmultithreadingand
multiprocess type handling is done will be illustrated time and again in subsequent
chapters.
Theconnectioniscloseddownbycallingtheclose()methodonthestream.Themethod
willreleaseallsocketresourcesandterminatetheconnection.
ThenextexampleillustrateshowtouseaConnectorinconjunctionwiththeAcceptor
showninthepreviousexample.
Example2
#include"ace/SOCK_Connector.h"
#include"ace/INET_Addr.h"
#defineSIZE_BUF128
#defineNO_ITERATIONS5
classClient{
public:
Client(char*hostname,intport):remote_addr_(port,hostname)
{
data_buf_="HellofromClient";
}
//Usesaconnectorcomponent`connector_toconnecttoa

10

//remotemachineandpasstheconnectionintoastream
//componentclient_stream_
intconnect_to_server()
{
//Initiateblockingconnectionwithserver.
ACE_DEBUG((LM_DEBUG,"(%P|%t)Startingconnectto%s:%d\n",

remote_addr_.get_host_name(),remote_addr_.get_port_number()));
if(connector_.connect(client_stream_,remote_addr_)==1)
ACE_ERROR_RETURN((LM_ERROR,"(%P|%t)%p\n","connectionfailed"),1);
else
ACE_DEBUG((LM_DEBUG,"(%P|%t)connectedto%s\n",

remote_addr_.get_host_name()));
return0;
}
//Usesastreamcomponenttosenddatatotheremotehost.
intsend_to_server()
{
//Senddatatoserver
for(inti=0;i<NO_ITERATIONS;i++){
if(client_stream_.send_n(data_buf_,ACE_OS::strlen(data_buf_)+1,0)==1){
ACE_ERROR_RETURN((LM_ERROR,"(%P|%t)%p\n","send_n"),0);
break;
}
}
//Closedowntheconnection
close();
}
//Closedowntheconnectionproperly.
intclose()
{
if(client_stream_.close()==1)
ACE_ERROR_RETURN((LM_ERROR,"(%P|%t)%p\n","close"),1);
else
return0;
}
private:
ACE_SOCK_Streamclient_stream_;
ACE_INET_Addrremote_addr_;
ACE_SOCK_Connectorconnector_;
char*data_buf_;
};
intmain(intargc,char*argv[])
{
if(argc<3){
ACE_DEBUG((LM_DEBUG,Usage%s<hostname><port_number>\n,argv[0]));
ACE_OS::exit(1);
}
Clientclient(argv[1],ACE_OS::atoi(argv[2]));
client.connect_to_server();

11

client.send_to_server();
}

Theaboveexampleillustratesaclientthatactivelyconnectstotheserverwhichwas
describedinExample1.Afterestablishingaconnection,itsendsasinglestringofdatato
theserverseveraltimesandclosesdowntheconnection.
TheclientisrepresentedbyasingleClientclass.Client containsaconnect_to_server()
andasend_to_server()method.
The connect_to_server() method uses a Connector, connector_, of type
ACE_SOCK_Connector,toactivelyestablishaconnection.Theconnectionsetupisdone
by calling the connect() method on the Connector connector_, passing it the
remote_addr_ofthemachinewewishtoconnectto,andanemptyACE_SOCK_Stream
client_stream_toestablishtheconnectioninto.Theremotemachineisspecifiedinthe
runtimeargumentsoftheexample.Oncetheconnect()methodreturnssuccessfully,the
streamcanbeusedtosendandreceivedataoverthenewlyestablishedlink.Thisis
accomplished by using the send() and recv() family of methods available in the
ACE_SOCK_Streamwrapperclass.
Intheexample,oncetheconnectionhasbeenestablished,thesend_to_server()methodis
calledtosendasinglestringtotheserverNO_ITERATIONStimes.Asmentionedbefore,
thisisdonebyusingthesend()methodsofthestreamwrapperclass.

Using Datagrams in ACE


The Datagrams wrapper classes in ACE are ACE_SOCK_Dgram and
ACE_LSOCK_Dgram. Thesewrappersincludemethodstosendandreceivedatagrams
andwrapthenonconnectionorientedUDPandUNIXdomainsocketsprotocol.Unlike
the Streams wrapper, these wrappers wrap a nonconnectionoriented protocol. This
meansthattherearenoacceptorsandconnectorsthatareusedtosetupaconnection.
Instead, in this case, communication is through a series ofsends and receives. Each
send()indicatesthedestinationremoteaddressasaparameter.Thefollowingexample
illustrateshowdatagramsareusedwithACE.TheexampleusestheACE_SOCK_Dgram
wrapper(i.e.,theUDPwrapper).TheACE_LSOCK_Dgram,whichwrapsUNIXdomain
datagrams, couldalsobeused.Theusageofbothwrappers is verysimilar,theonly
differenceisthatlocaldatagramsusetheACE_UNIX_Addrclassforaddressesinsteadof
ACE_INET_Addr.
Example3
//Server
#include"ace/OS.h"
#include"ace/SOCK_Dgram.h"
#include"ace/INET_Addr.h"
#defineDATA_BUFFER_SIZE1024
#defineSIZE_DATA19

12

classServer{
public:
Server(intlocal_port)
:local_addr_(local_port),local_(local_addr_)
{
data_buf=newchar[DATA_BUFFER_SIZE];
}
//Expectdatatoarrivefromtheremotemachine.Acceptitanddisplay
//it.Afterreceivingdata,immediatelysendsomedatabacktothe
//remote.
intaccept_data(){
intbyte_count=0;
while((byte_count=local_.recv(data_buf,SIZE_DATA,remote_addr_))!=1){
data_buf[byte_count]=0;
ACE_DEBUG((LM_DEBUG,"Datareceivedfromremote%swas%s\n"
,remote_addr_.get_host_name(),data_buf));
ACE_OS::sleep(1);
if(send_data()==1)break;
}
return1;
}

//Methodusedtosenddatatotheremoteusingthedatagramcomponent
//local_
intsend_data()
{
ACE_DEBUG((LM_DEBUG,"Preparingtosendreplytoclient%s:%d\n",
remote_addr_.get_host_name(),remote_addr_.get_port_number()));
ACE_OS::sprintf(data_buf,"Serversayshellotoyoutoo");
if(
local_.send(data_buf,ACE_OS::strlen(data_buf)+1,remote_addr_)==1)
return1;
else
return0;
}
private:
char*data_buf;
ACE_INET_Addrremote_addr_;
ACE_INET_Addrlocal_addr_;
ACE_SOCK_Dgramlocal_;
};
intmain(intargc,char*argv[])
{
if(argc<2){
ACE_DEBUG((LM_DEBUG,"Usage%s<PortNumber>",argv[0]));
ACE_OS::exit(1);
}
Serverserver(ACE_OS::atoi(argv[1]));
server.accept_data();

13

The above code is for a simple server that expects a client application to send it a
datagramonaknownport.Thedatagramcontainsafixedandpredeterminedamountof
datainit.Theserver,onreceptionofthisdata,proceedstosendareplybacktotheclient
thatoriginallysentthedata.
Thesingleclass Server containsan ACE_SOCK_Dgram named local_ asaprivate
memberwhichitusesbothtoreceiveandsenddata.TheServerinstantiateslocal_in
itsconstructor,withaknownACE_INET_Addr(localhostwithknownport)sotheclient
canlocateitandsendmessagestoit.
Theclasscontainstwomethods:accept_data(),usedtoreceivedatafromtheclient(uses
thewrappersrecv()call)andsend_data(),usedtosenddatatotheremoteclient(usesthe
wrapperssend()call).Noticethattheunderlyingcallsforboththesend()andreceive()of
the local_ wrapper class wrap the BSD sendto() and recvfrom() calls and have a
similarsignature.
Themainfunctionjustinstantiatesanobjectoftypeserverandcallsthe accept_data()
methodonitwhichwaitsfordatafromtheclient.Whenitgetsthedataitisexpectingit
calls send_data()tosendareplymessagebacktotheclient.Thisgoesonforeveruntil
theclientiskilled.
Thecorrespondingclientcodeisverysimilar:
Example4
//Client
#include"ace/OS.h"
#include"ace/SOCK_Dgram.h"
#include"ace/INET_Addr.h"
#defineDATA_BUFFER_SIZE1024
#defineSIZE_DATA28
classClient{
public:
Client(constchar*remote_host_and_port)
:remote_addr_(remote_host_and_port),
local_addr_((u_short)0),local_(local_addr_)
{
data_buf=newchar[DATA_BUFFER_SIZE];
}

//Receivedatafromtheremotehostusingthedatgramwrapper`local_.
//Theaddressoftheremotemachineisreceivedin`remote_addr_
//whichisoftypeACE_INET_Addr.Rememberthatthereisnoestablished
//connection.
intaccept_data()
{
if(local_.recv(data_buf,SIZE_DATA,remote_addr_)!=1){
ACE_DEBUG((LM_DEBUG,"Datareceivedfromremoteserver%swas:%s\n",

14

remote_addr_.get_host_name(),data_buf));
return0;
}
else
return1;
}
//Senddatatotheremote.Oncedatahasbeensentwaitforareply
//fromtheserver.
intsend_data()
{
ACE_DEBUG((LM_DEBUG,"Preparingtosenddatatoserver%s:%d\n",
remote_addr_.get_host_name(),remote_addr_.get_port_number()));
ACE_OS::sprintf(data_buf,"Clientsayshello");
while(local_.send(data_buf,ACE_OS::strlen(data_buf),remote_addr_)!=1){
ACE_OS::sleep(1);
if(accept_data()==1)
break;
}
return1;
}
private:
char*data_buf;
ACE_INET_Addrremote_addr_;
ACE_INET_Addrlocal_addr_;
ACE_SOCK_Dgramlocal_;
};
intmain(intargc,char*argv[])
{
if(argc<2){
ACE_OS::printf("Usage:%s<hostname:port_number>\n",argv[0]);
ACE_OS::exit(1);
}
Clientclient(argv[1]);
client.send_data();
}

Using Multicast with ACE


Youwillfind,onseveraloccasions,thatthesamemessagehastobesenttoamultitude
ofclientsorserversinyourdistributedsystem.Forexample,timeadjustmentupdatesor
otherperiodicinformationmayhavetobebroadcastedtoaparticularsetofterminals.
Multicastingisusedtoaddressthisproblem.Itallowsbroadcasting,nottoallterminals,
buttoacertainsubsetorgroupofterminals.Youcanthereforethinkofmulticastasa
kindofcontrolledbroadcastmechanism.Multicastingisafeaturewhichisnowavailable
onmostmodernoperatingsystems.

15

ACE provides for an unreliable multicast wrapper ACE_SOCK_Dgram_Mcast that


allowsprogrammerstosenddatagrammessagestoacontrolledgroup,calledamulticast
group.Thisgroupisidentifiedbyauniquemulticastaddress.
Clients and Servers that are interested in receiving broadcasts on this address must
subscribetoit.(alsocalledsubscribingtothemulticastgroup). Alltheprocessesthat
havesubscribedtothemulticastgroupwillthenreceiveanydatagrammessagesenttothe
group.Anapplicationthatwishestosendmessagestothemulticastgroup,butnotlisten
tothem,doesnothavetosubscribetothemulticastgroup.Infact,suchasendercanuse
theplainold ACE_SOCK_Dgram wrapperto send messagestothemulticastaddress.
Themessagesentwillthanbereceivedbytheentiremulticastgroup.
In ACE, multicast functionality is encapsulated in ACE_SOCK_Dgram_Mcast. This
includesfunctionstosubscribe,unsubscribeandreceiveonthemulticastgroup.
ThefollowingexamplesillustratehowmulticastingcanbeusedinACE.
Example5
#include"ace/SOCK_Dgram_Mcast.h"
#include"ace/OS.h"
#defineDEFAULT_MULTICAST_ADDR"224.9.9.2"
#defineTIMEOUT5
//Thefollowingclassisusedtoreceivemulticastmessagesfrom
//anysender.
classReceiver_Multicast{
public:
Receiver_Multicast(intport):
mcast_addr_(port,DEFAULT_MULTICAST_ADDR),remote_addr_((u_short)0)
{
//Subscribetomulticastaddress.
if(mcast_dgram_.subscribe(mcast_addr_)==1){
ACE_DEBUG((LM_DEBUG,"ErrorinsubscribingtoMulticastaddress\n"));
exit(1);
}
}
~Receiver_Multicast()
{
if(mcast_dgram_.unsubscribe()==1)
ACE_DEBUG((LM_ERROR,"ErrorinunsubscribingfromMcastgroup\n"));
}
//Receivedatafromsomeonewhoissendingdataonthemulticastgroup
//address.Todosoitmustusethemulticastdatagramcomponent
//mcast_dgram_.
intrecv_multicast()
{
//getreadytoreceivedatafromthesender.
if(mcast_dgram_.recv(&mcast_info,sizeof(mcast_info),remote_addr_)==1)

16

return1;
else{
ACE_DEBUG((LM_DEBUG,"(%P|%t)Receivedmulticastfrom%s:%d.\n",
remote_addr_.get_host_name(),remote_addr_.get_port_number()));
ACE_DEBUG((LM_DEBUG,"Successfullyreceived%d\n",mcast_info));
return0;
}
}

private:
ACE_INET_Addrmcast_addr_;
ACE_INET_Addrremote_addr_;
ACE_SOCK_Dgram_Mcastmcast_dgram_;
intmcast_info;
};

intmain(intargc,char*argv[])
{
Receiver_Multicastm(2000);
//Willrunforever
while(m.recv_multicast()!=1){
ACE_DEBUG((LM_DEBUG,"Multicastersuccessful\n"));
}
ACE_DEBUG((LM_ERROR,"Multicasterfailed\n"));
exit(1);
}

Theaboveexample showshowanapplication canuse ACE_SOCK_Dgram_Mcast to


subscribetoandreceivemessagesfromamulticastgroup.
Theconstructorofthe Receiver_Multicast classsubscribestheobjecttothemulticast
groupandthedestructoroftheobjectunsubscribes.Oncesubscribed,theapplication
waitsforeverforanydatathatissenttothemulticastaddress.
Thenextexampleshowshowanapplicationcansenddatagrammessagestothemulticast
addressorgroupusingtheACE_SOCK_Dgramwrapperclass.
Example6
#include"ace/SOCK_Dgram_Mcast.h"
#include"ace/OS.h"
#defineDEFAULT_MULTICAST_ADDR"224.9.9.2"
#defineTIMEOUT5
classSender_Multicast{
public:
Sender_Multicast(intport):
local_addr_((u_short)0),dgram_(local_addr_),
multicast_addr_(port,DEFAULT_MULTICAST_ADDR)
{
}

17

//Methodwhichusesasimpledatagramcomponenttosenddatatothe//multicastgroup.
intsend_to_multicast_group()
{
//Converttheinformationwewishtosendintonetworkbyteorder
mcast_info=htons(1000);

//Sendmulticast
if(dgram_.send(&mcast_info,sizeof(mcast_info),multicast_addr_)==1)
return1;

ACE_DEBUG((LM_DEBUG,
"%s;Sentmulticasttogroup.Numbersentis%d.\n",
__FILE__,
mcast_info));
return0;
}
private:
ACE_INET_Addrmulticast_addr_;
ACE_INET_Addrlocal_addr_;
ACE_SOCK_Dgramdgram_;
intmcast_info;
};
intmain(intargc,char*argv[])
{
Sender_Multicastm(2000);
if(m.send_to_multicast_group()==1){
ACE_DEBUG((LM_ERROR,"SendtoMulticastgroupfailed\n"));
exit(1);
}
else
ACE_DEBUG((LM_DEBUG,"SendtoMulticastgroupsuccessful\n"));
}

Inthisexample,theclientusesadatagramwrappertosenddatatothemulticastgroup.
TheSender_Multicastclasscontainsasimplesend_to_multicast_group()method.
Thismethodusesthedatagramwrappercomponentdgram_tosendasinglemessageto
themulticastgroup.Thismessagecontainsnothingbutaninteger.Whenthereceiver
receivesthemessageitwillprintittostandardoutput.

18

Memory Management
Chapter

An introduction to Memory Management in ACE


TheACEframeworkcontainsaveryricharrayofmemorymanagementclasses.These
classesallowyoutomanagebothdynamicmemory(memoryallocatedfromtheheap)
andsharedmemory(memorysharedbetweenprocesses)easilyandefficiently.Youcan
managememoryusingseveraldifferentschemes.You,theprogrammer,decidewhich
schemeismostsuitablefortheapplicationyouaredevelopingandthenusethecorrect
ACEclassthatimplementsthescheme.
ACEcontainstwodifferentsetsofclassesformemorymanagement.
ThefirstsetarethoseclasseswhicharebasedontheACE_Allocatorclass.Theclassesin
this set use dynamic binding and the strategy pattern to provide for flexibility and
extensibility.Classesfromthissetcanonlybeusedtoprovideforlocaldynamicmemory
allocation.
ThesecondsetofclassesisbasedontheACE_Malloctemplateclass.ThissetusesC++
templates and external polymorphism to provide for flexibility in memory allocation
mechanisms.Theclassesinthissetnotonlyincludeclassesforlocaldynamicmemory
management, but also include classes to manage shared memory between processes.
Thesesharedmemoryclassesusetheunderlyingoperatingsystems(OS)sharedmemory
interface.
Why use one set and not the other? The tradeoff here is between performance and
flexibility.TheACE_Allocatorclassesaremoreflexibleastheactualallocatorobjectcan
be changed at runtime. This is done through dynamic binding, which in C++ needs
virtualfunctions.Therefore,thisflexibilitydoesnotcomewithoutacost.Theindirection
causedbyvirtualfunctioncallsmakesthisalternativethemoreexpensiveoption.
The ACE_Malloc classes, on the other hand, perform better. The malloc class is
configured,atcompiletime,withthememoryallocatorthatitwilluse.Suchcompile
timeconfigurabilityiscalled ExternalPolymorphism. AnACE_Malloc basedallocator
cantbe configuredatruntime. Although ACE_Malloc ismoreefficient,itisnotas
flexibleasACE_Allocator.

19

Allocators
Allocators are used in ACE to provide a dynamic memory management mechanism.
Several Allocators are available in ACE which work using different policies. These
differentpoliciesprovidethesamefunctionalitybutwithdifferentcharacteristics.For
example,inrealtimesystemsitmaybenecessaryforanapplicationtopreallocateall
thedynamic memoryit willneedfromtheOS.Itwouldthencontrolallocation and
releaseinternally.Bydoingthistheperformancefortheallocationandreleaseroutinesis
highlypredictable.
AllAllocatorssupporttheACE_Allocatorinterfaceandthereforecanbeeasilyreplaced
withoneanother,eitheratruntimeorcompiletime.Andthatiswheretheflexibility
comesin.ConsequentlyanACE_AllocatorcanbeusedinconjunctionwiththeStrategy
Patterntoprovideveryflexiblememorymanagement.TheTablebelowgivesabrief
descriptionofthedifferentallocatorsthatareavailableinACE.Thedescriptionspecifies
thememoryallocationpolicyusedbyeachallocator.
Allocator

Description

ACE_Allocator

InterfaceclassforthesetofAllocatorclassesinACE.These
classes use inheritance and dynamic binding to provide
flexibility.

ACE_Static_Allocator

ThisAllocatormanagesafixedsizeofmemory.Everytime
arequestisreceivedformorememory,itmovesaninternal
pointer to return the chunk. This allocator assumes that
memory,onceallocated,willneverbefreed.

ACE_Cached_Allocator

ThisAllocatorpreallocatesapoolofmemorythatcontainsa
specificnumberofsizespecifiedchunks.Thesechunksare
maintained on an internal free list and returned when a
memoryrequest(malloc())isreceived.Whenapplications
callfree(),thechunkisreturnedbacktotheinternalfreelist
andnottotheOS.

ACE_New_Allocator

AnallocatorwhichprovidesawrapperovertheC++new
and delete operators, i.e. it internally uses the new and
deleteoperatorstosatisfydynamicmemoryrequests.

Using the Cached Allocator


TheACE_Cached_Allocatorpreallocatesmemoryandthenmanagesthismemoryusing
itsowninternalmechanisms.Thispreallocationoccursintheconstructoroftheclass.
20

Consequently,ifyouusethisallocatoryourmemorymanagementschemeonlyusesthe
OSmemoryallocationinterfaceinthebeginningtodothepreallocation.Afterthat,the
ACE_Cached_Allocatortakescareofallallocationandreleaseofmemory.
Whywouldanyonewanttodothis?Performanceandpredictability.Considerarealtime
system, which must be highly predictable. Using the OS to allocate memory would
involve expensive and nonpredictable calls into the kernel of the OS. The
ACE_Cached_Allocator ontheotherhandinvolvesnosuchcalls.Eachallocationand
releasewouldoccurinafixedamountoftime.

Internal Free
List

ACE
Cached
Allocator

Chunks

The Cached Allocator is illustrated in the diagram above. The memory that is pre
allocated,intheconstructor,ismaintainedinternallyonafreelist. Thislistmaintains
severalchunksofmemoryasitsnodes.Thechunkscanbeanycomplexdatatypes.
Youcanspecifytheactualtypeyouwantthechunktobe.Howyoudothatisillustrated
inlaterexamples.
Allocationandreleaseinthissysteminvolvesafixedamountofpointermanipulationin
thefreelist.Whenauserasksforachunkofmemoryitishandedapointerandthefree
listisadjusted.Whenauserfreesupmemoryitcomesbackintothefreelist.Thisgoes
onforever,unlesstheACE_Cached_Allocatorisdestroyedatwhichpointallmemoryis
returned to the OS. In the case of memory used in a realtime system, internal
fragmentationofchunksisaconcern.
Thefollowingexampleillustrateshowthe ACE_Cached_Allocator canbeusedtopre
allocatememoryandthenhandlerequestsformemory.
Example1
#include"ace/Malloc.h"
//Achunkofsize1Kiscreated.Inourcasewedecidedtouseasimplearray
//asthetypeforthechunk.Insteadofthiswecoulduseanystructorclass
//thatwethinkisappropriate.
typedefcharMEMORY_BLOCK[1024];

//CreateanACE_Cached_Allocatorwhichispassedinthetypeofthe
//chunkthatitmustpreallocateandassignonthefreelist.
//SincetheCached_Allocatorisatemplateclasswecanprettymuch

21

//passitANYtypewethinkisappropriatetobeamemoryblock.
typedefACE_Cached_Allocator<MEMORY_BLOCK,ACE_SYNCH_MUTEX>Allocator;

classMessageManager{
public:
//Theconstructorispassedthenumberofchunksthattheallocator
//shouldpreallocateandmaintainonitsfreelist.
MessageManager(intn_blocks):
allocator_(n_blocks),message_count_(0)
{
mesg_array_=newchar*[n_blocks];
}
//AllocatememoryforamessageusingtheAllocator.Rememberthemessage
//inanarrayandthenincreasethemessage countofvalidmessages
//onthemessagearray.
voidallocate_msg(constchar*msg)
{
mesg_array_[message_count_]=allocator_.malloc(ACE_OS::strlen(msg)+1);
ACE_OS::strcpy(mesg_array_[message_count_],msg);
message_count_++;
}
//Freeallthememorythatwasallocated.Thiswillcausethechunks
//tobereturnedtotheallocatorsinternalfreelist
//andNOTtotheOS.
voidfree_all_msg()
{
for(inti=0;i<message_count_;i++)
allocator_.free(mesg_array_[i]);
message_count_=0;
}
//Justshowallthecurrentlyallocatedmessagesinthemessagearray.
voiddisplay_all_msg()
{
for(inti=0;i<message_count_;i++)
ACE_OS::printf("%s\n",mesg_array_[i]);
}
private:
char**mesg_array_;
Allocatorallocator_;
intmessage_count_;
};

intmain(intargc,char*argv[])
{
if(argc<2){
ACE_DEBUG((LM_DEBUG,"Usage:%s<Numberofblocks>\n",argv[0]));
exit(1);

22

}
intn_blocks=ACE_OS::atoi(argv[1]);

//InstantiatetheMemoryManagerclassandpassinthenumberofblocks
//youwantontheinternalfreelist.
MessageManagermm(n_blocks);
//UsetheMemoryManagerclasstoassignmessagesandfreethem.
//Runthisinyourfavoritedebugenvironmentandyouwillnoticethatthe
//amountofmemoryyourprogramusesafterMemoryManagerhasbeen
//instantiatedremainsthesame.ThatmeanstheCachedAllocator
//controlsormanagesallthememoryfortheapplication.
//Doforever.
while(1){

//allocatethemessagessomewhere
ACE_DEBUG((LM_DEBUG,"\n\n\nAllocatingMessages\n"));
for(inti=0;i<n_blocks;i++){
ACE_OS::sprintf(message,"Message%d:HiThere",i);
mm.allocate_msg(message);
}
//showthemessages
ACE_DEBUG((LM_DEBUG,"Displayingthemessages\n"));
ACE_OS::sleep(2);
mm.display_all_msg();
//freeupthememoryforthemessages.
ACE_DEBUG((LM_DEBUG,"ReleasingMessages\n"));
ACE_OS::sleep(2);
mm.free_all_msg();
}
return0;
}

This simple example contains a message manager class which instantiates a cached
allocator.Thisallocatoristhenusedtoallocate,displayandfreemessagesforever.The
memoryusageoftheapplication,however,doesnotchange.Youcancheckthisoutwith
thedebuggingtoolofyourchoice.

ACE_Malloc
Asmentionedearlier,theMallocsetofclassesusethetemplateclass ACE_Malloc to
provideformemorymanagement. TheACE_Malloctemplatetakes twoarguments,a
memorypoolandalockforthepool,whichgivesusourallocatorclass,asisshownin
thefigurebelow.
23

Memory Pool Class

Lock Class
ACE_Malloc

How ACE_Malloc works


TheideahereisthattheACE_Mallocclasswill"acquire"memoryfromthememorypool
thatwaspassedinandtheapplicationthen"malloc()s"memoryusingtheACE_Malloc
classes interface. The memory returned by the underlying memory pool is returned
internally to the ACE_Malloc class in what are known in ACE as "chunks". The
ACE_Mallocclassusesthesechunksofmemorytoallocatesmaller"blocks"ofmemory
toanapplicationdeveloper.Thisisillustratedinthediagrambelow.

Chunks maintained by
Allocator and divided
into blocks,for giving to
clients.

OS

Client
ACE_Malloc

Chunks for
Allocator

Blocks for Client

Actual
Allocation

Underlying
Memory
Pool

Whenanapplicationrequestsablockofmemory,the ACE_Malloc classwillcheckif


thereisenoughspacetoallocatetheblockfromoneofthechunksithasalreadyacquired
fromthememorypool.Ifitcannotfindachunkwithenoughspaceonit,thenitasksthe
underlying memory pool to return a larger chunk, so it can satisfy the application's
requestforablockofmemory.Whenanapplicationissuesafree()call,ACE_Mallocwill
notreturnthememorythatwasfreedbacktothememorypool,butwillmaintainitonits
freelist.WhenACE_Mallocreceivessubsequentrequestsformemory,itwillusethis
freelisttosearchforemptyblocksthatcanbereturned.Thus,whenthe ACE_Malloc
classisused,theamountofmemoryallocatedfromtheOSwillonlygoupandnotdown,
ifsimplemalloc()andfree()callsaretheonlycallsissued.TheACE_Mallocclassalso

24

includesaremove()method,whichissuesarequesttothememorypoolforittoreturn
thememorytotheOS.ThismethodalsoreturnsthelocktotheOS.

Using ACE_Malloc
Usingthe ACE_Malloc classissimple.First,instantiate ACE_Malloc withamemory
poolandlockingmechanismofyourchoice,tocreateanallocatorclass.Thisallocator
classissubsequentlyusedtoinstantiateanobject,whichistheallocatoryourapplication
willuse.Whenyouinstantiateanallocatorobject,thefirstparametertotheconstructoris
astring,whichisthenameoftheunderlyingmemorypoolyouwanttheallocator
objecttouse.ItisVERYimportantthatthecorrectnameispassedintotheconstructor,
especiallyifyouareusingsharedmemory.Otherwisetheallocatorwillcreateanew
memorypoolforyou.This,ofcourse,isnotwhatyouwantifyouareusingashared
memorypool,sincethenyougetnosharing.
Tofacilitatethesharingoftheunderlyingmemorypool(again,ifyouareusingshared
memorythisisimportant),the ACE_Malloc classalsoincludesamaptypeinterface.
Eachblockofmemorythatisallocatedcanbegivenaname,andcanthuseasilybefound
foundbyanotherprocesslookingthroughthememorypool.Thisincludes bind() and
find()calls.Thebind()callisusedtogivenamestotheblocksthatarereturnedbythe
malloc()calltoACE_Malloc.Thefind()call,asyouprobablyexpect,isthenusedtofind
thememorypreviouslyassociatedwiththename.
Thereareseveraldifferentmemorypoolclassesthatareavailable(shownintablebelow)
tobeusedwheninstantiatingtheACE_Malloctemplateclass.Thesepoolscannotonlybe
usedtoallocatememorythatareusedwithinaprocess,butcanalsobeusedtoallocate
memorypoolsthataresharedbetweenprocesses.Thisalsomakesitclearerwhythe
ACE_Malloc template needs to be instantiated with a locking mechanism. The lock
ensures that when multiple processes access the shared memory pool, it doesnt get
corrupted. Note that even when multiple threads are using the allocator, it will be
necessarytoprovidealockingmechanism.
Thedifferentmemorypoolsavailablearelistedinthetablebelow:
NameofPool

Macro

Description

ACE_MMAP_
Memory_Pool

ACE_MMAP_MEMORY_POOL

Usesthe <mmap(2)> tocreatethe


pool. Thus memory can be shared
between processes. Memory is
updated to the backing store on
everyupdate.

ACE_Lite_MMAP_
Memory_Pool

ACE_LITE_MMAP_MEMORY_POOL

Usesthe <mmap(2)> tocreatethe


pool.Unlikethepreviousmap,this
does not update to the backing
store. The tradeoff is lowered

25

reliability.
ACE_Sbrk_
Memory_Pool

ACE_SBRK_MEMORY_POOL

Uses the <sbrk(2)> call to create


thepool.

ACE_Shared_
Memory_Pool

ACE_SHARED_MEMORY_POOL

Uses the System V <shmget(2)>


call to create the memory pool.
Memory can be shared between
processes.

ACE_Local_
Memory_Pool

ACE__LOCAL_MEMORY_POOL

Createsalocalmemorypoolbased
on the C++ new and delete
operators.Thispoolcan'tbeshared
betweenprocesses.

The following example uses the ACE_Malloc class with a shared memory pool (the
exampleshowsitusingACE_SHARED_MEMORY_POOL, butanymemorypool,from
thetableabove,thatsupportssharedmemorymaybeused).
Itcreatesaserverprocess,whichcreatesamemorypoolandthenallocatesmemoryfrom
thepool.Theserverthencreatesmessagesitwantstheclientprocesstopickupusing
thememoryitallocatedfromthepool.Next,itbindsnamestothesemessagessothatthe
clientcanusethecorrespondingfindoperationtofindthemessagestheserverinserted
intothepool.
Theclientstartsupandcreatesitsownallocator,butusestheSAMEmemorypool.This
isdonebypassingthesamenametotheconstructorfortheallocator,afterwhichituses
thefind()calltofindthemessagesinsertedbytheserverandprintthemoutfortheuser
tosee.
Example2
#include"ace/Shared_Memory_MM.h"
#include"ace/Malloc.h"
#include"ace/Malloc_T.h"
#defineDATA_SIZE100
#defineMESSAGE1"Hiyaoverthereclientprocess"
#defineMESSAGE2"Didyouhearmethefirsttime?"
LPCTSTRpoolname="My_Pool";
typedefACE_Malloc<ACE_SHARED_MEMORY_POOL,ACE_Null_Mutex>Malloc_Allocator;
staticvoid
server(void){
//Createthememoryallocatorpassingitthesharedmemory
//poolthatyouwanttouse
Malloc_Allocatorshm_allocator(poolname);
//Createamessage,allocatememoryforitandbinditwith
//anamesothattheclientcanthefinditinthememory
//pool
char*Message1=(char*)shm_allocator.malloc(strlen(MESSAGE1));

26

ACE_OS::strcpy(Message1,MESSAGE1);
shm_allocator.bind("FirstMessage",Message1);
ACE_DEBUG((LM_DEBUG,"<<%s\n",Message1));
//Howaboutasecondmessage
char*Message2=(char*)shm_allocator.malloc(strlen(MESSAGE2));
ACE_OS::strcpy(Message2,MESSAGE2);
shm_allocator.bind("SecondMessage",Message2);
ACE_DEBUG((LM_DEBUG,"<<%s\n",Message2));
//SettheServertogotosleepforawhilesothattheclienthas
//achancetodoitsstuff
ACE_DEBUG((LM_DEBUG,
"Serverdonewriting..goingtosleepzzz..\n\n\n"));
ACE_OS::sleep(2);
//Getridofallresourcesallocatedbytheserver.Inother
//wordsgetridofthesharedmemorypoolthathadbeen
//previouslyallocated
shm_allocator.remove();
}

staticvoid
client(void){
//Createamemoryallocator.Besurethattheclientpasses
//inthe"right"nameheresothatboththeclientandthe
//serverusethesamememorypool.Wewouldntwantthemto
//BOTHcreatedifferentunderlyingpools.
Malloc_Allocatorshm_allocator(poolname);
//Getthatfirstmessage.Noticethatthefindislookingupthe
//memorybasedonthe"name"thatwasboundtoitbytheserver.
void*Message1;
if(shm_allocator.find("FirstMessage",Message1)==1){
ACE_ERROR((LM_ERROR,
"Client:Problemcantfinddatathatserverhassent\n"));
ACE_OS::exit(1);
}
ACE_OS::printf(">>%s\n",(char*)Message1);
ACE_OS::fflush(stdout);
//Letsgetthatsecondmessagenow.
void*Message2;
if(shm_allocator.find("SecondMessage",Message2)==1){
ACE_ERROR((LM_ERROR,
"Client:Problemcantfinddatathatserverhassent\n"));
ACE_OS::exit(1);
}
ACE_OS::printf(">>%s\n",(char*)Message2);
ACE_OS::fflush(stdout);
ACE_DEBUG((LM_DEBUG,"Clientdonereading!BYENOW\n"));

27

ACE_OS::fflush(stdout);
}

intmain(int,char*[]){
switch(ACE_OS::fork())
{
case1:
ACE_ERROR_RETURN((LM_ERROR,"%p\n","fork"),1);
case0:
//Makesuretheserverstartsupfirst.
ACE_OS::sleep(1);
client();
break;
default:
server();
break;
}
return0;
}

Using the Malloc classes with the Allocator interface


MostcontainerclassesinACEallowforanAllocatorobjecttobepassedinformanaging
memory used in the container. Since certain memory allocation schemes are only
availablewiththe ACE_Malloc setofclasses,ACEincludesanadaptertemplateclass
ACE_Allocator_Adapter, which adapts the ACE_Malloc class to the ACE_Allocator
interface.Whatthismeansisthatthenewclasscreatedafterinstantiatingthetemplate
canbeusedinplaceofanyACE_Allocator.Forexample:
typedefACE_Allocator_Adapter<ACE_Malloc<ACE_SHARED_MEMORY_POOL,ACE_Null_Mutex>>
Allocator;

ThusthisnewlycreatedAllocator classcanbeusedwherevertheAllocatorinterfaceis
required, but it will use the underlying functionality of ACE_Malloc with a
ACE_Shared_Memory_Pool.ThustheadapteradaptstheMallocclasstotheAllocator
class.
Thisallowsonetousethefunctionalityassociatedwiththe ACE_Malloc setofclasses
with the dynamic binding flexibility available with ACE_Allocator. It is however,
important to remember that this flexibility comes at the price of sacrificing some
performance.

28

Chapter

4
Thread Management
Synchronization and thread management mechanisms in ACE
ACEcontainsmanydifferentclassesforcreatingandmanagingmultithreadedprograms.
Inthischapter,wewillgooverafewofthemechanismsthatareavailableinACEto
provideforthreadmanagement.Inthebeginning,wewillgooverthesimplethread
wrapper classes, which contain minimal management functionality. As the chapter
progresses, however, we will go over the more powerful management mechanisms
available in ACE_Thread_Manager. ACE also contains a very comprehensive set of
classesthatdealwithsynchronizationofthreads.Theseclasseswillalsobecoveredhere.

Creating and canceling threads


There are several different interfaces that are available for thread management on
differentplatforms.TheseincludethePOSIXpthreadsinterface,Solaristhreads,Win32
threadsetc.Eachoftheseinterfacesprovidesthesameorsimilarfunctionalitybutwith
APIs that are vastly different. This leads to difficult, tedious and errorprone
programming,sincetheapplicationprogrammermustmakehimselffamiliarwithseveral
interfacestowriteondifferentplatforms.Furthermore,suchprograms,oncewritten,are
nonportableandinflexible.
ACE_ThreadprovidesasimplewrapperaroundtheOSthreadcallsthatdealwithissues
such as creation, suspension, cancellation and deletion of threads. This gives the
application programmer a simple and easytouse interface which is portable across
differentthreadingAPIs. ACE_Thread isaverythinwrapper,withminimaloverhead.
MostmethodsareinlinedandthusareequivalenttoadirectcalltotheunderlyingOS
specificthreads interface. Allmethodsin ACE_Thread arestaticandtheclassisnot
meanttobeinstantiated.
Thefollowingexampleillustrateshowthe ACE_Thread wrapperclasscanbeusedto
create,yieldandjoinwiththreads.

29

Example1
#include"ace/Thread.h"
#include"ace/Synch.h"
staticintnumber=0;
staticintseed=0;
staticvoid*
worker(void*arg)
{
ACE_UNUSED_ARG(arg);
ACE_DEBUG((LM_DEBUG,"Thread(%t)Createdtodosomework"));
::number++;
ACE_DEBUG((LM_DEBUG,"andnumberis%d\n",::number));
//LettheotherguygowhileIfallasleepforarandomperiod
//oftime
ACE_OS::sleep(ACE_OS::rand()%2);
//Exitingnow
ACE_DEBUG((LM_DEBUG,
"\t\tThread(%t)Done!\tThenumberisnow:%d\n",number));
return0;
}
intmain(intargc,char*argv[])
{
if(argc<2)
{
ACE_DEBUG((LM_DEBUG,"Usage:%s<numberofthreads>\n",argv[0]));
ACE_OS::exit(1);
}
ACE_OS::srand(::seed);
//setuptherandomnumbergenerator
intn_threads=ACE_OS::atoi(argv[1]);
//numberofthreadstospawn
ACE_thread_t*threadID =newACE_thread_t[n_threads+1];
ACE_hthread_t*threadHandles=newACE_hthread_t[n_threads+1];
if(ACE_Thread::spawn_n(threadID,//id'sforeachofthethreads
n_threads,//numberofthreadstospawn
(ACE_THR_FUNC)worker,//entrypointfornewthread
0,
//argstoworker
THR_JOINABLE|THR_NEW_LWP,//flags

30

ACE_DEFAULT_THREAD_PRIORITY,
0,0,threadHandles)==1)
ACE_DEBUG((LM_DEBUG,"Errorinspawningthread\n"));
//spawnn_threads

for(inti=0;i<n_threads;i++)
ACE_Thread::join(threadHandles[i]);
//Waitforallthethreadstoexitbeforeyouletthemainfallthrough
//andhavetheprocessexit.
return0;
}

Inthissimpleexample n_thread numberofworkerthreadsarecreated.Eachofthese


threadsexecutestheworker()functiondefinedintheprogram.Thethreadsarecreatedby
using the ACE_Thread::spawn_n() call. This call is passed a pointer to the function
whichistobecalledasthestartingpointofexecutionforthethread.(Inthiscasethe
worker()function).OneimportantpointtonoteisthatACE_Thread::spawn_n()requires
allstartingfunctions(methods)forthreadstobestaticorglobal(asisrequiredwhen
usingtheOSthreadsAPIdirectly).
Oncetheworkerfunctionstarts,itincrementstheglobal number variable,reportsits
presentvalueandthenfallsasleeptoyieldtheprocessortoanotherthread.Thesleep()is
forarandomperiodoftime.Afterthethreadwakesupit,informstheuserofthepresent
valueofnumberandfallsoutoftheworker()function.
Onceathreadreturnsfromitsstartingfunctionitimplicitlyissuesathreadexit()callon
the thread library and exits. The worker thread thus exits once it falls out of the
worker() function. The main thread, which was responsible for creating the worker
thread,iswaitingforallotherthreadstocompletetheirexecutionandexitbeforeit
exits.Whenthemainthreaddoesexit(byfallingoutofthemain()function),theentire
processwillbedestroyed.Thishappensbecauseanimplicitcalltotheexit(3c)functionis
madewheneverathreadfallsoutofmain.Therefore,ifthemainthreadisnotforcedto
waitfortheotherthreadsthenwhenitdies,theprocesswillautomaticallybedestroyed,
destroyingalltheworkerthreadsalongwithit,beforetheycompletetheirjob!
ThiswaitisperformedusingtheACE_Thread::join()call.Thismethodtakesinahandle
(ACE_hthread_t)tothethatthatyouwishthemainthreadtojoiwith.
There are several facts worth noting in this example. First, there is no management
functionalityavailablethatwecancalluponthatinternallyrememberstheidsofthe
threads that the application has spawned. This makes it difficult to join(), kill() or
generallymanagethethreadswespawned.The ACE_Thread_Manager whichiscovered
laterinthischapteralleviatestheseproblemsandingeneralshouldbeusedinsteadofthe
threadswrapperAPI.

31

Second,nosynchronizationprimitiveswereusedintheprogramtoprotecttheglobal
data. In this case, they were not necessary, since all threads were only performing
additionoperationsontheglobal.Inreallife,however,lockswouldberequiredto
protectallsharedmutabledata(globalorstaticvariables),suchastheglobal number
variable.

Synchronization primitives in ACE


ACEhasseveralclassesthatcanbeusedforsynchronizationpurposes.Theseclassescan
bedividedintothefollowingcategories

The ACE Locks Class Category

The ACE Guards Class Category

The ACE Conditions Class Category

Miscellaneous ACE Synchronization classes

The ACE Locks Category


The locks category includes classes that wrap simple locking mechanisms, such as
mutexes,semaphores,read/writemutexesandtokens.Theclassesthatareavailableunder
this category are shown in the table below. Each class name is followed by a brief
descriptionofhowandwhatitcanbeusedfor:
Name

Description

ACE_Mutex

Wrapper class around the mutual exclusion mechanism


(which, depending on the platform, may be mutex_t,
pthread_mutex_t, etc.)andareusedtoprovideasimple
and efficient mechanism to serialize access to a shared
resource. Similar in functionality to a binary sempahore.
Canbeusedformutualexclusionamongboththreadsand
processes.

ACE_Thread_Mutex

Can be used in place of ACE_Mutex and is specific for


synchronizationofthreads.

ACE_Process_Mutex

Can be used in place of ACE_Mutex and is specific for


synchronizationofprocesses.

ACE_Null_Mutex

Provides a donothing implementation of the ACE_Mutex


interface, and can be replaced with it when no
synchronizationisrequired.

ACE_RW_Mutex

Wrapper classes which encapsulate readers/writers locks.


Thesearelocksthatareacquireddifferentlyforreadingand
32

writing,thusenablingmultiplereaderstoreadwhilenoone
iswriting.
ACE_RW_Thread_Mutex

Canbeusedinplaceof ACE_RW_Mutex andisspecificfor


synchronizationofthreads.

ACE_RW_Process_Mutex

Canbeusedinplaceof ACE_RW_Mutex andisspecificfor


synchronizationofprocesses.

ACE_Semaphore

These classes implement a counting semaphore, which is


usefulwhenafixednumberofthreadscanaccessaresource
atthesametime.InthecasewheretheOSdoesnotprovide
thiskindofsynchronizationmechanism,itissimulatedwith
mutexes.

ACE_Thread_Semaphore

ShouldbeusedinplaceofACE_Semaphoreandisspecific
forsynchronizationofthreads.

ACE_Process_Semaphore

ShouldbeusedinplaceofACE_Semaphoreandisspecific
forsynchronizationofprocesses.

ACE_Token

Thisprovidesforarecursivemutex,i.e.thethreadthat
currently holds the token can reacquire it multiple times
withoutblocking.Also,whenthetokenisreleased,itmakes
surethatthenextthreadwhichwasblockedandwaitingfor
thetokenisthenextonetogo.

ACE_Null_Token

A donothing implementation of the token interface used


whenyouknowthatmultiplethreadswon'tbepresent.

ACE_Lock

An interface class which defines the locking interface. A


purevirtualclass,which,ifused,willentailvirtualfunction
calloverhead.

ACE_Lock_Adapter

A templatebased adapter which allows any of the


previouslymentionedlockingmechanisms toadapttothe
ACE_Lockinterface.

Theclassesdescribedinthetableaboveallsupportthesameinterface.However,these
classesareNOTrelatedtoeachotherinanyinheritancehierarchy.InACE,locksare
usuallyparameterizedusingtemplatessincetheoverheadofhavingvirtualfunctioncalls
is,inmostcases,unacceptable.Theusageoftemplatesallowstheprogrammeracertain
degreeofflexibility.Hecanchoosethetypeoflockingmechanismhewishestouseat
compiletime,butnotatruntime.Nevertheless,insomeplacestheprogrammermayneed
tousedynamicbindingandsubstitution,andforthesecases,ACEincludesthe ACE_Lock
andACE_Lock_Adapterclasses.

33

Using the Mutex classes


Amuteximplementsasimpleformofsynchronizationcalledmutualexclusion(hence
thenamemutex).Mutexsprohibitmultiplethreadsfromenteringaprotectedorcritical
sectionofcode.Consequently,atanymomentoftime,onlyonethreadisallowedinto
suchaprotectedsectionofcode.
Beforeanythreadcanenteradefinedcriticalsection,itmustacquireownershipofthe
mutexassociated withthatsection.Ifanotherthreadalreadyownsthemutexforthe
criticalsectionthenotherthreadscantenter.Theseotherthreadswillbeforcedtowait
untilthecurrentownerreleasesthemutex.
Whendoyouusemutexes?Mutexesareusedtoprotectsharedmutablecode,i.e.data
thatiseitherglobalorstatic.Suchdatamustbeprotectedbymutexes topreventits
corruptionwhenmultiplethreadsaccessitatthesametime.
Thefollowingexampleillustratesusageofthe ACE_Thread_Mutex class.Noticethat
ACE_MutexcouldeasilyreplacetheuseofACE_Thread_Mutexclassheresincetheyboth
havethesameinterface.

Example2
#include"ace/Synch.h"
#include"ace/Thread.h"
//Argumentsthataretobepassedtotheworkerthreadarepassed
//throughthisstruct.
structArgs{
public:
Args(intiterations):
mutex_(),iterations_(iterations){}
ACE_Thread_Mutexmutex_;
intiterations_;
};
//Thestartingpointfortheworkerthreads
staticvoid*
worker(void*arguments){
Args*arg=(Args*)arguments;
for(inti=0;i<arg>iterations_;i++)
{
ACE_DEBUG((LM_DEBUG,
"(%t)Tryingtogetaholdofthisiteration\n"));
//Thisisourcriticalsection
arg>mutex_.acquire();

ACE_DEBUG((LM_DEBUG,"(%t)Thisisiterationnumber%d\n",i));

ACE_OS::sleep(2);
//simulatecriticalwork
arg>mutex_.release();

34

}
return0;
}
intmain(intargc,char*argv[])
{
if(argc<2){
ACE_OS::printf("Usage:%s<number_of_threads>
<number_of_iterations>\n",argv[0]);
ACE_OS::exit(1);
}
Argsarg(ACE_OS::atoi(argv[2]));
//Setupthearguments
intn_threads=ACE_OS::atoi(argv[1]);
//determinethenumberofthreadstobespawned.
ACE_thread_t*threadID =newACE_thread_t[n_threads+1];
ACE_hthread_t*threadHandles=newACE_hthread_t[n_threads+1];
if(ACE_Thread::spawn_n(threadID,//id'sforeachofthethreads
n_threads,//numberofthreadstospawn
(ACE_THR_FUNC)worker,//entrypointfornewthread
&arg,//argstoworker
THR_JOINABLE|THR_NEW_LWP,//flags
ACE_DEFAULT_THREAD_PRIORITY,
0,0,threadHandles)==1)
ACE_DEBUG((LM_DEBUG,"Errorinspawningthread\n"));
//spawnn_threads
for(inti=0;i<n_threads;i++)
ACE_Thread::join(threadHandles[i]);
//Waitforallthethreadstoexitbeforeyouletthemainfallthrough
//andhavetheprocessexit.
return0;
}

Inthe aboveexample, the ACE_Thread wrapper class is usedtospawnoffmultiple


threads toperformthe worker() function,asinthepreviousexample.Eachthreadis
passedinanArgobjectwhichcontainsthenumberofiterationsitissupposedtoperform
andthemutexthatitisgoingtouse.
Inthisexample,onstartup,eachthreadimmediatelyentersa for loop.Onceinsidethe
loop,thethreadentersacriticalsection.Theworkdonewithinthiscriticalsectionis
protectedbyusinganACE_Thread_Mutexmutexobject.Thisobjectwaspassedinasan
argumenttotheworkerthreadfromthemainthread.Controlofthecriticalsectionis
achievedbyacquiringownershipofthemutex.Thisisdonebyissuinganacquire()call
ontheACE_Thread_Mutexobject.Oncethemutexisacquired,nootherthreadcanenter
thissectionofcode.Controlofthecriticalsectionisreleasedbyusingtherelease()call.
Onceownershipofthemutexisrelinquished,anyotherwaitingthreadsareawakened.
35

These threads then compete to obtain ownership of the mutex. Whichever thread
managestoacquireownershipfirstentersthecriticalsection.
Using the Lock and Lock Adapter for dynamic binding
Asmentionedearlier,themutexvarietyoflocksismeanttobeusedeitherdirectlyin
yourcode,or,ifflexibilityisdesired,asatemplateparameter.However,ifyouneedto
changethetypeoflockthatisusedwithyourcodedynamically,i.e.atruntime,these
lockscannotbeused.
Tocounterthisproblem,ACEincludestheACE_LockandACE_Lock_Adapterclasses,
whichallowforsuchruntimesubstitution.
The following example illustrates how the ACE_Lock class and ACE_Lock_Adapter
provide the application programmer with the facility to use dynamic binding and
substitutionwiththelockingmechanisms.
Example3
#include"ace/Synch.h"
#include"ace/Thread.h"
//Argumentsthataretobepassedtotheworkerthreadarepassed
//throughthisclass.
structArgs
{
public:
Args(ACE_Lock*lock,intiterations):
mutex_(lock),iterations_(iterations){}
ACE_Lock*mutex_;
intiterations_;
};
//Thestartingpointfortheworkerthreads
staticvoid*
worker(void*arguments){
Args*arg=(Args*)arguments;
for(inti=0;i<arg>iterations_;i++){
ACE_DEBUG((LM_DEBUG,
"(%t)Tryingtogetaholdofthisiteration\n"));
//Thisisourcriticalsection
arg>mutex_>acquire();
ACE_DEBUG((LM_DEBUG,"(%t)Thisisiterationnumber%d\n",i));
ACE_OS::sleep(2);
//simulatecriticalwork
arg>mutex_>release();
}
return0;
}
intmain(intargc,char*argv[])

36

{
if(argc<4){
ACE_OS::printf("Usage:%s<number_of_threads>
<number_of_iterations><lock_type>\n",argv[0]);
ACE_OS::exit(1);
}
//Polymorphiclockthatwillbeusedbytheapplication
ACE_Lock*lock;
//Decidewhichlockyouwanttouseatruntime,
//recursiveornonrecursive.
if(ACE_OS::strcmp(argv[3],"Recursive"))
lock=newACE_Lock_Adapter<ACE_Recursive_Thread_Mutex>;
else
lock=newACE_Lock_Adapter<ACE_Thread_Mutex>
//Setupthearguments
Argsarg(lock,ACE_OS::atoi(argv[2]));
//spawnthreadsandwaitasinpreviousexamples..
}

Inthisexample,theonlydifferencefromthepreviousexampleisthatthe ACE_Lockclass
isusedwithACE_Lock_Adaptertoprovidedynamicbinding.Thedecisionastowhether
the underlying locking mechanism will be use recursive or nonrecursive mutexes is
madefromcommandlineargumentswhiletheprogramisrunning.Theadvantageof
usingdynamicbinding,again,isthattheactuallockingmechanismcanbesubstitutedat
runtime.Thedisadvantageisthateachcalltothelocknowentailsanextralevelof
indirectionthroughthevirtualfunctiontable.
Using Tokens
AsmentionedinthetabletheACE_Tokenclassprovidesforanamedrecursivemutex,
whichcanbereacquiredmultipletimesbythesamethreadthathadinitiallyacquiredit.
TheACE_Token classalsoensuresstrictFIFOorderingofallthreadsthattrytoacquire
it.
Recursivelocksallowthesamethreadtoacquirethesamelockmultipletimes.Thatisa
lockcannotdeadlocktryingtoacquirealockitalreadyhas.Thesetypesoflockscomein
handyinvariousdifferentsituations.Forexample,ifyouusealockformaintaingthe
consistencyofatracestreamyoumaywantthislocktoberecursive.Thiscomesin
handyasonemethodmaycallatraceroutine,acquirethelock,beinterruptedbyasignal
andagaintrytoacquirethetracelock.Ifthelockwasnonrecursivethethreadwould
deadlockonitselfhere.Youwillfindmanyotherinterestingapplicationsforrecursive
locks.Oneimportantpointtorememberisthat youmustrelease therecursivelockas
manytimesasyouacquireit.

37

WhenIranthepreviousexample(example3)onSunOS5.xIfoundthatthethreadthat
releasedthelockwastheonethatmanagedtoreacquireittoo!(inaround90%ofthe
cases.) However if you run the example with the ACE_Token class as the locking
mechanismeachthreadhasitsturnandthengivesuptothenextthreadinline.
AlthoughACE_Tokens are very useful as named recursive locks they are really part of
a larger framework for token management. This framework allows you to maintain the
consistency of data within a data store. Unfortunatley this is beyond the scope of this
tutorial.
Example4
#include"ace/Token.h"
#include"ace/Thread.h"
//Argumentsthataretobepassedtotheworkerthreadarepassed
//throughthisstruct.
structArgs
{
public:
Args(intiterations):
token_(myToken),iterations_(iterations){}
ACE_Tokentoken_;
intiterations_;
};
//Thestartingpointfortheworkerthreads
staticvoid*
worker(void*arguments){
Args*arg=(Args*)arguments;
for(inti=0;i<arg>iterations_;i++){
ACE_DEBUG((LM_DEBUG,"(%t)Tryingtogetaholdofthisiteration\n"));
//Thisisourcriticalsection
arg>token_.acquire();
ACE_DEBUG((LM_DEBUG,"(%t)Thisisiterationnumber%d\n",i));
//work
ACE_OS::sleep(2);
arg>token_.release();
}
return0;
}
intmain(intargc,char*argv[])
{
//sameaspreviousexamples..
}

38

The ACE Guards Category


TheGuardsinACEareusedtoautomaticallyacquireandreleaselocks.Anobjectofthe
Guardclassdefinesablockofcodeoverwhichalockisacquired.Thelockisreleased
automaticallywhentheblockisexited.
The Guard classes in ACE are templates which are parameterized with the type of
lockingmechanismrequired.Theunderlyinglockcouldbeanyoftheclassesthatwere
describedintheACELocksCategory,i.e.anyofthemutexorlockclasses.Thisworks
byusingtheconstructoroftheobjecttoacquirethelockandthedestructortoreleasethe
lock.ThefollowingGuardsareavailableinACE:
Name

Description

ACE_Guard

Automaticallycalls acquire() and release() ontheunderlying


lock.CanbepassedanyofthelocksintheACElocksclass
categoryasitstemplateparameter.

ACE_Read_Guard

Automatically calls acquire_read() and release() on the


underlyinglock.

ACE_Write_Guard

Automatically calls acquire_write() and release() on the


underlyinglock.

Thefollowingexampleillustrateshowguardscanbeused:
Example5
#include"ace/Synch.h"
#include"ace/Thread.h"

//Argumentsthataretobepassedtotheworkerthreadarepassed
//throughthisclass.
classArgs{
public:
Args(intiterations):
mutex_(),iterations_(iterations){}
ACE_Thread_Mutexmutex_;
intiterations_;
};
//Thestartingpointfortheworkerthreads
staticvoid*
worker(void*arguments){
Args*arg=(Args*)arguments;
for(inti=0;i<arg>iterations_;i++){
ACE_DEBUG((LM_DEBUG,"(%t)Tryingtogetaholdofthisiteration\n"));
ACE_Guard<ACE_Thread_Mutex>guard(arg>mutex_);
{
//Thisisourcriticalsection
ACE_DEBUG((LM_DEBUG,"(%t)Thisisiterationnumber%d\n",i));

39

//work
ACE_OS::sleep(2);
}//endcriticalsection
}
return0;
}

intmain(intargc,char*argv[])
{
//sameaspreviousexample
}

Intheaboveexample,aguardmanagesthecriticalsectionintheworkerthread.The
GuardobjectiscreatedfromtheACE_Guardtemplateclass.Thetemplateispassedthe
typeoflocktheguardshoulduse.Theguardobjectiscreatedbypassingtheactuallock
objectitneedstoacquirethroughitsconstructor.Thelockisinternallyandautomatically
acquiredbyACE_Guardandthesectionintheforloopisthusaprotectedcriticalsection.
Onceitcomesoutofscope,theguardobjectisautomaticallydestroyed,whichcausesthe
locktobereleased.
Guardsareusefulastheyensurethatonceyouacquirealockyouwillalwaysreleaseit
(unlessofcourseyourthreaddiesawayduetounforeseencircumstances).Thisproves
extremelyusefulincomplicatedmethodsthatfollowmanydifferentreturnpaths.

The ACE Conditions Category


TheACE_Conditionclassisawrapperclassaroundtheconditionvariableprimitiveof
theOS.Sowhatareconditionvariablesanyway?
Oftentimesathreadneedsacertainconditiontobesatisfiedbeforeitcancontinuewith
its operation. For example, consider a thread which needs to insert messages onto a
globalmessagequeue.Beforeinsertinganymessages,itmustchecktoseeifthereis
spaceavailableonthemessagequeue.Ifthemessagequeueisinthefullstate,thenit
cannotdoanything,andmustgotosleepandtryagainatsomelatertime.Thatis,before
accessing the global resource, a condition must be true. Later, when another thread
emptiesthemessagequeue,thereshouldbeawaytoinformorsignaltheoriginalthread
thatthereisnowroomonthemessagequeueandthatitshouldnowtrytoinsertthe
messageagain.Thiscanbedoneusingconditionvariables.Conditionvariablesareused
notasmutualexclusionsprimitives,butasindicatorsthatacertainconditionhasbeen
reached,andthusathreadwhichwasblockedbecausetheconditionwasnottrueshould
trytocontinue.
Yourprogramshouldgothroughthefollowingstepswhenusingconditionvariables:
40

Acquire the lock (mutex) to the global resource (e.g. the message queue).

Check the condition. (e.g. Does the message queue have space on it?).

If condition fails then call the condition variables wait() method. Wait for
some future point in time when the condition may be true.
When another thread performs some action on the global resource, it signal()s
all the other threads that have tried some condition on the resource. (For example,
another thread dequeues a message from the message queue and then signal()s on
the condition variable, so that the threads that are blocked on the wait() can try to
insert their message into the queue.)
After waking up, re-check to see if the condition is now true. If it is, then
perform some action on the global resource. (For example, enqueue a message on
the global message queue.) At this point the thread has the mutex and must release
it so that other threads can continue.

Oneimportant thingyoushouldnotice isthat theconditionvariable mechanism, i.e.


ACE_Cond,takescareofreleasingthemutexontheglobalresourcebeforeblocking
internallyinthewaitcall.Ifitdidnotdothis,thennootherthreadcouldworkonthe
resource(whichisthecauseofthechangeinthecondition).Also,oncetheblocked
threadissignaledtowakeupagain,itinternallyreacquiresthelockbeforecheckingthe
condition.
Theexamplebelowisarehashofthefirstexampleinthischapter.Ifyouremember,we
hadsaidthatusingtheACE_Thread::join()calltomakethemainthreadwaitforallother
threads to terminate. Another way to achieve the same solution, using condition
variables,wouldbeforthemainthreadtowaitfortheconditionallthreadsaredoneto
betruebeforeexiting.Thelastthreadcouldsignalthewaitingmainthreadthrougha
conditionvariablethatallthreadsaredoneanditisthelast.Themainthreadwouldthen
goaheadandexittheapplicationanddestroytheprocess.Thisisillustratedbelow:
Example6
#include"ace/Thread.h"
#include"ace/Synch.h"
staticintnumber=0;
staticintseed=0;
classArgs{
public:
Args(ACE_Condition<ACE_Thread_Mutex>*cond,intthreads):
cond_(cond),threads_(threads){}
ACE_Condition<ACE_Thread_Mutex>*cond_;
intthreads_;
};
staticvoid*worker(void*arguments){
Args*arg=(Args*)arguments;
ACE_DEBUG((LM_DEBUG,"Thread(%t)Createdtodosomework\n"));

41

::number++;
//Work
ACE_OS::sleep(ACE_OS::rand()%2);
//Exitingnow
ACE_DEBUG((LM_DEBUG,
"\tThread(%t)Done!\n\tThenumberisnow:%d\n",number));
//Ifallthreadsaredonesignalmainthreadthat
//programcannowexit
if(number==arg>threads_){
ACE_DEBUG((LM_DEBUG,
"(%t)LastThread!\nAllthreadshavedonetheirjob!
Signalmainthread\n"));
arg>cond_>signal();
}
return0;
}

intmain(intargc,char*argv[]){
if(argc<2){
ACE_DEBUG((LM_DEBUG,
"Usage:%s<numberofthreads>\n",argv[0]));
ACE_OS::exit(1);
}
intn_threads=ACE_OS::atoi(argv[1]);
//Setuptherandomnumbergenerator
ACE_OS::srand(::seed);
//Setupargumentsforthreads
ACE_Thread_Mutexmutex;
ACE_Condition<ACE_Thread_Mutex>cond(mutex);
Argsarg(&cond,n_threads);
//Spawnoffn_threadsnumberofthreads
for(inti=0;i<n_threads;i++){
if(ACE_Thread::spawn((ACE_THR_FUNC)worker,(void*)&arg,
THR_DETACHED|THR_NEW_LWP)==1)
ACE_DEBUG((LM_DEBUG,"Errorinspawningthread\n"));
}
//Waitforsignalindicatingthatallthreadsaredoneandprogram
//canexit.Theglobalresourcehereisnumberandthecondition
//thattheconditionvariableiswaitingforisnumber==n_threads.
mutex.acquire();
while(number!=n_threads)
cond.wait();
mutex.release();
ACE_DEBUG((LM_DEBUG,"(%t)MainThreadgotsignal.Program
exiting..\n"));
ACE_OS::exit(0);
}

42

Noticethatbeforeevaluatingthecondition,amutexisacquiredbythemainthread.The
conditionisthenevaluated.Iftheconditionisnottrue,thenthemainthreadcallsawait
ontheconditionvariable.Theconditionvariableinturnreleasesthemutexautomatically
andcausesthemainthreadtofallasleep,whenthewait()returnsthethreadwillonce
againhaveacquiredthemutexanditmustthereforebyexplicitlyreleasedintheprogram.
Condition variables arealways usedinconjunction withamutexlike this.Thisis a
generalpattern[I]thatcanbedescribedas
while(expressionNOTTRUE)waitonconditionvariable;
Rememberthatconditionvariablesarenotusedformutualexclusion,butareusedforthe
signalingfunctionalitywehavebeendescribing.
BesidestheACE_Conditionclass,ACEalsoincludesanACE_Condition_Thread_Mutex
class,whichusesan ACE_Thread_Mutex astheunderlyinglockingmechanismforthe
globalresource..

Miscellaneous Synchronization Classes


Besides the synchronization classes described above ACE also includes other
synchronizationclassessuchasACE_BarrierandACE_Atomic_Op.
Barriers in ACE
Barriershaveagoodname.Thenameprettymuchdescribeswhatbarriersaresupposed
todo.Agroupofthreadscanuseabarriertocollectivelysynchronizewitheachother.
Eachthreadinthegroupexecutesuntilitarrivesatthebarrier,afterwhichitblocks.
Afteralltheinvolvedthreadshavereachedtheirrespectivebarriers,theyallcontinue
withtheirexecution.Thatis,theyallblockonebyonewaitingfortheotherstoreachthe
barrier.Onceallthreadsreachthebarrierpointintheirexecutionpaths,theyallrestart
together.
InACE,thebarrierisimplementedintheACE_Barrierclass.Whenthebarrierobjectis
instantiated,itispassedthenumberofthreadsitisgoingtobewaitingon.Eachthread
issues a wait() call onthe barrier object once theyreach thebarrier pointin their
executionpath.Theyblockatthispointandwaitfortheotherthreadstoreachtheir
respective barrier points before they all continue together. When the barrier has
receivedtheappropriatenumberofwait()callsfromtheinvolvedthreads,itwakesupall
theblockedthreadstogether.
ThefollowingexampleillustrateshowbarrierscanbeusedwithACE
Example7
#include"ace/Thread.h"
#include"ace/Synch.h"
staticintnumber=0;

43

staticintseed=0;
classArgs{
public:
Args(ACE_Barrier*barrier):
barrier_(barrier){}
ACE_Barrier*barrier_;
};
staticvoid*
worker(void*arguments){
Args*arg=(Args*)arguments;
ACE_DEBUG((LM_DEBUG,"Thread(%t)Createdtodosomework\n"));
::number++;
//Work
ACE_OS::sleep(ACE_OS::rand()%2);
//Exitingnow
ACE_DEBUG((LM_DEBUG,
"\tThread(%t)Done!\n\tThenumberisnow:%d\n",number));
//Letthebarrierknowwearedone.
arg>barrier_>wait();
ACE_DEBUG((LM_DEBUG,"Thread(%t)isexiting\n"));
return0;
}

intmain(intargc,char*argv[]){
if(argc<2){
ACE_DEBUG((LM_DEBUG,"Usage:%s<numberofthreads>\n",argv[0]));
ACE_OS::exit(1);
}
intn_threads=ACE_OS::atoi(argv[1]);
ACE_DEBUG((LM_DEBUG,"Preparingtospawn%dthreads",n_threads));
//Setuptherandomnumbergenerator
ACE_OS::srand(::seed);
//Setupargumentsforthreads
ACE_Barrierbarrier(n_threads);
Argsarg(&barrier);
//Spawnoffn_threadsnumberofthreads
for(inti=0;i<n_threads;i++){
if(ACE_Thread::spawn((ACE_THR_FUNC)worker,
(void*)&arg,THR_DETACHED|THR_NEW_LWP)==1)
ACE_DEBUG((LM_DEBUG,"Errorinspawningthread\n"));
}
//Waitforalltheotherthreadstoletthemainthread
//knowthattheyaredoneusingthebarrier
barrier.wait();
ACE_DEBUG((LM_DEBUG,"(%t)Otherthreadsarefinished.Programexiting..\n"));

44

ACE_OS::sleep(2);
}

Inthisexample,abarrieriscreatedandthenpassedtotheworkerthreads.Eachworker
threadcalls wait() onthebarrierjustbeforeexiting,causingallthreadstobeblocked
aftertheyhavecompletedtheirworkandrightbeforetheyexit.Themainthreadalso
blocksjustbeforeexiting.Onceallthreads(includingmain)havereachedtheendoftheir
execution,theyallcontinueandthenexittogether.
Atomic Op
The ACE_Atomic_Op classisusedtotransparentlyparameterizesynchronizationinto
basic arithmetic operations. ACE_Atomic_Op is a template class that is passed in a
locking mechanism and the type which is be parameterized. ACE achieves this by
overloading all arithmetic operators and ensuring that a lock is acquired before the
operationandthenreleasedaftertheoperation.Theoperationitselfisdelegatedtothe
classpassedinthroughthetemplate.
Thefollowingexampleillustratestheusageofthisclass.
Example8
#include"ace/Synch.h"
//Globalmutableandshareddataonwhichwewillperformsimple
//arithmeticoperationswhichwillbeprotected.
ACE_Atomic_Op<ACE_Thread_Mutex,int>foo;
//Theworkerthreadswillstartfromhere.
staticvoid*worker(void*arg){
ACE_UNUSED_ARG(arg);
foo=5;
ACE_ASSERT(foo==5);

++foo;
ACE_ASSERT(foo==6);
foo;
ACE_ASSERT(foo==5);

foo+=10;
ACE_ASSERT(foo==15);

foo=10;
ACE_ASSERT(foo==5);

foo=5L;
ACE_ASSERT(foo==5);
return0;
}

45

intmain(intargc,char*argv[])
{
//spawnthreadsasinpreviousexamples
}

Intheaboveprogram,severalsimplearithmeticoperationsareperformedon the foo


variable.Aftertheoperation,anassertioncheckisperformedtomakesurethatthevalue
ofthevariableiswhatitissupposedtobe.
Youmaybewonderingwhysynchronizationofsucharithmeticprimitives(suchasinthe
programabove)isnecessary.Youmustthinkthatincrementanddecrementoperations
shouldbeatomic.
However,theseoperationsareusuallyNOTatomic.TheCPUwouldprobablydividethe
instructionsintothreesteps:areadofthevariable,anincrementordecrementandthena
writeback.Insuchacase,ifatomicoperationsarenotused,thenthefollowingscenario
maybedeveloped.

Thread one reads the variable, increments and gets swapped out without
writing the new value back.
Thread two reads the old value of the variable, increments it and writes back a
new incremented value.
Thread one overwrites thread twos increment with its own.

The above example program may not break even if there are no synchronization
primitivesinplace.Thereasonisthatthethreadinthiscaseiscomputeboundandthe
OSmaynotpreemptsuchathread.However,writingsuchcodewouldbeunsafe,since
youcannotrelyontheOSschedulertoactthisway.Inanycase,inmostenvironments,
timingrelationshipsarenondeterministic(becauseofrealtimeeffectslikepagefaults,
ortheuseoftimersorbecauseofactuallyhavingmultiplephysicalprocessors).

Thread Management with the ACE_Thread_Manager


Inall the previous examples, wehave beenusingthe ACE_Thread wrapperclass to
createanddestroythreads.However,thefunctionalityofthiswrapperclassissomewhat
limited.TheACE_Thread_Managerprovidesasupersetofthefacilitiesthatareavailable
inACE_Thread.Inparticular,itaddsmanagementfunctionalitytomakeiteasiertostart,
cancel, suspend and resume a group of related threads. It provides for creating and
destroyinggroupsofthreadsandtasks(ACE_Taskisahigherlevelconstructthanthreads
andcanbeusedinACEfordoingmultithreadedprogramming.Wewilltalkmoreabout
taskslater).Italsoprovidesfunctionalitysuchassendingsignalstoagroupofthreadsor
waitingonagroupofthreadsinsteadofcallingjoininthenonportablefashionthatwe
haveseeninthepreviousexamples.

46

ThefollowingexampleillustrateshowACE_Thread_Managercanbeusedtocreate,and
thenwaitfor,thecompletionofagroupofthreads.
Example9
#include"ace/Thread_Manager.h"
#include"ace/Get_Opt.h"
staticvoid*taskone(void*){
ACE_DEBUG((LM_DEBUG,"Thread:(%t)startedTaskone!\n"));
ACE_OS::sleep(2);
ACE_DEBUG((LM_DEBUG,"Thread:(%t)finishedTaskone!\n"));
return0;
}
staticvoid*tasktwo(void*){
ACE_DEBUG((LM_DEBUG,"Thread:(%t)startedTasktwo!\n"));
ACE_OS::sleep(1);
ACE_DEBUG((LM_DEBUG,"Thread:(%t)finishedTasktwo!\n"));
return0;
}
staticvoidprint_usage_and_die(){
ACE_DEBUG((LM_DEBUG,"Usageprogram_name
a<numthreadsforpool1>b<numthreadsforpool2>"));
ACE_OS::exit(1);
}
intmain(intargc,char*argv[]){
intnum_task_1;
intnum_task_2;
if(argc<3)
print_usage_and_die();
ACE_Get_Optget_opt(argc,argv,"a:b:");
charc;
while((c=get_opt())!=EOF){
switch(c){
case'a':
num_task_1=ACE_OS::atoi(get_opt.optarg);
break;
case'b':
num_task_2=ACE_OS::atoi(get_opt.optarg);
break;
default:
ACE_ERROR((LM_ERROR,"Unknownoption\n"));
ACE_OS::exit(1);
}
}

//Spawnthefirstsetofthreadsthatworkontask1.
if(ACE_Thread_Manager::instance()>spawn_n(num_task_1,
(ACE_THR_FUNC)taskone,//Executetaskone

47

0,//Noarguments
THR_NEW_LWP,//NewLightWeightProcess
ACE_DEFAULT_THREAD_PRIORITY,
1)==1)//GroupIDis1
ACE_ERROR((LM_ERROR,
Failuretospawnfirstgroupofthreads:%p\n"));

//Spawnsecondsetofthreadsthatworkontask2.
if(ACE_Thread_Manager::instance()>spawn_n(num_task_2,
(ACE_THR_FUNC)tasktwo,//Executetaskone
0,//Noarguments
THR_NEW_LWP,//NewLightWeightProcess
ACE_DEFAULT_THREAD_PRIORITY,
2)==1)//GroupIDis2
ACE_ERROR((LM_ERROR,
"Failuretospawnsecondgroupofthreads:%p\n"));

//Waitforalltasksingrp1toexit
ACE_Thread_Manager::instance()>wait_grp(1);
ACE_DEBUG((LM_DEBUG,"Tasksingroup1haveexited!Continuing\n"));
//Waitforalltasksingrp2toexit
ACE_Thread_Manager::instance()>wait_grp(2);
ACE_DEBUG((LM_DEBUG,"Tasksingroup2haveexited!Continuing\n"));
}

Thisnextexampleillustratesthesuspension,resumptionandcooperativecancellation
mechanismsthatareavailableintheACE_Thread_Manager.
Example10
//Testoutthegroupmanagementmechanismsprovidedbythe
//ACE_Thread_Manager,includingthegroupsuspensionandresumption,
//andcooperativethreadcancellationmechanisms.
#include"ace/Thread_Manager.h"
staticconstintDEFAULT_THREADS=ACE_DEFAULT_THREADS;
staticconstintDEFAULT_ITERATIONS=100000;
staticvoid*
worker(intiterations)
{
for(inti=0;i<iterations;i++){
if((i%1000)==0){
ACE_DEBUG((LM_DEBUG,
"(%t)checkingcancellationbeforeiteration%d!\n",
i));
//Beforedoingworkcheckifyouhavebeencanceled.Ifsodont
//doanymorework.
if(ACE_Thread_Manager::instance()>testcancel
(ACE_Thread::self())!=0){
ACE_DEBUG((LM_DEBUG,
"(%t)hasbeencanceledbeforeiteration%d!\n",i));

48

break;
}
}
}
return0;
}

intmain(intargc,char*argv[]){
intn_threads=argc>1?ACE_OS::atoi(argv[1]):DEFAULT_THREADS;
intn_iterations=argc>2?ACE_OS::atoi(argv[2]):
DEFAULT_ITERATIONS;
ACE_Thread_Manager*thr_mgr=ACE_Thread_Manager::instance();
//Createagroupofthreadsn_threadsthatwillexecutetheworker
//functionthespawn_nmethodreturnsthegroupIDforthegroupof
//threadsthatarespawned.Theargumentn_iterationsispassedback
//totheworker.Noticethatallthreadsarecreateddetached.
intgrp_id=thr_mgr>spawn_n(n_threads,ACE_THR_FUNC(worker),
(void*)n_iterations,
THR_NEW_LWP|THR_DETACHED);
//Waitfor1secondandthensuspendeverythreadinthegroup.
ACE_OS::sleep(1);
ACE_DEBUG((LM_DEBUG,"(%t)suspendinggroup\n"));
if(thr_mgr>suspend_grp(grp_id)==1)
ACE_ERROR((LM_DEBUG,"(%t)%p\n","Couldnotsuspend_grp"));
//Waitfor1moresecondandthenresumeeverythreadinthe
//group.
ACE_OS::sleep(1);
ACE_DEBUG((LM_DEBUG,"(%t)resuminggroup\n"));
if(thr_mgr>resume_grp(grp_id)==1)
ACE_ERROR((LM_DEBUG,"(%t)%p\n","resume_grp"));
//Waitfor1moresecondandthencancelallthethreads.
ACE_OS::sleep(ACE_Time_Value(1));
ACE_DEBUG((LM_DEBUG,"(%t)cancelinggroup\n"));
if(thr_mgr>cancel_grp(grp_id)==1)
ACE_ERROR((LM_DEBUG,"(%t)%p\n","cancel_grp"));
//Performabarrierwaituntilallthethreadshaveshutdown.
thr_mgr>wait();
return0;
}

Inthisexamplen_threadsarecreatedtoexecutetheworkerfunction.Eachthreadloops
in the worker function for n_iterations. While these threads loop in the worker
function,themainthreadwillsuspend()them,thenresume()themandlastlywillcancel

49

them.Eachthreadinworkerwillcheckforcancellationusingthetestcancel()methodof
ACE_Thread_Manager.

Thread Specific Storage


Whenasinglethreadedprogramwishestocreateavariablewhosevaluepersistsacross
multiplefunctioncalls,itallocatesthatdatastaticallyorglobally.Whensuchaprogram
ismademultithreaded,thisglobalorstaticdataisthesameforallthethreads.Thismay
ormaynotbedesirable.Forexample,apseudorandomgeneratormayneedastaticor
globalintegerseedvariablewhichisnotaffectedbyitsvaluebeingchangedbymultiple
threadsatthesametime.However,inothercasestheglobalorstaticdataelementmay
needtobedifferentforeachthreadthatexecutes.Forexample,consideramultithreaded
GUIapplicationinwhicheachwindowrunsinaseparatethreadandhasaninputport
fromwhichitreceiveseventinput.Suchaninputportmustremainpersistentacross
functioncallsinthewindowbutalsomustbewindowspecificorprivate.Toachieve
this,ThreadSpecificStorageisused.Astructuresuchastheinputportcanbeputinto
threadspecificstorageandislogicallyaccessedasifitisstaticorglobalwhenitis
actuallyprivatetothethread.
Traditionally,threadspecificstoragewasachievedusingaconfusinglowleveloperating
system API.InACE,TSS is achieved usingthe ACE_TSS template class.The class
whichistobethreadspecificispassedintotheACE_TSStemplateandthenallitspublic
methodsmaybeinvokedusingtheC++>operator.
ThefollowingexampleillustrateshowsimpleitistousethreadspecificstorageinACE.

Example11
#include"ace/Synch.h"
#include"ace/Thread_Manager.h"
classDataType{
public:
DataType():data(0){}
voidincrement(){data++;}
voidset(intnew_data){data=new_data;}
voiddecrement(){data;}
intget(){returndata;}
private:
intdata;
};
ACE_TSS<DataType>data;
staticvoid*thread1(void*){
data>set(10);
ACE_DEBUG((LM_DEBUG,"(%t)Thevalueofdatais%d\n",data>get()));
for(inti=0;i<5;i++)

50

data>increment();
ACE_DEBUG((LM_DEBUG,"(%t)Thevalueofdatais%d\n",data>get()));
return0;
}
staticvoid*thread2(void*){
data>set(100);
ACE_DEBUG((LM_DEBUG,"(%t)Thevalueofdatais%d\n",data>get()));
for(inti=0;i<5;i++)
data>increment();
ACE_DEBUG((LM_DEBUG,"(%t)Thevalueofdatais%d\n",data>get()));
return0;
}
intmain(intargc,char*argv[]){
//Spawnoffthefirstthread
ACE_Thread_Manager::instance()>spawn((ACE_THR_FUNC)thread1,0,THR_NEW_LWP|THR_DETACHED);
//Spawnoffthesecondthread
ACE_Thread_Manager::instance()>spawn((ACE_THR_FUNC)thread2,0,THR_NEW_LWP|THR_DETACHED);
//Waitforallthreadsinthemanagertocomplete.
ACE_Thread_Manager::instance()>wait();
ACE_DEBUG((LM_DEBUG,"Boththreadsdone.Exiting..\n"));
}

Intheaboveexample,theclassDataTypewascreatedinthreadspecificstorage.
Themethodsofthisclassarethenaccessedusingthe > operatorfromthefunctions
thread1andthread2,whichareexecutedintwoseparatethreads.Thefirstthreadsets
theprivatedatavariableto10andthenincrementsitby5totakeitto15.Thesecond
threadtakesitsprivatedatavariable,setsitsvalueto100andincrementsitby5to105.
Althoughthedatalooksglobalitisactuallythreadspecific,andeachthreadprintsout15
and105respectively,indicatingthesame.
Thereareseveraladvantagesinusingthreadspecificstoragewherepossible.Ifglobalor
datacanbekeptinthreadspecificstorage,thentheoverheadduetosynchronizationcan
beminimized.ThisisthemajoradvantageofusingTSS.

51

Chapter

5
Tasks and Active Objects
Patterns for Concurrent Programming
This chapter introduces the ACE_Task class, which was mentioned in the previous
chapter,andalsopresentstheActiveObjectpattern.Thischapterwillbasicallycovertwo
topics.First,itwillcoverhowtousethe ACE_Task constructasahighlevelobject
orientedmechanismforwritingmultithreadedprograms.Second,itwilldiscusshowthe
ACE_TaskisusedintheActiveObjectPattern[II].

Active Objects
Sowhatisanactiveobjectanyway?Traditionally,allobjectsarepassivepiecesofcode.
Codeinanobjectisexecutedwithinthethreadthathasissuedmethodcallsonit.Thatis,
thecallingthreadisborrowedtoexecutemethodsonthepassiveobject.
Activeobjects,however,actdifferently.Theseobjectsretaintheirownthread(oreven
multiplethreads)andusethisthreadforexecutionofanymethodsinvokedonthem.Thus
ifyouthinkofatraditionalobjectwithathread(ormultiplethreads)encapsulatedwithin
it,yougetanactiveobject.
Forexample,consideranobjectAthathasbeeninstantiatedinthemain()functionof
yourprogram.Whenyourprogramstartsup,asinglethreadiscreatedbytheOS,to
execute startingfromthe main() function.Ifyoucallanymethods onobject A, this
threadwillflowthroughandexecutethecodeinthatmethod.Oncecompleted,this
threadreturnsbacktothepointfromwhichthemethodhadbeeninvokedandcontinues
withitsexecution.However,ifAwasanActiveObject,thisisnotwhathappens.In
thiscase,themainthreadisnotborrowedbytheActiveObject.Instead,whenamethod
isinvokedonA,theexecutionofthemethodoccurswithinthethreadthatisretained
bytheActiveObject.Anotherwaytothinkaboutthisis:ifthemethodisinvokedona
passiveobject(aregularobject)thenthecallwillbeblockingorsynchronous;if,onthe
otherhand,themethodisinvokedonanActiveObject,thecallwillbenonblockingor
asynchronous.
52

ACE_Task
ACE_TaskisthebaseclassfortheTaskorActiveObject processingconstructthatis
availableinACE.ThisclassisoneoftheclassesthatisusedtoimplementtheActive
ObjectpatterninACE.Allobjectsthatwishtobeactivemustderivefromthisclass.
YoucanalsothinkofACE_Taskasbeingahigherlevel,moreOO,threadclass.
YoumusthavenoticedsomethingbadwhenweusedtheACE_Threadwrapper,inthe
previous chapter. Most of the examples in that chapter were programs which were
decomposed into functions, instead of objects. Why did this happen? Right, the
ACE_Threadwrapperneedstobepassedaglobalfunctionnameorastaticmethodasan
argument.Thisfunction(staticmethod)isthenusedasthestartpointforthethreadthat
isspawned.Thisnaturallyledtowritingafunctionforeachthread.Aswesawthismay
resultinnonobjectorienteddecompositionofprograms.
Incontrast,ACE_Taskdealswithobjects,andisthuseasiertothinkaboutwhenbuilding
OOprograms.Therefore,inmostcases,itisbettertouseasubclassofACE_Taskwhen
youneedtobuildmultithreadedprograms.Thereareseveraladvantagesindoingthis.
ForemostiswhatIjustmentioned,i.e.thisleadstobetterOOsoftware.Second,you
donthavetoworryaboutyourthreadentrypointbeingstatic,sincetheentrypointfor
ACE_Task isaregularmemberfunction.Furthermore,wewillseethat ACE_Task also
includesaneasytousemechanismforcommunicatingwithothertasks.
ToreiteratewhatIjustsaid,ACE_Taskcanbeusedas

A Higher Level Thread (which we call a Task ).

As an Active Object in the Active Object Pattern.

Structure of a Task
AnACE_TaskhasastructuresimilarinnaturetothestructureofActorsinActorBased
Systems[III].Thisstructureisillustratedbelow:
Tasks in ACE

Underlying
Threads

Underlying
Message
Queue

53

TheabovediagramshowsthateachTaskcontainsoneormorethreadsandanunderlying
message queue. Tasks communicate with each other through these message queues.
However,themessagequeuesarenotentitiesthattheprogrammerneedstobeawareof.
Asendingtaskcanjustusetheputq()calltoinsertamessageintothemessagequeueof
anothertask.Thereceivingtaskcanthenextractthismessagefromitsownmessage
queuebyusingthegetq()call.
Thus,youcanthinkofasystemofmoreorlessautonomoustasks(oractiveobjects)
communicatingwitheachotherthroughtheirmessagequeues.Suchanarchitecturehelps
considerablyinsimplifyingtheprogrammingmodelformultithreadedprograms.

Creating and using a Task


As mentioned above, to create a task or active object, you must subclass from the
ACE_Taskclass.Aftersubclassing,thefollowingstepsmustbetaken.

Implementing Service Initialization and Termination Methods: The


open() method should contain all task-specific initialization code. This may
include resources such as connection control blocks, locks and memory. The
close() method is the corresponding termination method.
Calling the Activation method: After an Active Object is instantiated, you
must activate it by calling activate(). The activate() method is passed, among
other things, the number of threads that are to be created within the Active Object.
The activate() method will cause the svc() method to be the starting point of all
threads that are spawned by it.
Implementing the Service Specific Processing Methods: As mentioned
above, after an active object is activated, each new thread is started in the svc()
method. This method must be defined in the subclass by the application developer.

Thefollowingexampleillustrateshowyouwouldgoaboutcreatingatask
Example1
#include"ace/OS.h"
#include"ace/Task.h"
classTaskOne:publicACE_Task<ACE_MT_SYNCH>{
public:
//ImplementtheServiceInitializationandTerminationmethods
intopen(void*){
ACE_DEBUG((LM_DEBUG,"(%t)ActiveObjectopened\n"));
//Activatetheobjectwithathreadinit.
activate();
return0;

54

}
intclose(u_long){
ACE_DEBUG((LM_DEBUG,"(%t)ActiveObjectbeingcloseddown\n"));
return0;
}
intsvc(void){
ACE_DEBUG((LM_DEBUG,
"(%t)Thisisbeingdoneinaseparatethread\n"));
//dothreadspecificworkhere
//.......
//.......
return0;
}
};
intmain(intargc,char*argv[]){
//Createthetask
TaskOne*one=newTaskOne;
//Startupthetask
one>open(0);
//waitforallthetaskstoexit
ACE_Thread_Manager::instance()>wait();
ACE_DEBUG((LM_DEBUG,"(%t)MainTaskends\n"));
}

TheaboveexampleillustrateshowACE_Taskcanbeusedasahigherlevelthreadclass.
Intheexample,theclassTaskOnederivesfromACE_Taskandimplementstheopen(),
close() and svc() methods.Afterthetaskobject is instantiated, the open() method is
invokedonit.Thismethodinturncallstheactivate()method,whichcausesanewthread
tobespawnedandstarted.Theentrypointforthisthreadisthesvc()routine.Themain
threadwaitsfortheactiveobjectthreadtoexpireandthenexitstheprocess.

Communication between tasks


Asmentionedearlier,eachtaskinACEhasanunderlyingmessagequeue(seeillustration
above).Thismessagequeueisusedasameansofcommunicationbetweentasks.When
onetaskswantstotalktoanothertask,itcreatesamessageandenqueuesthatmessage
ontothemessagequeueofthetaskthatitwishestotalkto.Thereceivingtaskisusually
onagetq()fromitsmessagequeue.Ifnodataisavailableinthequeue,itfallsasleep.If
someothertaskhasinsertedsomethingintoitsqueue,itwillwakeup,pickupthedata
fromitsqueueandprocessit. Thus,inthiscase,thereceivingtaskwillreceivethe
messagefromthesendingtaskandrespondtoitinsomeapplicationspecificmanner.
55

Thisnextexampleillustrateshowtwotaskscommunicatewitheachotherusingtheir
underlying message queue. This example involves an implementation for the classic
producerconsumer problem. The Producer Task creates data, which it sends to the
ConsumerTask.TheConsumerTaskinturnconsumesthisdata.Usingthe ACE_Task
construct, we think of both the Producer and Consumers as separate objects of type
ACE_Task. These tasks communicate with each other using the underlying message
queue.
Example2
#include"ace/OS.h"
#include"ace/Task.h"
#include"ace/Message_Block.h"
//TheConsumerTask.
classConsumer:
publicACE_Task<ACE_MT_SYNCH>{
public:
intopen(void*){
ACE_DEBUG((LM_DEBUG,"(%t)Producertaskopened\n"));
//ActivatetheTask
activate(THR_NEW_LWP,1);
return0;
}
//TheServiceProcessingroutine
intsvc(void){
//GetreadytoreceivemessagefromProducer
ACE_Message_Block*mb=0;
do{
mb=0;
//Getmessagefromunderlyingqueue
getq(mb);
ACE_DEBUG((LM_DEBUG,
"(%t)Gotmessage:%dfromremotetask\n",*mb>rd_ptr()));
}while(*mb>rd_ptr()<10);
return0;
}
intclose(u_long){
ACE_DEBUG((LM_DEBUG,"Consumerclosesdown\n"));
return0;
}
};
classProducer:
publicACE_Task<ACE_MT_SYNCH>{
public:
Producer(Consumer*consumer):
consumer_(consumer),data_(0){
mb_=newACE_Message_Block((char*)&data_,sizeof(data_));

56

}
intopen(void*){
ACE_DEBUG((LM_DEBUG,"(%t)Producertaskopened\n"));
//ActivatetheTask
activate(THR_NEW_LWP,1);
return0;
}
//TheServiceProcessingroutine
intsvc(void){
while(data_<11){
//Sendmessagetoconsumer
ACE_DEBUG((LM_DEBUG,
"(%t)Sendingmessage:%dtoremotetask\n",data_));
consumer_>putq(mb_);
//Gotosleepforasec.
ACE_OS::sleep(1);
data_++;
}
return0;
}
intclose(u_long){
ACE_DEBUG((LM_DEBUG,"Producerclosesdown\n"));
return0;
}
private:
chardata_;
Consumer*consumer_;
ACE_Message_Block*mb_;
};

intmain(intargc,char*argv[]){
Consumer*consumer=newConsumer;
Producer*producer=newProducer(consumer);
producer>open(0);
consumer>open(0);
//Waitforallthetaskstoexit.

ACE_Thread_Manager::instance()>wait();

Here,eachoftheProducerandConsumertasksareverysimilar.Neitherhasanyservice
initializationorterminationcode.Thesvc()methodforbothclassesisdifferent,however.
AftertheProducerisactivatedintheopen()method,thesvc()methodiscalled.Inthis
method,theProducercreatesamessage,whichitinsertsontotheconsumer'squeue.The
messageiscreatedusingtheACE_Message_Blockclass.(Toreadmoreabouthowtouse
57

ACE_Message_Block,pleasereadthechapteronmessagequeuesinthisguideandinthe
onlineACEtutorials).Theproducermaintainsapointertotheconsumertask(object).It
needsthispointersoitcanenqueueamessageontotheconsumer'smessagequeue.The
pointerissetupinthemain()function,throughitsconstructor.
The consumer sits in aloop in its svc() method and waits fordata tocome into its
messagequeue.Ifthereisnodataonthemessagequeue,theconsumerblocksandfalls
asleep.(Thisisdoneautomagicallybythe ACE_Taskclass).Oncedatadoesarriveon
theconsumer'squeue,itwakesupandconsumesthedata.
Here, the data that is sent by the producer consists of one integer. This integer is
incrementedbytheproducereachtime,beforebeingsenttotheconsumer.
Asyoucansee,thesolutionfortheproducerconsumerproblemissimpleandobject
oriented.Usingthe ACE_Thread wrapper,thereisagoodchancethataprogrammer
wouldcreateasolutionwhichwouldhaveaproducerandconsumerfunctionforeachof
the threads. ACE_Task is the better way to go when writing objectoriented multi
threadedprograms.

The Active Object Pattern


TheActiveObjectpatternisusedtodecouplemethodexecutionfrommethodinvocation.
Thispatternfacilitatesanother,moretransparentmethodforintertaskcommunication.
Thispatternusesthe ACE_Task classasanactiveobject.Methodsareinvokedonthis
objectasifitwerearegularobject.Thatis,methodinvocationisdonethroughthesame
old> operator,thedifferencebeingthattheexecutionofthesemethodsoccursinthe
thread which is encapsulated within ACE_Task. The client programmer will see no
difference, or only a minimal difference, when programming with passive or active
objects.Thisishighlydesirableforaframeworkdeveloper,whereyouwanttoshieldthe
clientoftheframeworkfromtheinnardsofhowtheframeworkisdoingitswork.Thusa
frameworkUSERdoesnothavetoworryaboutthreads,synchronization,rendezvous,
etc.

How the Active Object Pattern Works


The Active Object pattern is one of the more complicated patterns that has been
implementedinACEandhasseveralparticipants:
Thepatternhasthefollowingparticipants:
1. AnActiveObject(basedonanACE_Task).
2. AnACE_Activation_Queue.
3. Several ACE_Method_Objects. (One method object is needed for each of the
methodsthattheactiveobjectsupports).
4. SeveralACE_FutureObjects.(Oneisneededforeachofthemethodsthatreturns
aresult).
58

WehavealreadyseenhowanACE_Taskcreatesandencapsulatesathread.Tomakean
ACE_Taskanactiveobject,afewadditionalthingshavetobedone.
A method object must be written for all the methods that are going to be called
asynchronouslyfromtheclient.EachMethodObjectthatiswrittenwillderivefromthe
ACE_Method_Object andwillimplementthe call() method.EachMethodObjectalso
maintains context information (such as parameters, which are needed to execute the
methodandan ACE_Future Object,whichisusedtorecoverthereturnvalue.These
valuesaremaintainedasprivateattributes).Youcanconsiderthemethodobjecttobethe
closure of the method call. When a client issues a method call, this causes the
corresponding method object to be instantiated and then enqueued on the activation
queue.TheMethodObjectisaformofthe Command pattern.(Seethereferenceson
DesignPatterns).
The ACE_Activation_Queue isaqueueonwhichthemethodobjectsareenqueuedas
theywaittobeexecuted.Thustheactivationqueuecontainsallofthependingmethod
invocationsonit(intheformofmethodobjects).ThethreadencapsulatedinACE_Task
staysblocked,waitingforanymethodobjectstobeenqueuedontheActivationQueue.
Onceamethodobjectisenqueued,thetaskdequeuesthemethodobjectandissuesthe
call() method on it. The call() method should, in turn, call the corresponding
implementation of that method in the ACE_Task. After the implementation method
returns,thecall()methodset()stheresultthatisobtainedinanACE_Futureobject.
The ACE_Future object is used bythe client to obtain results for any asynchronous
operations it may have issued on the active object. Once the client issues an
asynchronouscall,itisimmediatelyreturnedan ACE_Future object.Theclientisthen
freetotrytoobtaintheresultsfromthisfutureobjectwheneveritpleases.Iftheclient
triestoextracttheresultfromthefutureobjectbeforeithasbeen set(),theclientwill
block.Iftheclientdoesnotwishtoblock,itcanpollthefutureobjectbyusingthe
ready() call. This method returns 1 if the result has been set and 0 otherwise. The
ACE_Futureobjectisbasedontheideaofpolymorphicfutures[IV]
The call() method should be implemented such that it sets the internal value of the
returnedACE_Futureobjecttotheresultobtainedfromcallingtheactualimplementation
method(thisactualimplementationmethodiswritteninACE_Task).
Thefollowingexampleillustrateshowtheactiveobjectpatterncanbeimplemented.In
thisexample,theActiveObjectisaLoggerobject.TheLoggerissentmessageswhich
itistologusingaslowI/Osystem.SincetheI/Osystemisslow,wedonotwantthe
mainapplicationtaskstobesloweddownbecauseofrelativelynontimecriticallogging.
Topreventthis,andtoallowtheprogrammertoissuethelogcallsasiftheyarenormal
methodcalls,weusetheActiveObjectPattern.
ThedeclarationfortheLoggerclassisshownbelow:
Example3a
//Theworkerthreadwithwhichtheclientwillinteract

59

classLogger:publicACE_Task<ACE_MT_SYNCH>{
public:
//Initializationandterminationmethods
Logger();
virtual~Logger(void);
virtualintopen(void*);
virtualintclose(u_longflags=0);
//TheentrypointforallthreadscreatedintheLogger
virtualintsvc(void);
///////////////////////////////////////////////////////
//Methodswhichcanbeinvokedbyclientasynchronously.
///////////////////////////////////////////////////////
//Logmessage
ACE_Future<u_long>logMsg(constchar*msg);
//ReturnthenameoftheTask
ACE_Future<constchar*>name(void);
///////////////////////////////////////////////////////
//ActualimplementationmethodsfortheLogger
///////////////////////////////////////////////////////
u_longlogMsg_i(constchar*msg);
constchar*name_i();
private:
char*name_;
ACE_Activation_Queueactivation_queue_;
};

As we can see, the Logger Active Object derives from ACE_Task and contains an
ACE_Activation_Queue.TheLoggersupportstwoasynchronousmethods,i.e. logMsg()
andname().Thesemethodsshouldbeimplementedsuchthatwhentheclientcallsthem,
they instantiate the corresponding method object type and enqueue it onto the task's
private activation queue. The actual implementation for these two methods (which
means the methods that really contain the code that does the requested job) are
logMsg_i()andname_i().
Thenextsegmentshowstheinterfacestothetwomethodobjectsthatweneed,onefor
eachofthetwoasynchronousmethodsintheLoggerActiveObject.
Example3b
//MethodObjectwhichimplementsthelogMsg()methodoftheactive//Loggeractiveobject
class
classlogMsg_MO:publicACE_Method_Object{
public:
//Constructorwhichispassedareferencetotheactiveobject,the
//parametersforthemethod,andareferencetothefuturewhich
//containstheresult.
logMsg_MO(Logger*logger,constchar*msg,
ACE_Future<u_long>&future_result);

60

virtual~logMsg_MO();
//Thecall()methodwillbecalledbytheLoggerActiveObject
//class,oncethismethodobjectisdequeuedfromtheactivation
//queue.Thisisimplementedsothatitdoestwothings.Firstit
//mustexecutetheactualimplementationmethod(whichisspecified
//intheLoggerclass.Second,itmustsettheresultitobtainsfrom
//thatcallinthefutureobjectthatithasreturnedtotheclient.
//Notethatthemethodobjectalwayskeepsareferencetothesame
//futureobjectthatitreturnedtotheclientsothatitcansetthe
//resultvalueinit.
virtualintcall(void);
private:
Logger*logger_;
constchar*msg_;
ACE_Future<u_long>future_result_;
};
//MethodObjectwhichimplementsthename()methodoftheactiveLogger//activeobject
class
classname_MO:publicACE_Method_Object{
public:
//Constructorwhichispassedareferencetotheactiveobject,the
//parametersforthemethod,andareferencetothefuturewhich
//containstheresult.
name_MO(Logger*logger,ACE_Future<constchar*>&future_result);
virtual~name_MO();
//Thecall()methodwillbecalledbytheLoggerActiveObject
//class,oncethismethodobjectisdequeuedfromtheactivation
//queue.Thisisimplementedsothatitdoestwothings.Firstit
//mustexecutetheactualimplementationmethod(whichisspecified
//intheLoggerclass.Second,itmustsettheresultitobtainsfrom
//thatcallinthefutureobjectthatithasreturnedtotheclient.
//Notethatthemethodobjectalwayskeepsareferencetothesame
//futureobjectthatitreturnedtotheclientsothatitcansetthe
//resultvalueinit.
virtualintcall(void);
private:
Logger*logger_;
ACE_Future<constchar*>future_result_;
};

Eachofthemethodobjectscontainsaconstructor,whichisusedtocreateaclosurefor
themethodcall.Thismeansthattheconstructorensuresthattheparametersandreturn
valuesforthecallarerememberedbytheobjectbyrecordingthemasprivatemember
datainthemethodobject.Thecallmethodcontainscodethatwilldelegatetheactual
implementation methods specified inthe Logger Active Object (i.e. logMsg_i() and
name_i()).
This next segment of the example contains the implementation for the two Method
Objects.
61

Example3c
//ImplementationforthelogMsg_MOmethodobject.
//Constructor
logMsg_MO::logMsg_MO(Logger*logger,constchar*msg,ACE_Future<u_long>&future_result)
:logger_(logger),msg_(msg),future_result_(future_result){

ACE_DEBUG((LM_DEBUG,"(%t)logMsginvoked\n"));
}
//Destructor
logMsg_MO::~logMsg_MO(){
ACE_DEBUG((LM_DEBUG,"(%t)logMsgobjectdeleted.\n"));
}
//InvokethelogMsg()method
intlogMsg_MO::call(void){
returnthis>future_result_.set(
this>logger_>logMsg_i(this>msg_));
}

Example3c
//Implementationforthename_MOmethodobject.
//Constructor
name_MO::name_MO(Logger*logger,ACE_Future<constchar*>&future_result):
logger_(logger),future_result_(future_result){

ACE_DEBUG((LM_DEBUG,"(%t)name()invoked\n"));
}
//Destructor
name_MO::~name_MO(){
ACE_DEBUG((LM_DEBUG,"(%t)nameobjectdeleted.\n"));
}
//Invokethename()method
intname_MO::call(void){
returnthis>future_result_.set(this>logger_>name_i());
}

The implementation for these two methods object is quite straightforward. As was
explained above, the constructor for the method object is responsible for creating a
closure(capturingtheinputparametersandtheresult).Thecall()methodcallstheactual
implementation methods and then sets the value in the future object by using its
ACE_Future::set()method.
ThisnextsegmentofcodeshowstheimplementationfortheLoggerActiveObjectitself.
Mostofthecodeisinthe svc() method.Itisinthismethodthatitdequeuesmethod
objectsfromtheactivationqueueandinvokesthecall()methodonthem.
Example3d
//ConstructorfortheLogger
Logger::Logger(){

this>name_=newchar[sizeof("Worker")];
ACE_OS:strcpy(name_,"Worker");

62

//Destructor
Logger::~Logger(void){

deletethis>name_;
}
//Theopenmethodwheretheactiveobjectisactivated
intLogger::open(void*){

ACE_DEBUG((LM_DEBUG,"(%t)Logger%sopen\n",this>name_));
returnthis>activate(THR_NEW_LWP);
}
//CalledthentheLoggertaskisdestroyed.
intLogger::close(u_longflags=0){

ACE_DEBUG((LM_DEBUG,"ClosingLogger\n"));

return0;
}
//Thesvc()methodisthestartingpointforthethreadcreatedinthe
//Loggeractiveobject.Thethreadcreatedwillruninaninfiniteloop
//waitingformethodobjectstobeenqueuedontheprivateactivation
//queue.Onceamethodobjectisinsertedontotheactivationqueuethe
//threadwakesup,dequeuesthemethodobjectandtheninvokesthe
//call()methodonthemethodobjectitjustdequeued.Ifthereareno
//methodobjectsontheactivationqueue,thetaskblocksandfalls
//asleep.
intLogger::svc(void){
while(1){
//Dequeuethenextmethodobject(weuseanautopointerin
//caseanexceptionisthrowninthe<call>).
auto_ptr<ACE_Method_Object>mo
(this>activation_queue_.dequeue());
ACE_DEBUG((LM_DEBUG,"(%t)callingmethodobject\n"));
//Callit.
if(mo>call()==1)
break;
//Destructorautomaticallydeletesit.
}
return0;
}
//////////////////////////////////////////////////////////////
//Methodswhichareinvokedbyclientandexecuteasynchronously.
//////////////////////////////////////////////////////////////
//Logthismessage
ACE_Future<u_long>Logger::logMsg(constchar*msg){
ACE_Future<u_long>resultant_future;
//Createandenqueuemethodobjectontotheactivationqueue
this>activation_queue_.enqueue
(newlogMsg_MO(this,msg,resultant_future));
returnresultant_future;
}

63

//ReturnthenameoftheTask
ACE_Future<constchar*>Logger::name(void){
ACE_Future<constchar*>resultant_future;

//Createandenqueueontotheactivationqueue
this>activation_queue_.enqueue
(newname_MO(this,resultant_future));
returnresultant_future;
}
///////////////////////////////////////////////////////
//ActualimplementationmethodsfortheLogger
///////////////////////////////////////////////////////
u_longLogger::logMsg_i(constchar*msg){
ACE_DEBUG((LM_DEBUG,"Logged:%s\n",msg));
//GotosleepforawhiletosimulateslowI/Odevice
ACE_OS::sleep(2);
return10;
}
constchar*Logger::name_i(){
//GotosleepforawhiletosimulateslowI/Odevice
ACE_OS::sleep(2);
returnname_;

Thelastsegmentofcodeillustratestheapplicationcode,whichinstantiatestheLogger
ActiveObjectandusesitforloggingpurposes.
Example3e
//Clientorapplicationcode.
intmain(int,char*[]){
//CreateanewinstanceoftheLoggertask
Logger*logger=newLogger;
//TheFuturesorIOUsforthecallsthataremadetothelogger.
ACE_Future<u_long>logresult;
ACE_Future<constchar*>name;
//Activatethelogger
logger>open(0);
//Logafewmessagesonthelogger
for(size_ti=0;i<n_loops;i++){
char*msg=newchar[50];
ACE_DEBUG((LM_DEBUG,
Issuinganonblockingloggingcall\n"));
ACE_OS::sprintf(msg,"Thisisiteration%d",i);
logresult=logger>logMsg(msg);

64

//Dontusethelogresulthereasitisn'tthatimportant...
}
ACE_DEBUG((LM_DEBUG,
"(%t)Invokedallthelogcalls\
andcannowcontinuewithotherwork\n"));
//Dosomeworkoverhere...
//...
//...
//Findoutthenameoftheloggingtask
name=logger>name();
//Checkto"see"iftheresultofthename()callisavailable
if(name.ready())
ACE_DEBUG((LM_DEBUG,"Nameisready!\n"));
else
ACE_DEBUG((LM_DEBUG,
"BlockingtillIgettheresultofthatcall\n"));
//obtaintheunderlyingresultfromthefutureobject.
constchar*task_name;
name.get(task_name);
ACE_DEBUG((LM_DEBUG,
"(%t)==>Thenameofthetaskis:%s\n\n\n",task_name));
//Waitforallthreadstoexit.
ACE_Thread_Manager::instance()>wait();
}

The client code issues several nonblocking asynchronous calls onthe Logger active
object.Noticethatthecallsappearasiftheyarebeingmadeonaregularpassiveobject.
Infact,thecallsarebeingexecutedinaseparatethreadofcontrol.Afterissuingthecalls
tologmultiplemessages,theclientthenissuesacalltodeterminethenameofthetask.
Thiscallreturnsafuturetotheclient.Theclientthenproceedstocheckwhetherthe
resultissetinthefutureobjectornot,usingtheready()method.Itthendeterminesthe
underlyingvalueinthefuturebyusingtheget()method.Noticehoweleganttheclient
codeis,withnomentionofthreads,synchronization,etc.Therefore,theactiveobject
patterncanbeusedtohelpmakethelivesofyourclientsalittlebiteasier.

65

The Reactor
Chapter

An Architectural Pattern for Event De-multiplexing and Dispatching


TheReactor Patternhas beendevelopedtoprovideanextensible OO frameworkfor
efficienteventdemultiplexinganddispatching. CurrentOSabstractionsthatareused
foreventdemultiplexingaredifficultandcomplicatedtouse,andarethereforeerror
prone.TheReactorpatternessentiallyprovidesforasetofhigherlevelprogramming
abstractions that simplify the design and implementation of eventdriven distributed
applications.Besidesthis,theReactorintegratestogetherthedemultiplexingofseveral
differentkindsofeventstooneeasytouseAPI.Inparticular,theReactorhandlestimer
based events, signal events, I/Obased port monitoring events and userdefined
notificationsuniformly.
Inthischapter,wedescribehowtheReactorisusedtodemultiplexallofthesedifferent
eventtypes.

Reactor Components
Application

Application
Specific

EventHandler

Application
Specific

Application
Specific

EventHandler

EventHandler

Application
Specific

EventHandler

Framework
Timer Queue

Handle Table

OS Event De-Multiplexing Interface


66

Signal Handlers

Asshownintheabovefigure,theReactorinACEworksinconjunctionwithseveral
components, both internal and external to itself. The basic idea is that the Reactor
frameworkdetermines thataneventhasoccurred(bylisteningontheOSEventDe
multiplexingInterface)andissuesacallback toamethodinapreregistered event
handler object.Thisobjectisimplementedbytheapplicationdeveloperandcontains
applicationspecificcodetohandletheevent.
Theuser(i.e.,theapplicationdeveloper)mustthus:
1) Create an Event Handler to handle an event he is interested in.
2) Register with the Reactor, informing it that he is interested in handling an event and at

this time also passing a pointer to the event handler he wishes to handle the event.

TheReactorframeworkthenautomaticallywill:
1) The Reactor maintains tables internally, which associate different event types with

event handler objects


2) When an event occurs that the user had registered for, it issues a call back to the

appropriate method in the handler.

Event Handlers
TheReactorpatternisimplementedinACEastheACE_Reactorclass,whichprovidesan
interfacetothereactorframework'sfunctionality.
Aswasmentionedabove,thereactoruseseventhandlerobjectsastheserviceproviders
whichhandleaneventoncethereactorhassuccessfullydemultiplexedanddispatchedit.
Thereactorthereforeinternallyremembers which eventhandlerobjectistobecalled
backwhenacertaintypeofeventoccurs.Thisassociationbetweeneventsandtheirevent
handlersiscreatedwhenanapplicationregistersitshandlerobjectwiththereactorto
handleacertaintypeofevent.
SincethereactorneedstorecordwhichEventHandleristobecalledback,itneedsto
know the type of all Event Handler object. This is achieved with the help of the
substitutionpattern(orinotherwordsthroughinheritanceoftheisatypeofvariety).
Theframeworkprovides anabstractinterface classnamed ACE_Event_Handler from
whichallapplication specific eventhandlers MUSTderive. (This causeseachofthe
ApplicationSpecificHandlerstohavethesametype,namelyACE_Event_Handler,and
thustheycanbesubstitutedforeachother).Formoredetailonthisconcept,pleasesee
thereferenceontheSubstitutionPattern[V].

67

Ifyounoticethecomponentdiagramabove,itshowsthattheeventhandlerovalsconsist
ofablueEvent_Handlerportion,whichcorrespondstoACE_Event_Handler,andawhite
portion,whichcorrespondstotheapplicationspecificportion.
Thisisillustratedintheclassdiagrambelow:

ACE_Event_Handler
int handle_input()
int handle_output()

Application_Handler1

Application_Handler2

int handle_input()
int get_handle()

int handle_output()
int get_handle()

The ACE_Event_Handler classhasseveraldifferent handlemethods,eachofwhich


are used to handle different kinds of events. When an application programmer is
interestedinacertainevent,hesubclassestheACE_Event_Handlerclassandimplements
thehandlemethodsthatheisinterestedin.Asmentionedabove,hethenproceedsto
registerhiseventhandlerclassforthatparticulareventwiththereactor.Thereactor
willthenmakesurethatwhentheeventoccurs,theappropriatehandlemethodinthe
appropriateeventhandlerobjectiscalledbackautomatically.
Onceagain,therearebasicallythreestepstousingtheACE_Reactor:

Create a subclass of ACE_Event_Handler and implement the correct


handle_ method in your subclass to handle the type of event you wish to service
with this event handler. (See table below to determine which handle_ method
you need to implement. Note that you may use the same event handler object to
handle multiple types of events, and thus may overload more then one of the
handle_ methods.)
Register your Event handler with the reactor by calling register_handler() on
the reactor object.
As events occur, the reactor will automatically call back the correct handle_
method of the event handler object that was previously registered with the Reactor
to process that event.

Asimpleexampleshouldhelptomakethisalittleclearer.
Example1
#include<signal.h>
#includeace/Reactor.h

68

#includeace/Event_Handler.h
//Createoursubclasstohandlethesignalevents
//thatwewishtohandle.Sinceweknowthatthisparticular
//eventhandlerisgoingtobeusingsignalsweonlyoverloadthe
//handle_signalmethod.
class
MyEventHandler:publicACE_Event_Handler{
int
handle_signal(intsignum,siginfo_t*,ucontext_t*){
switch(signum){
caseSIGWINCH:
ACE_DEBUG((LM_DEBUG,YoupressedSIGWINCH\n));
break;
caseSIGINT:
ACE_DEBUG((LM_DEBUG,YoupressedSIGINT\n));
break;
}
return0;
}
};
intmain(intargc,char*argv[]){
//instantiatethehandler
MyEventHandler*eh=newMyEventHandler;
//RegisterthehandleraskingtocallbackwheneitherSIGWINCH
//orSIGINTsignalsoccur.Notethatinboththecasesweaskedthe//Reactortocallback
thesameEvent_Handleri.e.,MyEventHandler.//Thisisthereasonwhywehadtowritea
switchstatementinthe//handle_signal()methodabove.AlsonotethattheACE_Reactor
is//beingusedasaSingletonobject(Singletonpattern)
ACE_Reactor::instance()>register_handler(SIGWINCH,eh);
ACE_Reactor::instance()>register_handler(SIGINT,eh);
while(1)
//Startthereactorseventloop
ACE_Reactor::instance()>handle_events();
}

Intheaboveexample,wefirstcreateasubclassof ACE_Event_Handler inwhichwe


overload the handle_signal() method, since we intend to use this handler to handle
varioustypesofsignals.Inthemainroutine,weinstantiateourhandlerandthencall
register_handler on the ACE_Reactor Singleton, specifying that we wish the event
handler eh to be called back when either SIGWINCH (signal on terminal window
change)orSIGINT(interruptsignal,usually^C)occur.Afterthis,westartthereactor's
eventloopbycallinghandle_events()inaninfiniteloop.Whenevereitheroftheevents
happenthereactorwillcallbacktheeh>handle_signal()methodautomatically,passing
it the signal number which caused the callback, and the siginfo_t structure (see
siginfo.hformoreonsiginfo_t).
69

NoticetheuseoftheSingletonpatterntoobtainareferencetotheglobalreactorobject.
Mostapplicationsrequireasinglereactorandthus ACE_Reactor comescompletewith
the instance() method which insures that whenever this method is called, the same
ACE_Reactorinstanceisreturned.(ToreadmoreabouttheSingletonPatternpleasesee
theDesignPatternsreference[VI].)
The following table shows which methods must be overloaded in the subclass of
ACE_Event_Handlertoprocessdifferenteventtypes.
Handle Methods in
ACE_Event_Handler
handle_signal()
handle_input()
handle_exception()
handle_timeout()
handle_output()

Overloaded in subclass to be used to handle event of type:


Signals. When any signal is registered with the reactor, it will call back this
method automatically when the signal occurs.
Input from I/O device. When input is available on an I/O handle (such as a file
descriptor in UNIX), this method will be called back by the reactor
automatically.
Exceptional Event. When an exceptional event occurs on an event that has
been registered with the reactor (for example, if SIGURG (Urgent Signal) is
received), then this method will be automatically called back.
Timer. When the time for any registered timer expires, this method will be
called back automatically.
Output possible on I/O device. When the output queue of an I/O device has
space available on it, this method will automatically be called back.

Registration of Event Handlers


Aswesawintheexampleabove,aneventhandlerisregisteredtohandleacertainevent
bycallingtheregister_handler()methodonthereactor.Theregister_handler()methodis
overloaded,i.e.thereareactuallyseveralmethodsforregisteringdifferenteventtypes,
eachcalledregister_handler(),buthavingadifferentsignature,i.e.themethodsdifferin
theirarguments.Theregister_handler()methodsbasicallytakethehandle/event_handler
tupleorthe signal/event_handler tupleasargumentsandaddittothereactor'sinternal
dispatch tables. When an event occurs on handle, it finds the corresponding
event_handlerinitsinternaldispatchtableandautomaticallycallsbacktheappropriate
methodontheevent_handleritfinds.Moredetailsofspecificcallstoregisterhandlers
willbeillustratedinlatersections.

Removal and lifetime management of Event Handlers


Oncetherequiredeventhasbeenprocessed,itmaynotbenecessarytokeeptheevent
handlerregisteredwiththereactor.Thereactorthusofferstechniquestoremoveanevent
handlerfromitsinternaldispatchtables.Oncetheeventhandlerisremoved,itwillno
longerbecalledbackbythereactor.
Anexampleofsuchasituationcouldbeaserverwhichservesmultipleclients.The
clientsconnecttotheserver,haveitperformsomeworkandthendisconnectlater.When
anewclientconnectstotheserver,aneventhandlerobjectisinstantiatedandregistered
intheserver'sreactortohandleallI/Ofromthisclient.Whentheclientdisconnectsthen
theservermustremovetheeventhandlerfromthereactor'sdispatchqueue,sinceitno
70

longer expects any further I/O from the client. In this example, the client/server
connectionmaybecloseddown,whichleavestheI/Ohandle(filedescriptorinUNIX)
invalid.ItisimportantthatsuchadefuncthandleberemovedfromtheReactor,since,if
thisisnotdone,theReactorwillmarkthehandleasreadyforreadingandcontinually
callbackthehandle_input()methodoftheeventhandlerforever.
Thereareseveraltechniquestoremoveaneventhandlerfromthereactor.
Implicit Removal of Event Handlers from the Reactors Internal dispatch tables
Themorecommontechniquetoremoveahandlerfromthereactorisimplicitremoval.
Eachofthehandle_methodsoftheeventhandlerreturnsanintegertothereactor.If
this integer is 0,thenthe event handler remains registered withthereactor afterthe
handlemethodiscompleted.However,ifthehandle_methodreturns<0, thenthe
reactorwillautomaticallycallbackthehandle_close()methodoftheEventHandlerand
removeitfromitsinternaldispatchtables.Thehandle_close()methodisusedtoperform
anyhandlerspecificcleanupthatneedstobedonebeforetheeventhandlerisremoved,
whichmayincludethingslikedeletingdynamicmemorythathadbeenallocatedbythe
handlerorclosinglogfiles.
Intheexampledescribedabove,itisnecessarytoactuallyremovetheeventhandlerfrom
memory.Suchremovalcanalsooccurinthehandle_close()methodoftheconcreteevent
handlerclass.Considerthefollowingconcreteeventhandler:
classMyEventHandler:publicACE_Event_Handler{
public:
MyEventHandler(){//constructinternaldatamembers}
virtualint
handle_close(ACE_HANDLEhandle,ACE_Reactor_Maskmask){
deletethis;//commitsuicide
}
~MyEventHandler(){//destroyinternaldatamembers}
private:
//internaldatamembers
}

Thisclassdeletesitselfwhenitisderegistersfromthereactorandthe handle_close()
hookmethodiscalled.ItisVERYimportanthoweverthatMyEventHandlerisalways
allocateddynamicallyotherwisetheglobalmemoryheapmaybecorrupted.Onewayto
ensurethattheclassisalwayscreateddynamicallyistomovethedestructorintothe
privatesectionoftheclass.Forexample:
classMyEventHandler:publicACE_Event_Handler{
public:
MyEventHandler(){//constructinternaldatamembers}
virtualinthandle_close(ACE_HANDLEhandle,ACE_Reactor_Maskmask){
deletethis;//commitsuicide}
private:
//Classmustbeallocateddynamically
~MyEventHandler(){//destroyinternaldatamembers}
};

71

Explicit removal of Event Handlers from the Reactors Internal Dispatch Tables
AnotherwaytoremoveanEventHandlerfromthereactor'sinternaltablesistoexplicitly
calltheremove_handler()setofmethodsofthereactor.Thismethodisalsooverloaded,
asisregister_handler().Ittakesthehandleorthesignalnumberwhosehandleristobe
removed and removes it from the reactors internal dispatch tables. When the
remove_handler()iscalled,italsocallsthehandle_close()methodoftheEventHandler
(whichisbeingremoved)automatically.Thiscanbecontrolledbypassinginthemask
ACE_Event_Handler::DONT_CALL tothe remove_handler() method,whichcausesthe
handle_close() method NOT to be called. More specific examples of the use of
remove_handler()willbeshowninthenextfewsections.

Event Handling with the Reactor


Inthenextfewsections,wewillillustratehowtheReactorisusedtohandlevarious
typesofevents.

I/O Event De-multiplexing


TheReactorcanbeusedtohandleI/Odevicebasedinputeventsbyoverloadingthe
handle_input()methodintheconcreteeventhandlerclass.SuchI/Omaybeondiskfiles,
pipes, FIFOs or network sockets. For I/O devicebased event handling, the Reactor
internallyusesthehandletothedevicewhichisobtainedfromtheoperatingsystem.(The
handleonUNIXbasedsystemisthefiledescriptorreturnedbytheOSwhenafileor
socket is opened. In Windows the handle is a handle to the device returned by
Windows.).Oneofthemostusefulapplicationsofsuchdemultiplexingisobviouslyfor
networkapplications.Thefollowingexamplewillhelpillustratehowthereactormaybe
usedinconjunctionwiththeconcreteacceptortobuildaserver.
Example2
#includeace/Reactor.h
#includeace/SOCK_Acceptor.h
#definePORT_NO19998
typedefACE_SOCK_AcceptorAcceptor;
//forwarddeclaration
classMy_Accept_Handler;
class
My_Input_Handler:publicACE_Event_Handler{
public:
//Constructor
My_Input_Handler(){
ACE_DEBUG((LM_DEBUG,Constructor\n);

72

}
//Calledbacktohandleanyinputreceived
int
handle_input(ACE_HANDLE){
//receivethedata
peer().recv_n(data,12);
ACE_DEBUG((LM_DEBUG,%s\n,data));
//dosomethingwiththeinputreceived.
//...
//keepyourselfregisteredwiththereactor
return0;
}
//Usedbythereactortodeterminetheunderlyinghandle
ACE_HANDLE
get_handle()const{
returnthis>peer_i().get_handle();
}
//Returnsareferencetotheunderlyingstream.
ACE_SOCK_Stream&
peer_i(){
returnthis>peer_;
}
private:
ACE_SOCK_Streampeer_;
chardata[12];
};

class
My_Accept_Handler:publicACE_Event_Handler{
public:
//Constructor
My_Accept_Handler(ACE_Addr&addr){
this>open(addr);
}
//Openthepeer_acceptorsoitstartstolisten
//forincomingclients.
int
open(ACE_Addr&addr){
peer_acceptor.open(addr);
return0;
}
//Overloadthehandleinputmethod
int
handle_input(ACE_HANDLEhandle){
//Clienthasrequestedconnectiontoserver.
//Createahandlertohandletheconnection
My_Input_Handler*eh=newMy_Input_Handler();

73

//AccepttheconnectionintotheEventHandler
if(this>peer_acceptor.accept(eh>peer(),//stream
0,//remoteaddress
0,//timeout
1)==1)//restartifinterrupted
ACE_DEBUG((LM_ERROR,Errorinconnection\n));
ACE_DEBUG((LM_DEBUG,Connectionestablished\n));
//Registertheinputeventhandlerforreading
ACE_Reactor::instance()>
register_handler(eh,ACE_Event_Handler::READ_MASK);
//Unregisterastheacceptorisnotexpectingnewclients
return1;
}
//Usedbythereactortodeterminetheunderlyinghandle
ACE_HANDLE
get_handle(void)const{
returnthis>peer_acceptor.get_handle();
}
private:
Acceptorpeer_acceptor;
};
intmain(intargc,char*argv[]){
//Createanaddressonwhichtoreceiveconnections
ACE_INET_Addraddr(PORT_NO);
//CreatetheAcceptHandlerwhichautomaticallybeginstolisten
//forclientrequestsforconnections
My_Accept_Handler*eh=newMy_Accept_Handler(addr);
//Registerthereactortocallbackwhenincomingclientconnects
ACE_Reactor::instance()>register_handler(eh,
ACE_Event_Handler::ACCEPT_MASK);
//Starttheeventloop
while(1)
ACE_Reactor::instance()>handle_events();
}

Intheaboveexample,twoconcreteeventhandlersarecreated.Thefirstconcreteevent
handler, My_Accept_Handler, is used to accept and establish incoming connections
fromclients.TheothereventhandlerisMy_Input_Handler,whichisusedtohandlethe
connection after it has been established. Thus My_Accept_Handler accepts the
connectionanddelegatestheactualhandlingofftoMy_Input_Handler.

74

Main Routine

Reactor
creates

My_Accept_Handler

My_Input_Handler

registers
get_handle()
handle_input ()

creates

get_handle()
handle_input()

Intheaboveexample,firstwecreatean ACE_INET_Addr objectpassingittheporton


whichwewishtoacceptconnections.Next,anobjectoftype My_Accept_Handler is
instantiated. The address object is then passed to My_Accept_Handler through its
constructor. My_Accept_Handler has an underlying concrete acceptor (read more
about concrete acceptors in the chapter on IPC) which it uses to establish the
connection.Theconstructorof My_Accept_Handler delegates thelisteningfornew
connections to the open() method of this concrete acceptor. After the handler starts
listeningforconnectionsitisregisteredwiththereactorinformingitthatitistobecalled
backwhenanewconnectionrequestisreceived.Todothis,wecallregister_handler()
withthemaskACE_Event_Handler::ACCEPT_MASK.
When the reactor is told to register the handler, it performs a double dispatch to
determinetheunderlyinghandleoftheeventhandler.Todothis,itcallstheget_handle()
method.Sincethereactorusesthe get_handle() methodtodeterminethehandletothe
underlying stream, it is necessary that this method be implemented in
My_Accept_Handler. In this example, we simply call get_handle() on the concrete
acceptor,whichreturnstheappropriatehandletothereactor.
Onceanewconnectionrequestisreceivedonthishandle,thereactorwillautomatically
callbackthehandle_input()methodofMy_Accept_Handler.TheAcceptHandlerthen
instantiatesanewInputHandlerandcalls accept()ontheconcreteacceptortoactually
establishtheconnection.NoticethattheunderlyingstreamoftheInputHandlerispassed
in as the first argument to the accept() call. This causes the stream in the newly
instantiatedinputhandlertobesettothenewstreamwhichhasjustbeencreatedafter
establishmentoftheconnection(bytheaccept()call).TheAcceptHandlerthenregisters
theInputHandlerwiththereactor,informingittocallbackifanyinputisavailableto
read(usingACE_Event_Handler::READ_MASK).Itthenreturns1,whichcausesittobe
removedfromthereactor'sinternaleventdispatchtables.
When any input now arrives from the client, My_Input_Handler::handle_input() will
automaticallybecalledbackbythereactor.Notethatinthe handle_input() methodof
My_Input_Handler,0isreturnedtothereactor.Thisindicatesthatwewishtokeepit
registered, whereas in My_Accept_Handler we insured that is was deregistered by
returning1initshandle_input()method.
BesidestheREAD_MASKandACCEPT_MASKthatareusedintheexampleabove,
thereareseveralothermasksthatcanbeusedwhenregisteringandremovinghandles
75

from the reactor. These masks are shown in the table below, and can be used in
conjunction with the register_handler() and remove_handler() methods. Each mask
insuresdifferentbehaviorofthereactor whenitcallsbackaneventhandler,usually
meaningadifferenthandlemethodistobecalled.
MASK
ACE_Event_Handler::
READ_MASK

Calls back method

When

handle_input().

ACE_Event_Handler::
WRITE_MASK

handle_output().

ACE_Event_Handler::
TIMER_MASK

handle_close().

ACE_Event_Handler::
ACCEPT_MASK

handle_input().

ACE_Event_Handler::
CONNECT_MASK
ACE_Event_Handler::
DONT_CALL

handle_input().

When there is data


available to read on the
handle.
When there is room
available on the I/O
devices output buffer and
new data can be sent to
it.
Passed to handle_close()
to indicate the reason for
calling it was a time-out.
When a client request for
a new connection is
heard on the OSs
internal listen queue.
When the connection has
been established.

None.

Used with
register_handler()

register_handler()

Acceptor and Connectors


handle_timeout methods
and NOT by the Reactor.
register_handler()

register_handler()

Insures that the


remove_handler()
handle_close() method of
the Event_Handler is
NOT called when the
reactor's
remove_handler()
method is called.

Timers
TheReactoralsoincludesmethodstoscheduletimers,whichonexpirycallbackthe
handle_timeout()methodoftheappropriateeventhandler.Toschedulesuchtimers,the
reactorhasaschedule_timer()method.Thismethodispassedtheeventhandler,whose
handle_timeout() method is to be called back, and the delay in the form of an
ACE_Time_Valueobject.Inaddition,anintervalmayalsobespecifiedwhichcausesthe
timertoberesetautomaticallyafteritexpires.
Internally,theReactormaintainsanACE_Timer_Queuewhichmaintainsallofthetimers
intheorderinwhichtheyaretobescheduled.Theactualdatastructureusedtoholdthe
timers can be varied by using the set_timer_queue() method of the reactor. Several
differenttimerstructuresareavailabletousewiththereactor,includingtimerwheels,
timerheapsandhashedtimerwheels.Thesearediscussedinalatersectionindetail.

76

ACE_Time_Value
The ACE_Time_Value objectisawrapperclasswhichencapsulatesthedataandtime
structureoftheunderlyingOSplatform.Itisbasedonthetimevalstructureavailableon
mostUNIXoperatingsystems,whichstoresabsolutetimeinsecondsandmicroseconds.
OtherOSplatforms,suchasPOSIXandWin32,useslightlydifferentrepresentations.
ThisclassencapsulatesthesedifferencesandprovidesaportableC++interface.
The ACE_Time_Value class uses operator overloading, which provides for simple
arithmetic additions, subtractions and comparisons. Methods in this class are
implementedtonormalizetimequantities.Normalizationadjuststhetwofieldsina
timevalstructtouseacanonicalencodingschemethatensuresaccuratecomparisons.
(FormoreseeAppendixandReferenceGuide).

Setting and Removing Timers


Thefollowingexampleillustrateshowtimerscanbeusedwiththereactor.
Example3
#includetest_config.h
#includeace/Timer_Queue.h
#includeace/Reactor.h
#defineNUMBER_TIMERS10
staticintdone=0;
staticintcount=0;
classTime_Handler:publicACE_Event_Handler
{
public:
//MethodwhichiscalledbackbytheReactorwhentimeoutoccurs.
virtualinthandle_timeout(constACE_Time_Value&tv,
constvoid*arg){
longcurrent_count=long(arg);
ACE_ASSERT(current_count==count);
ACE_DEBUG((LM_DEBUG,%d:Timer#%dtimedoutat%d!\n,
count,current_count,tv.sec()));
//Incrementcount
count++;
//Makesureassertiondoesntfailformissing5thtimer.
if(count==5)
count++;
//Ifalltimersdonethensetdoneflag
if(current_count==NUMBER_TIMERS1)
done=1;
//KeepyourselfregisteredwiththeReactor.
return0;

77

}
};
int
main(int,char*[])
{
ACE_Reactorreactor;
Time_Handler*th=newTime_Handler;
inttimer_id[NUMBER_TIMERS];
inti;
for(i=0;i<NUMBER_TIMERS;i++)
timer_id[i]=reactor.schedule_timer(th,
(constvoid*)i,//argumentsenttohandle_timeout()
ACE_Time_Value(2*i+1));
//settimertogooffwithdelay
//Cancelthefifthtimerbeforeitgoesoff
reactor.cancel_timer(timer_id[5]);//TimerIDoftimertoberemoved
while(!done)
reactor.handle_events();
return0;
}

Intheaboveexample,aneventhandler,Time_Handlerisfirstsetuptohandlethetime
outs by implementing the handle_timeout() method. The main routine instantiates an
object of type Time_Handler and schedules multiple timers (10 timers) using the
schedule_timer()methodofthereactor.Thismethodtakes,asarguments,apointertothe
handlerwhichwillbecalledback,thetimeafterwhichthetimerwillgooffandan
argumentthatwillbesenttothehandle_timeout() methodwhenitiscalledback.Each
timeschedule_timer()iscalled,itreturnsauniquetimeridentifierwhichisstoredinthe
arraytimer_id[].Thisidentifiermaybeusedtocancelthattimeratanytime.Anexample
ofcancelingatimerisalsoshownintheaboveexample,wherethefifthtimeriscanceled
bycalling thereactor's cancel_timer() method afterallthe timers havebeeninitially
scheduled. We cancel this timer by using its timer_id as an argument to the
cancel_timer()methodofthereactor.

Using different Timer Queues


Differentenvironmentsmayrequiredifferentwaysofschedulingandcancelingtimers.
Theperformanceofalgorithmstoimplementtimersbecomeanissuewhenanyofthe
followingaretrue:

Finegranularitytimersarerequired.

Thenumberofoutstandingtimersatanyonetimecanpotentiallybeverylarge.

Thealgorithmisimplementedusinghardwareinterruptswhicharetooexpensive.

ACEallowstheusertochoosefromamongseveraltimerswhichpreexistinACE,orto
developtheirowntimerstoaninterfacedefinedfortimers.Thedifferenttimersavailable
inACEaredetailedinthefollowingtable:
78

Timer
ACE_Timer_Heap
ACE_Timer_List
ACE_Timer_Hash

ACE_Timer_Wheel

Description of data structure

Performance

The timers are stored in a heap implementation Cost of schedule_timer()= O(lg n)


of a priority queue.
Cost of cancel_timer()= O(lgn)
Cost of finding current timer O(1)
The timers are stored in a doubly linked list ..
Cost of schedule_timer()= O(n)
insertions are..??
Cost of cancel_timer()= O(1)
Cost of finding current timer O(1)
This structure used in this case is a variation on Cost of schedule_timer()= Worst = O(n)
the timer wheel algorithm. The performance is Best = O(1)
highly dependent on the hashing function used. Cost of cancel_timer()= O(1)
Cost of finding current timer O(1)
The timers are stored in an array of pointers to Cost of schedule_timer()= Worst =O(n)
arrays. With each array being pointed to is
Cost of cancel_timer()= O(1)
sorted.
Cost of finding current timer O(1)

See reference [VII] on Timers for more.

Handling Signals
Aswesawinexample1,theReactorincludesmethodstoallowthehandlingofsignals.
The Event Handler which handles the signals should overload the handle_signal()
method,sincethiswillbecalledbackbythereactorwhenthesignaloccurs.Toregister
forasignal,weuseoneoftheregister_handler()methods,aswasillustratedinexample
1.Wheninterestinacertainsignalends,thehandlercanberemovedandrestoredtothe
previouslyinstalledsignalhandlerbycalling remove_handler().TheReactorinternally
usesthesigaction()systemcalltosetandresetsignalhandlers.Signalhandlingcanalso
bedonewithoutthereactor byusingthe ACE_Sig_Handlers classanditsassociated
methods.
One important difference in using the reactor for handling signals and using the
ACE_Sig_Handlersclassisthatthereactorbasedmechanismonlyallowstheapplication
toassociateoneeventhandlerwitheachsignal.TheACE_Sig_Handlersclasshowever
allowsmultipleeventhandlerstobecalledbackwhenasignaloccurs.

Using Notifications
Thereactornotonlyissuescallbackswhensystemeventsoccur,butcanalsocallback
handlers when user defined events occur. This is done through the reactor's
Notification interface, which consists of two methods, notify() and
max_notify_iterations().
Thereactorcanbeexplicitlyinstructedtoissueacallbackonacertaineventhandler
object byusing the notify() method. This is veryuseful whenthe reactor is used in
conjunctionwithmessagequeuesorwithcooperatingtasks.Goodexamplesofthiskind
ofusagecanbefoundwhentheASXframeworkcomponentsareusedwiththereactor.
The max_notify_iterations() methodinformsthereactortoperformonlythespecified
numberofiterationsatatime.Here,"iterations"referstothenumberofnotifications
79

thatcanoccurinasinglehandle_events()call.Thusifmax_notify_iterations()isusedto
setthemaxnumberofiterationsto20,and25notificationsarrivesimultaneously,then
the handle_events() method will only service 20 of the notifications at a time. The
remainingfivenotificationswillbehandledwhenhandle_events()iscalledthenexttime
intheeventloop.
Anexamplewillhelpillustratetheseideasfurther:
Example4
#includeace/Reactor.h
#includeace/Event_Handler.h
#includeace/Synch_T.h
#includeace/Thread_Manager.h
#defineWAIT_TIME1
#defineSLEEP_TIME2
classMy_Handler:publicACE_Event_Handler{
public:
//Starttheeventhandlingprocess.
My_Handler(){
ACE_DEBUG((LM_DEBUG,EventHandlercreated\n));
ACE_Reactor::instance()>max_notify_iterations(5);
return0;
}
//Performthenotificationsi.e.,notifythereactor10times
voidperform_notifications(){
for(inti=0;i<10;i++)
ACE_Reactor::instance()>
notify(this,ACE_Event_Handler::READ_MASK);
}
//Theactualhandlerwhichinthiscasewillhandlethenotifications
inthandle_input(ACE_HANDLE){
ACE_DEBUG((LM_DEBUG,Gotnotification#%d\n,no));
no++;
return0;
}
private:
staticintno;
};
//Staticmembers
intMy_Handler::no=1;

intmain(intargc,char*argv[]){
ACE_DEBUG((LM_DEBUG,Startingtest\n));
//Instantiatingthehandler
My_Handlerhandler;
//Thedoneflagissettonotdoneyet.
intdone=0;
while(1){

80

//AfterWAIT_TIMEthehandle_eventswillfallthroughifnoevents
//arrive.
ACE_Reactor::instance()>handle_events(ACE_Time_Value(WAIT_TIME));
if(!done){
handler.perform_notifications();
done=1;
}
sleep(SLEEP_TIME);
}
}

Intheaboveexample, aconcretehandleriscreatedasusualandthehandle_input()
methodisoverload,asitwouldbeifwewereexpectingourhandlertohandleinputdata
from an I/O device. The handler also contains an open() method, which performs
initializationforthehandler,andamethodwhichactuallyperformsthenotifications.
In the main() function, we first instantiate an instance of our concrete handler. The
constructorofthehandlerinsuresthatthenumberofmax_notify_iterationsissetto5by
usingthe max_notify_iterations() methodofthereactor.Afterthis,thereactorsevent
handlingloopisstarted.
Onemajordifferenceintheeventhandlinglooptobenotedhereisthathandle_events()
is passed an ACE_Time_Value. If no events occur within this time, then the
handle_events() method will fall through. After handle_events() falls through,
perform_notifications()iscalled,whichusesthereactorsnotify()methodtorequestitto
notifythehandlerthatispassedinasanargumentoftheoccurrenceofanevent.The
reactorwillthenproceedtousethemaskthatitispassedtoperformanupcallonthe
appropriatehandlemethodofthehandler.Inthiscase,weuse notify() toinformour
eventhandlerofinputbypassingittheACE_Event_Handler::READ_MASK.Thiscauses
thereactortocallbackthehandle_input()methodofthehandler.
Sincewehavesetthemax_notify_iterationsto5thereforeonly5ofthenotificationswill
actuallybeissuedbythereactorduringonecalltohandle_events().Tomakethisclear,
we stop the reactive event loop for SLEEP_TIME before issuing the next call to
handle_events().
Theaboveexample isoverlysimplisticandverynonrealistic,sincethenotifications
occurinthesamethreadasthereactor.Amorerealisticexamplewouldbeofevents
whichoccurinanotherthreadandwhichthennotifythereactorthreadoftheseevents.
Thesameexamplewithadifferentthreadtoperformthenotificationsisshownbelow:
Example5
#includeace/Reactor.h
#includeace/Event_Handler.h
#includeace/Synch_T.h
#includeace/Thread_Manager.h

81

classMy_Handler:publicACE_Event_Handler{
public:
//Starttheeventhandlingprocess.
My_Handler(){
ACE_DEBUG((LM_DEBUG,Gotopen\n));
activate_threads();
ACE_Reactor::instance()>max_notify_iterations(5);
return0;
}
//Spawnaseparatethreadsothatitnotifiesthereactor
voidactivate_threads(){
ACE_Thread_Manager::instance()
>spawn((ACE_THR_FUNC)svc_start,(void*)this);
}
//NotifytheReactor10times.
voidsvc(){
for(inti=0;i<10;i++)
ACE_Reactor::instance()>
notify(this,ACE_Event_Handler::READ_MASK);
}
//Theactualhandlerwhichinthiscasewillhandlethenotifications
inthandle_input(ACE_HANDLE){
ACE_DEBUG((LM_DEBUG,Gotnotification#%d\n,no));
no++;
return0;
}
//Theentrypointforthenewthreadthatistobecreated.
staticintsvc_start(void*arg);
private:
staticintno;
};
//Staticmembers
intMy_Handler::no=1;
intMy_Handler::svc_start(void*arg){
My_Handler*eh=(My_Handler*)arg;
eh>svc();
return1;//deregisterfromthereactor
}
intmain(intargc,char*argv[]){
ACE_DEBUG((LM_DEBUG,Startingtest\n));
My_Handlerhandler;
while(1){
ACE_Reactor::instance()>handle_events();
sleep(3);
}
}

82

This example is very similar to the previous example, except for a few additional
methodstospawnathreadandthenactivateitintheeventhandler.Inparticular,the
constructoroftheconcretehandler My_Handlercallstheactivatemethod.Thismethod
usesthe ACE_Thread_Manager::spawn() methodtospawnaseparatethread withits
entrypointassvc_start().
Thesvc_start()methodcallsperform_notifications()andthenotificationsaresenttothe
reactor,butthistimetheyaresentfromthisnewthreadinsteadoffromthesamethread
thatthereactorresidesin.Notethattheentrypointofthethread,svc_start(),wasdefined
asastaticmethodinthefunction,whichthencalledthenonstaticsvc()method.Thisisa
requirement when using thread libraries, i.e. the entry point of a thread be a static
functionwithfilescope.

83

The Acceptor and Connector


Chapter

Patterns for connection establishment


TheAcceptor/Connectorpatternhasbeendesignedtodecoupleconnectionestablishment
from the service which is performed after the connection has been established. For
example,inaWWWbrowser,theserviceoractualworkperformedistheparsingand
displayingoftheHTMLpagethathasbeenreceivedbytheclientbrowser.Connection
establishmentmaybeofsecondarysignificance,andisprobablydonethroughtheBSD
socketsinterfaceorsomeotherequivalentIPCmechanism.Theusageofthesepatterns
willallowtheprogrammertofocusonthisactualworkwithminimalconcernasto
howtheconnectionbetweentheserverandclientisactuallymade.Ontheflipside,the
programmercanalsofinetunetheconnectionestablishmentpoliciesindependentofthe
serviceroutineshemayhavewrittenorisabouttowrite.
Sincethispatterndecouplestheservicefromtheconnectionestablishmentmethod,itis
veryeasytochangeeitheronewithoutaffectingtheother.Thisallowsforcodereuse
both of prewritten connection establishment mechanisms and of prewritten service
routines. In the same example, the browser programmer using these patterns could
initiallybuildhissystemandrunitusingacertainconnectionestablishmentmechanism
andtestit.Later,hemaydecidethathewishestochangetheunderlyingconnection
mechanismtobemultithreaded,usingathreadpoolpolicyperhaps, iftheprevious
connection mechanism proved undesirable for the browser he has built. Using this
pattern,thiscouldbeachievedwithaminimalamountofeffort,duetothestrictde
couplingitprovides.
Youneedtoread:
YouwillhavetoreadthroughthechapterontheReactorandonIPC_SAP(inparticular
thesectiononacceptorsandconnectors)beforeyouwillbeabletoclearlyunderstand
many of the examples shown in this chapter, and especially to understand the more
advancedsection.Inaddition,youmayhavetorefertothesectiononthreadsandthread
management.

84

THE ACCEPTOR PATTERN


AnacceptorisusuallyusedwhereyouwouldimagineyouwouldusetheBSDaccept()
system call, as was discussed in the chapter on standalone acceptors. The Acceptor
Patternisalsoapplicableinthesamesituation,butaswewillsee,providesalotmore
functionality.InACE,theacceptorpatternisimplementedwiththehelpofaFactory
which is named ACE_Acceptor. A factory is a class which is used to abstract the
instantiation process of helper objects (usually). It is common in OO designs for a
complexclasstodelegatecertainfunctionstoahelperclass.Thechoiceofwhichclass
thecomplexclasscreatesasahelperandthendelegatestomayhavetobeflexible.This
flexibility is afforded with the help of a factory. Thus a factory allows an object to
changeitsunderlyingstrategiesbychangingtheobjectthatitdelegatestheworkto.The
factory,however,providesthesameinterfacetotheapplicationswhichareusingthe
factory,andthustheclientcodemaynotneedtobechangedatall.(Readmoreabout
factoriesinthereferenceonDesignPatterns).
Factory
Client

Helper object
Helper object
Helper object

The ACE_Acceptor factory allows an application developer to change the helper


objectsusedfor:

Passive Connection Establishment

Handling of the connection after establishment

Similarly, the ACE_Connector factory allows an application developer to change the


helperobjectsusedfor:

Active Connection Establishment

Handling of the connection after establishment

Thefollowingdiscussionappliesequallytoacceptorsandconnectors,soIwilljusttalk
aboutacceptorshereandtheargumentwillholdequallywellforconnectors.
TheACE_Acceptorhasbeenimplementedasatemplatecontainer,whichisinstantiated
withtwoclassesasitsactualparameters.Thefirstimplementstheparticularservice(and
isoftypeACE_Event_Handler,sinceitistobeusedtohandleI/Oeventsandmustbe
fromtheeventhandlinghierarchyofclasses)thattheapplicationistoperformafterit
establishesitsconnection,andthesecondisaconcreteacceptor(ofthevarietythatwas
discussedinthechapteronIPC_SAP).
85

AnimportantpointtonoteisthattheACE_Acceptorfactoryandtheunderlyingconcrete
acceptorthatisusedarebothverydifferentthings.Theconcreteacceptorcanbeused
independently of the ACE_Acceptor factory without having anything to do with the
acceptorpatternthatwearediscussinghere.(Theindependentusageofacceptorswas
discussed and illustrated in the chapter on IPC_SAP). The ACE_Acceptor factory is
intrinsictothispatternandcannotbeusedwithoutanunderlyingconcreteacceptor.Thus
ACE_AcceptorUSEStheunderlyingconcreteacceptorstoestablishconnections.Aswe
haveseen,thereareseveralclasseswhichcomebundledwithACEwhichcanbeusedas
the second parameter (i.e., the concrete acceptor class) to the ACE_Acceptor factory
template.Howevertheservicehandlingclassmustbeimplementedbytheapplication
developerandmustbeoftypeACE_Event_Handler.TheACE_Acceptorfactorycouldbe
instantiatedas:
typedefACE_Acceptor<My_Service_Handler,ACE_SOCK_ACCEPTOR>MyAcceptor;

HereMyAcceptor hasbeenpassedtheeventhandlercalled My_Service_Handler and


the concrete acceptor ACE_SOCK_ACCEPTOR. ACE_SOCK_ACCEPTOR is a TCP
acceptorbasedontheBSDsocketsstreamfamily.(Seetableattheendofthissectionand
inIPCchapterfordifferenttypesofacceptorsthatcanbepassedtotheacceptorfactory).
Note once again that, when using the acceptor pattern, we always deal with two
acceptors.ThefactoryacceptorcalledACE_Acceptor andoneoftheconcreteacceptors
whichareavailableinACE,suchasACE_SOCK_ACCEPTOR.(Youcancreatecustom
concrete acceptors which would replace ACE_SOCK_ACCEPTOR but you probably
won'tchangeanythingintheACE_Acceptorfactoryclass).
ImportantNote:ACE_SOCK_ACCEPTORisactuallyamacrodefinedas:
#defineACE_SOCK_ACCEPTORACE_SOCK_Acceptor,ACE_INET_Addr

Theuseofthismacrowasdeemednecessarysincetypedefsinsideaclassdont
work for compilers on certain platforms. If this hadnt been the case, then
ACE_Acceptorwouldhavebeeninstantiatedas:
typedefACE_Acceptor<My_Service_Handler,ACE_SOCK_Acceptor>
MyAcceptor;

Themacrosareillustratedinthetableattheendofthissection.

COMPONENTS
Asisclearfromtheabovediscussion,therearethreemajorparticipantclassesinthe
Acceptorpattern:

The concrete acceptor, which contains a specific strategy for establishing a


connection which is tied to an underlying transport protocol mechanism.
Examples of different concrete acceptors that can be used in ACE are
ACE_SOCK_ACCEPTOR (uses TCP to establish the connection),
ACE_LSOCK_ACCEPTOR (uses UNIX domain sockets to establish the
connection), etc.
86

The concrete service handler, which is written by the application developer


and whose open() method is called back automatically when the connection has
been established. The acceptor framework assumes that the service handling class
is of type ACE_Event_Handler, which is an interface class defined in ACE (This
class was discussed in detail in the chapter on the Reactor). Another class which
has been exclusively created for service handling for the acceptor and connector
patterns is ACE_Svc_Handler. This class is based not only on the
ACE_Event_Handler interface (which is necessary to use the Reactor ), but also
on the ACE_Task classes, which are used in the ASX Streams Framework. The
ACE_Task classes provide the ability to create separate threads, use message
queues to store incoming data messages, to process them concurrently and several
other useful functions. This extra functionality can be obtained if the concrete
service handler that is used with the acceptor pattern is created by deriving from
ACE_Svc_Handler instead of ACE_Event_Handler. The usage of the extra
functionality available in ACE_Svc_Handler is discussed in detail in the advanced
sections of this chapter. In the following discussion we will use the
ACE_Svc_Handler as our event handler. One important difference between a
simple ACE_Event_Handler and the ACE_Svc_Handler class is that the service
handler contains an underlying communication stream component (of the variety
discussed in the IPC SAP chapter). This stream is set when the ACE_Svc_Handler
template is instantiated. In the case of ACE_Event_Handler, we had to add the I/O
communication endpoint (i.e. the stream object) as a private data member of the
event handler ourselves. Thus, in this case the application developer creates his
service handler as a subclass of the ACE_Svc_Handler class and implements the
open() method as the first method which will be called back automatically by the
framework. In addition, since ACE_Svc_Handler is a template, the
communication stream component and the locking mechanism are passed in as
template parameters.
The reactor, which is used in conjunction with the ACE_Acceptor. As we
will see, after instantiating the acceptor we start the reactors event handling loop.
The reactor, as explained earlier, is an event-dispatching class and in this case is
used by the acceptor to handle the dispatching of the connection establishment
event to the appropriate service handling routine.

USAGE
FurtherunderstandingoftheAcceptorcanbegainedbylookingatasimpleexample.
Thisexampleisofasimpleapplicationwhichusestheacceptortoacceptconnections
andthencallbacktheserviceroutine.Whentheserviceroutineiscalledback,itjustlets
theuserknowthattheconnectionhasbeenestablishedsuccessfully.
Example1
#includeace/Reactor.h
#includeace/Svc_Handler.h
#includeace/Acceptor.h
#includeace/Synch.h

87

#includeace/SOCK_Acceptor.h
//CreateaServiceHandlerwhoseopen()methodwillbecalledback//automatically.This
classMUSTderivefromACE_Svc_Handlerwhichisan//interfaceandascanbeseenisa
templatecontainerclassitself.The//firstparametertoACE_Svc_Handleristhe
underlyingstreamthatit//mayuseforcommunication.SinceweareusingTCPsocketsthe
stream//isACE_SOCK_STREAM.Thesecondistheinternalsynchronization//mechanismit
coulduse.Sincewehaveasinglethreadedapplicationwe//passitanulllockwhich
willdonothing.
classMy_Svc_Handler:
publicACE_Svc_Handler<ACE_SOCK_STREAM,ACE_NULL_SYNCH>{
//theopenmethodwhichwillbecalledbackautomaticallyafterthe//connectionhasbeen
established.
public:
intopen(void*){
cout<<Connectionestablished<<endl;
}
};
//Createtheacceptorasdescribedabove.
typedefACE_Acceptor<My_Svc_Handler,ACE_SOCK_ACCEPTOR>MyAcceptor;
intmain(intargc,char*argv[]){
//createtheaddressonwhichwewishtoconnect.Theconstructortakes//theportnumber
onwhichtolistenandwillautomaticallytakethe//hostsIPaddressastheIPAddress
fortheaddrobject
ACE_INET_Addraddr(PORT_NUM);
//instantiatetheappropriateacceptorobjectwiththeaddressonwhich//wewishto
acceptandtheReactorinstancewewanttouse.Inthis//casewejustusetheglobal
ACE_Reactorsingleton.(Readmoreabout//thereactorinthepreviouschapter)
MyAcceptoracceptor(addr,ACE_Reactor::instance());
while(1)
//Startthereactorseventloop
ACE_Reactor::instance()>handle_events();
}

Intheaboveexample,wefirstcreateanendpointaddressonwhichwewishtoaccept.
SincewehavedecidedtouseTCP/IPastheunderlyingconnectionprotocol,wecreatean
ACE_INET_Addrasourendpointandpassittheportnumberwewantittolistenon.We
pass this address and an instance of the reactor singleton to the acceptor that we
instantiateafterthis.Thisacceptor,afterbeinginstantiated,willautomaticallyacceptany
connectionrequestsonPORT_NUMandcallbackMy_Svc_Handlersopen()methodafter
establishing connections for such requests. Notice that when we instantiated the
ACE_Acceptor factory, we passed it the concrete acceptor we wanted to use, i.e.
ACE_SOCK_ACCEPTOR, and the concrete service handler we wanted to use, i.e.
My_Svc_Handler.
88

Nowletstrysomethingabitmoreinteresting.Inthenextexample,wewillregisterour
servicehandlerbackwiththereactorafteritiscalledbackonconnectionestablishment.
Now, if any data comes on the newly created connection, then our service handling
routines handle_input() method would be called back automatically. Thus in this
example,weareusingthefeaturesofboththereactorandacceptortogether:
Example2
#includeace/Reactor.h
#includeace/Svc_Handler.h
#includeace/Acceptor.h
#includeace/Synch.h
#includeace/SOCK_Acceptor.h
#definePORT_NUM10101
#defineDATA_SIZE12
//forwarddeclaration
classMy_Svc_Handler;
//CreatetheAcceptorclass
typedefACE_Acceptor<My_Svc_Handler,ACE_SOCK_ACCEPTOR>
MyAcceptor;
//Createaservicehandlersimilartoasseeninexample1.Exceptthis//timeincludethe
handle_input()methodwhichwillbecalledback//automaticallybythereactorwhennew
dataarrivesonthenewly//establishedconnection
classMy_Svc_Handler:
publicACE_Svc_Handler<ACE_SOCK_STREAM,ACE_NULL_SYNCH>{
public:
My_Svc_Handler(){
data=newchar[DATA_SIZE];
}
intopen(void*){
cout<<Connectionestablished<<endl;
//Registertheservicehandlerwiththereactor
ACE_Reactor::instance()>register_handler(this,
ACE_Event_Handler::READ_MASK);
return0;
}
inthandle_input(ACE_HANDLE){
//Afterusingthepeer()methodofACE_Svc_Handlertoobtaina
//referencetotheunderlyingstreamoftheservicehandlerclass
//wecallrecv_n()onittoreadthedatawhichhasbeenreceived.
//Thisdataisstoredinthedataarrayandthenprintedout
peer().recv_n(data,DATA_SIZE);
ACE_OS::printf(<<%s\n,data);
//keepyourselfregisteredwiththereactor
return0;
}
private:

89

char*data;
};
intmain(intargc,char*argv[]){
ACE_INET_Addraddr(PORT_NUM);
//createtheacceptor
MyAcceptoracceptor(addr,//addresstoaccepton
ACE_Reactor::instance());//thereactortouse
while(1)
//Startthereactorseventloop
ACE_Reactor::instance()>handle_events();
}

Theonlydifferencebetweenthisexampleandthepreviousoneisthatweregisterthe
service handler with the reactor in the open() method of our service handler. We
consequentlyhavetowritea handle_input() methodwhichwillbecalledbackbythe
reactorwhendatacomesinontheconnection.Inthiscasewejustprintoutthedatawe
receive on the screen. The peer() method of the ACE_Svc_Handler class is a useful
methodwhichreturnsareferencetotheunderlyingpeerstream.Weusethe recv_n()
method of the underlying stream wrapper class to obtain the data received on the
connection.
Therealpowerofthispatternliesinthefactthattheunderlyingconnectionestablishment
mechanism is fully encapsulated in the concrete acceptor. This can very easily be
changed. In the next example, we change the underlying connection establishment
mechanismsothatitusesUNIXdomainsocketsinsteadofTCPsockets,aswewere
usingbefore.Theexample,againwithminimalchanges(underlined),isasfollows:
Example3
classMy_Svc_Handler:
publicACE_Svc_Handler<ACE_LSOCK_STREAM,ACE_NULL_SYNCH>{
public:
intopen(void*){
cout<<Connectionestablished<<endl;
ACE_Reactor::instance()
>register_handler(this,ACE_Event_Handler::READ_MASK);
}
inthandle_input(ACE_HANDLE){
char*data=newchar[DATA_SIZE];
peer().recv_n(data,DATA_SIZE);
ACE_OS::printf(<<%s\n,data);
return0;
}
};
typedefACE_Acceptor<My_Svc_Handler,ACE_LSOCK_ACCEPTOR>MyAcceptor;

90

intmain(intargc,char*argv[]){
ACE_UNIX_Addraddr(/tmp/addr.ace);
MyAcceptoracceptor(address,ACE_Reactor::instance());
while(1)/*Startthereactorseventloop*/
ACE_Reactor::instance()>handle_events();
}

The differences between example 2 and example 3 are underlined. As noted, the
differencesbetweenthetwoprogramsareveryminimal.Howevertheybothusevery
different connection establishment paradigms. Some of the connection establishment
mechanismsthatareavailableinACEarelistedinthetablebelow.
Type of
Acceptor

Address used

TCP stream
ACE_INET_Addr
Acceptor
UNIX domain
ACE_UNIX_Addr
local stream socket
acceptor
PIPES as the
ACE_SPIPE_Addr
underlying
communication
mechanism

Stream used

Concrete Acceptor

ACE_SOCK_STREAM

ACE_SOCK_ACCEPTOR

ACE_LSOCK_STREAM

ACE_LSOCK_ACCEPTOR

ACE_SPIPE_STREAM

ACE_SPIPE_ACCEPTOR

THE CONNECTOR
TheConnectorisverysimilartotheAcceptor.Itisalsoafactory,butinthiscaseitis
usedto actively connecttoaremotehost.Aftertheconnectionhasbeenestablished,it
willautomaticallycallbacktheopen()methodoftheappropriateservicehandlingobject.
TheconnectorisusuallyusedwhereyouwouldusetheBSDconnect()call.InACE,the
connector, justliketheacceptor, is implemented as atemplate container class called
ACE_Connector.Asmentionedearlier,ittakestwoparameters,thefirstbeingtheevent
handlerclasswhichistobecalledwhentheconnectionisestablishedandthesecond
beingaconcreteconnectorclass.
YouMUSTnotethattheunderlyingconcreteconnectorandthefactoryACE_Connector
are both very different things. The ACE_Connector factory USES the underlying
concreteconnectortoestablishtheconnection.TheACE_ConnectorfactorythenUSES
theappropriateeventorservicehandlingroutine(theonepassedinthroughitstemplate
argument)tohandlethenewconnectionaftertheconnectionhasbeenestablishedbythe
concreteconnector.Theconcreteconnectorscanbeusedwithoutthe ACE_Connector
factoryaswesawintheIPCchapter.TheACE_Connectorfactory,however,cannotbe
usedwithoutaconcrete connector class(since itisthis class whichactually handles
connectionestablishment).
AnexampleofinstantiatingtheACE_Connectorclassis:
91

typedefACE_Connector<My_Svc_Handler,ACE_SOCK_CONNECTOR>MyConnector;

The second parameter in this case is the concrete connector class


ACE_SOCK_CONNECTOR. Theconnector,liketheacceptorpattern,usesthereactor
internallytocallbacktheopen()methodoftheservicehandlerwhentheconnectionhas
been established. We can reuse the service handling routines we had written forthe
previousexampleswiththeconnector.
Anexampleusingtheconnectorshouldmakethisclearer.
Example4
typedefACE_Connector<My_Svc_Handler,ACE_SOCK_CONNECTOR>MyConnector;
intmain(intargc,char*argv[]){
ACE_INET_Addraddr(PORT_NO,HOSTNAME);
My_Svc_Handler*handler=newMy_Svc_Handler;
//Createtheconnector
MyConnectorconnector;
//Connectstoremotemachine
if(connector.connect(handler,addr)==1)
ACE_ERROR(LM_ERROR,%P|%t,%p,Connectionfailed);
//RegisterswiththeReactor
while(1)
ACE_Reactor::instance()>handle_events();
}

Intheaboveexample, PORT_NO and HOSTNAME arethemachine andportwewishto


activelyconnectto.Afterinstantiatingtheconnector,wecallitsconnectmethod,passing
ittheserviceroutinethatistobecalledbackwhentheconnectionisfullyestablished,
andtheaddressthatwewishtoconnectto.

USING THE ACCEPTOR AND CONNECTOR TOGETHER


TheAcceptorandConnectorpatternswill,ingeneral,beusedtogether.Inaclientserver
application,theserverwilltypicallycontaintheacceptor,whereasaclientwillcontain
theconnector.However,incertainapplications,boththeacceptorandconnectormaybe
usedtogether.Anexampleofsuchanapplicationisgivenbelow.Inthisexample,a
singlemessageis repeatedly senttothepeermachine, andatthesametime another
messageisreceivedfromtheremote.Sincetwofunctionsmustbeperformedatthesame
time,aneasysolutionistosendandreceivemessagesinseparatethreads.
Thisexamplecontainsbothanacceptorandaconnector.Theusercantakeargumentsat
thecommandpromptandtelltheapplicationwhetheritisgoingtoplayaserverorclient
role.Theapplicationwillthencallmain_accept()ormain_connect()asappropriate.
Example5

92

#includeace/Reactor.h
#includeace/Svc_Handler.h
#includeace/Acceptor.h
#includeace/Synch.h
#includeace/SOCK_Acceptor.h
#includeace/Thread.h
//AddourownReactorsingleton
typedefACE_Singleton<ACE_Reactor,ACE_Null_Mutex>Reactor;
//CreateanAcceptor
typedefACE_Acceptor<MyServiceHandler,ACE_SOCK_ACCEPTOR>Acceptor;
//CreateaConnector
typedefACE_Connector<MyServiceHandler,ACE_SOCK_CONNECTOR>Connector;
classMyServiceHandler:
publicACE_Svc_Handler<ACE_SOCK_STREAM,ACE_NULL_SYNCH>{
public:
//Usedbythetwothreadsgloballytodeterminetheirpeerstream
staticACE_SOCK_Stream*Peer;
//ThreadIDusedtoidentifythethreads
ACE_thread_tt_id;
intopen(void*){
cout<<Acceptor:receivednewconnection<<endl;
//Registerwiththereactortorememberthishandle
Reactor::instance()
>register_handler(this,ACE_Event_Handler::READ_MASK);
//Determinethepeerstreamandrecorditglobally
MyServiceHandler::Peer=&peer();
//Spawnnewthreadtosendstringeverysecond
ACE_Thread::spawn((ACE_THR_FUNC)send_data,0,THR_NEW_LWP,&t_id);
//keeptheservicehandlerregisteredbyreturning0tothe
//reactor
return0;
}
staticvoid*send_data(void*){
while(1){
cout<<>>HelloWorld<<endl;
Peer>send_n(HelloWorld,sizeof(HelloWorld));
//Gotosleepforasecondbeforesendingagain
ACE_OS::sleep(1);
}
return0;
}
inthandle_input(ACE_HANDLE){
char*data=newchar[12];
//Checkifpeerabortedtheconnection
if(Peer.recv_n(data,12)==0){

93

cout<<Peerprobablyabortedconnection);
ACE_Thread::cancel(t_id);//killsendingthread..
return1;//deregisterfromtheReactor.
}
//Showwhatyougot..
cout<<<<%s\n,data<<endl;
//keepyourselfregistered
return0;
}
};
//Globalstreamidentifierusedbyboththreads
ACE_SOCK_Stream*MyServiceHandler::Peer=0;

voidmain_accept(){
ACE_INET_Addraddr(PORT_NO);
Acceptormyacceptor(addr,Reactor::instance());
while(1)
Reactor::instance()>handle_events();
return0;
}
voidmain_connect(){
ACE_INET_Addraddr(PORT_NO,HOSTNAME);
Connectormyconnector;
myconnector.connect(my_svc_handler,addr);
while(1)
Reactor::instance()>handle_events();
}

intmain(intargc,char*argv[]){
//UseACE_Get_Opttoparseandobtainargumentsandthencallthe
//appropriatefunctionforacceptorconnect.
...
}

Thisisasimpleexamplewhichillustrateshowtheacceptorandconnectorpatternscanbe
usedincombinationtoproduceaservicehandlingroutinewhichiscompletelydecoupled
fromtheunderlyingnetworkestablishmentmethod.Theaboveexamplecanbeeasily
changedtouseanyotherunderlyingnetworkestablishmentprotocol,bychangingthe
appropriatetemplateparametersfortheconcreteconnectorandacceptor.

94

Advanced Sections
The following sections give a more detailed explanation of how the Acceptor and
Connector patterns actually work. This is required if you wish to tune the service
handlingandconnectionestablishmentpolicies.Thisincludestuningthecreationand
concurrencystrategyofyourservicehandlingroutineandtheconnectionestablishment
strategythattheunderlyingconcreteconnectorwilluse.Inaddition,thereisasection
whichexplainshowyoucanusetheadvancedfeaturesyouautomaticallygetbyusingthe
ACE_Svc_Handler classes. Lastly, we show how you can use a simple lightweight
ACE_Event_Handlerwiththeacceptorandconnectorpatterns.

THE ACE_SVC_HANDLER CLASS


TheACE_Svc_Handlerclass,asmentionedabove,isbasedbothonACE_Task,whichis
apartoftheASXStreamsframework,andonthe ACE_Event_Handler interfaceclass.
ThusanACE_Svc_HandlerisbothaTaskandanEventHandler.Herewewillgiveyoua
briefintroductiontoACE_TaskandwhatyoucandowithACE_Svc_Handler.
ACE_Task
ACE_Task hasbeendesignedtobeusedwiththeASXStreamsframework,whichis
basedonthestreamsfacilityinUNIXSystemV.ASXisalsoverysimilarindesignto
theXkernelprotocoltoolsbuiltbyLarryPeterson[VIII].
ThebasicideainASXisthatanincomingmessageisassignedtoastream.Thisstreamis
constructedoutofseveral modules. Eachmoduleperformssomefixedfunctiononthe
incomingmessage,whichisthenpassedontothenextmoduleforfurtherprocessing
untilitreachestheendofthestream.Theactualprocessinginthemoduleisdoneby
tasks.Thereareusuallytwotaskstoeachmodule,oneforprocessingincomingmessages
andoneforprocessingoutgoingmessages.Thiskindofastructureisveryusefulwhen
constructing protocol stacks. Since each module used has a fixed simple interface,
modulescanbecreatedandeasilyreusedacrossdifferentapplications.Forexample,
consideranapplicationthatprocessesincomingmessagesfromthedatalinklayer.The
programmerwouldconstructseveralmodules,eachdealingwithadifferentlevelofthe
protocolprocessing.Thus,hewouldconstructaseparatemoduletodonetworklayer
processing,anotherfortransportlayerprocessingandstillanotherforpresentationlayer
processing.Afterconstructingthesemodulestheycanbechainedtogetherintoastream
(withthehelpofASX)andused.Atalatertime,ifanew(andperhapsbetter)transport
module is created, then the earlier transport module can be replaced in the stream
withoutaffectinganythingelseintheprogram.Notethatthemoduleislikeacontainer
whichcontainstasks.Thetasksaretheactualprocessingelements.Amodulemayneed
tohavetwotasks,asintheexampleabove,ormayjustneedone.ACE_Task,asyoumay
95

haveguessed,istheimplementationoftheprocessingelementsinthemoduleswhichare
calledtasks.
An Architecture: Communicating Tasks
EachACE_Taskhasaninternalmessagequeuethatisitsmeansofcommunicatingwith
othertasks,modulesortheoutsideworld.IfoneACE_Taskwishestosendamessageto
anothertask,itwillenqueuethemessageonthedestinationtasksmessagequeue.Once
thetaskreceivesthemessage,itwillimmediatelybeginprocessingit.
Every ACE_Task can run as zero or more threads. Messages can be enqueued and
dequeuedbymultiplethreadsonanACE_Tasksmessagequeuewithouttheprogrammer
worryingaboutcorruptinganyofthedatastructures.Thustasksmaybeusedasthe
fundamentalarchitecturalcomponentofasystemofcooperatingthreads.Eachthreadof
controlcanbeencapsulatedinanACE_Task,whichinteractswithothertasksbysending
messagestotheirmessagequeues,whichtheyprocessandthenrespondto.
Theonlyproblemwiththiskindofarchitectureisthattaskscanonlycommunicatewith
each other through their message queues within the same process. The
ACE_Svc_Handlersolvesthisproblem.ACE_Svc_HandlerinheritsfrombothACE_Task
and ACE_Event_Handler,andaddsaprivatedatastream.Thiscombination makes it
possibleforanACE_Svc_Handler objecttoactasataskthathastheabilitytoreactto
eventsandtosendandreceivedatabetweenremotetasksonremotehosts.
ACE_Task hasbeenimplementedasatemplatecontainer,whichisinstantiatedwitha
lockingmechanism,thelockbeingusedtoinsuretheintegrityoftheinternalmessage
queueinamultithreadedenvironment.Asmentionedearlier,ACE_Svc_Handlerisalso
a template container which is passed not only the locking mechanism, but also the
underlyingdatastreamthatitwilluseforcommunicationtoremotetasks.
Creating an ACE_ Svc_Handler
The ACE_Svc_Handler templateisinstantiatedtocreatethedesiredservicehandlerby
passinginthelockingmechanismandtheunderlyingstream.Ifnolockistobeused,as
wouldbedoneiftheapplicationwasonlysinglethreaded,itcanbeinstantiatedwith
ACE_NULL_SYNCH,as wedid above. However, if we intend to use it in amulti
threadedapplication(whichisthecommoncase),itwouldbedoneas:
classMySvcHandler:
publicACE_Svc_Handler<ACE_SOCK_STREAM,ACE_MT_SYNCH>{
...
}

96

Creating multiple threads in the Service Handler


InExample5above,wecreatedaseparatethreadtosenddatatotheremotepeerusing
the ACE_Thread wrapper class and its static spawn() method. When we did this,
however,wehadtodefinethe send_data() method,whichwaswrittenatfilescope
usingtheC++staticspecifier.Theconsequenceofthis,ofcourse,wasthatwecouldnt
accessanydatamembersoftheactualobjectwehadinstantiated.Inotherwords,we
wereforcedtomakethesend_data()memberfunctionclasswidewhenthiswasNOT
what we wanted to do. The only reason this was done was because
ACE_Thread::spawn() canonlyuseastaticmemberfunctionastheentrypointforthe
threaditcreates.Anotheradversesideaffectwasthatareferencetothepeerstreamhad
tobemadestaticalso.Inshort,this wasntthebestwaythis codecouldhavebeen
written.
ACE_Task providesanicemechanismtoavoidthisproblem.Each ACE_Task hasan
activate() methodwhichcanbecalledtocreatethreadsforthe ACE_Task.Theentry
pointofthecreatedthreadwillbeinthenonstaticmemberfunctionsvc().Sincesvc()is
anonstaticmemberfunction,itcancallanyobjectinstancespecificdataormember
functions.ACEhidesallthenittygrittyofhowthisisdonefromtheprogrammer.The
activate()methodisveryversatile.Itallowstheprogrammertocreatemultiplethreads,
allofwhichusethesvc()methodastheirentrypoint.Threadpriorities,handles,names,
etc.canalsobeset.Themethodprototypeforactivateis:
//=Activeobjectactivationmethod.
virtualintactivate(longflags=THR_NEW_LWP,
intn_threads=1,
intforce_active=0,
longpriority=ACE_DEFAULT_THREAD_PRIORITY,
intgrp_id=1,
ACE_Task_Base*task=0,
ACE_hthread_tthread_handles[]=0,
void*stack[]=0,
size_tstack_size[]=0,
ACE_thread_tthread_names[]=0);

Thefirstparameter,flags,describesdesiredpropertiesofthethreadswhicharetobe
created.Thesearedescribedindetailonthechapteronthreads.Thepossibleflagshere
are:
THR_CANCEL_DISABLE,THR_CANCEL_ENABLE,THR_CANCEL_DEFERRED,
THR_CANCEL_ASYNCHRONOUS,THR_BOUND,THR_NEW_LWP,THR_DETACHED,
THR_SUSPENDED,THR_DAEMON,THR_JOINABLE,THR_SCHED_FIFO,
THR_SCHED_RR,THR_SCHED_DEFAULT

Thesecondparameter,n_threads,specifiesthenumberofthreadstobecreated.Thethird
parameter,force_active,isusedtospecifywhethernewthreadsshouldbecreated,evenif
the activate() methodhasalreadybeencalledpreviously,andthusthetaskorservice
handlerisalreadyrunningmultiplethreads.Ifthisissettofalse(0),thenifactivate()is
97

calledagain,itwillresultinthefailurecodebeingsetandnofurtherthreadswillbe
spawned.
Thefourthparameterisusedtosetthepriorityoftherunningthreads.Bydefault,orif
the priority is set to ACE_DEFAULT_THREAD_PRIORITY, an appropriate priority
valueforthegivenschedulingpolicy(specifiedinflagse.g.,THR_SCHED_DEFAULT)
is used. This value is calculated dynamically, andis the median value between the
minimumandmaximumpriorityvaluesforthegivenpolicy.Ifanexplicitvalueisgiven,
itisused.NotethatactualpriorityvaluesareEXTREMELYimplementationdependent,
andareprobablybestavoided.Morecanbereadonprioritiesofthreadsonthechapteron
threads.
ThreadHandles,Threadnamesandstackspacesforthethreadstobecreatedcanbe
passedin,tobeusedbythethreadcreationcalls.Ifthesearenulltheyarenotused.
Howeverifmultiplethreadsarecreatedusingactivate,itwillbenecessarytopasseither
namesorhandlesforthethreadsbeforetheycanbeusedeffectively.
Anexamplewillhelpinfurtherunderstandinghowtheactivatemethodmaybeused:
Example6
#includeace/Reactor.h
#includeace/Svc_Handler.h
#includeace/Acceptor.h
#includeace/Synch.h
#includeace/SOCK_Acceptor.h
classMyServiceHandler;//forwarddeclaration
typedefACE_Singleton<ACE_Reactor,ACE_Null_Mutex>Reactor;
typedefACE_Acceptor<MyServiceHandler,ACE_SOCK_ACCEPTOR>Acceptor;
classMyServiceHandler:
publicACE_Svc_Handler<ACE_SOCK_STREAM,ACE_MT_SYNCH>{
//Thetwothreadnamesarekepthere
ACE_thread_tthread_names[2];
public:
intopen(void*){
ACE_DEBUG((LM_DEBUG,Acceptor:receivednewconnection\n));
//Registerwiththereactortorememberthishandler..
Reactor::instance()
>register_handler(this,ACE_Event_Handler::READ_MASK);
ACE_DEBUG((LM_DEBUG,Acceptor:ThreadID:(%t)open\n));
//Createtwonewthreadstocreateandsendmessagestothe
//remotemachine.
activate(THR_NEW_LWP,
2,//2newthreads
0,//forceactivefalse,ifalreadycreateddonttryagain.
ACE_DEFAULT_THREAD_PRIORITY,//Usedefaultthreadpriority
1,

98

this,//WhichACE_Taskobjecttocreate?Inthiscasethisone.
0,//dontcareaboutthreadhandlesused
0,//dontcareaboutwherestacksarecreated
0,//dontcareaboutstacksizes
thread_names);//keepidentifiersinthread_names
//keeptheservicehandlerregisteredwiththeacceptor.
return0;
}
voidsend_message1(void){
//Sendmessagetype1
ACE_DEBUG((LM_DEBUG,(%t)Sendingmessage::>>));
//Sendthedatatotheremotepeer
ACE_DEBUG((LM_DEBUG,Sentmessage1));
peer().send_n(Message1,LENGTH_MSG_1);
}//endsend_message1
intsend_message2(void){
//Sendmessagetype1
ACE_DEBUG((LM_DEBUG,(%t)Sendingmessage::>>));
//Sendthedatatotheremotepeer
ACE_DEBUG((LM_DEBUG,SentMessage2));
peer().send_n(Message2,LENGTH_MSG_2);
}//endsend_message_2
intsvc(void){
ACE_DEBUG((LM_DEBUG,(%t)Svcthread\n));
if(ACE_Thread::self()==thread_names[0])
while(1)send_message1();//sendmessage1sforever
else
while(1)send_message2();//sendmessage2sforever
return0;//keepthecompilerhappy.
}
inthandle_input(ACE_HANDLE){
ACE_DEBUG((LM_DEBUG,(%t)handle_input::));
char*data=newchar[13];
//Checkifpeerabortedtheconnection
if(peer().recv_n(data,12)==0){
printf(Peerprobablyabortedconnection);
return1;//deregisterfromtheReactor.
}
//Showwhatyougot..
ACE_OS::printf(<<%s\n,data);
//keepyourselfregistered
return0;
}

99

};
intmain(intargc,char*argv[]){
ACE_INET_Addraddr(10101);
ACE_DEBUG((LM_DEBUG,Thread:(%t)main));
//Preparetoacceptconnections
Acceptormyacceptor(addr,Reactor::instance());
//waitforsomethingtohappen.
while(1)
Reactor::instance()>handle_events();
return0;
}

Inthisexample,activate()iscalledaftertheservicehandlerisregisteredwiththereactor
in its open() method. It is used to create 2 threads. The names of the threads are
rememberedsothatwhentheycallthesvc()routine,wecandistinguishbetweenthem.
Eachthreadsendsadifferenttypeofmessagetotheremotepeer.Noticethatinthiscase
thethreadcreationistotallytransparent.Inaddition,sincetheentrypointisanormal
nonstatic member function, it is used without any ugly changes to remember data
members,suchasthepeerstream.Wecansimplycallthememberfunction peer() to
obtaintheunderlyingstreamwheneverweneedit.
Using the message queue facilities in the Service Handler
Asmentionedbefore,the ACE_Svc_Handler classhasabuiltinmessagequeue.This
message queue is used as the primary communication interface between an
ACE_Svc_Handlerandtheoutsideworld.Messagesthatothertaskswishtosendtothe
service handler are enqueued into its message queue. These messages may then be
processedinaseparatethread(createdbycallingthe activate() method).Yetanother
threadmaythentaketheprocessedmessageandsenditacrossthenetworktoadifferent
remotedestination(quitepossiblytoanotherACE_Svc_Handler).
As mentioned earlier, in this multithreaded scenario the ACE_Svc_Handler will
automaticallyensurethattheintegrityofthemessagequeueismaintainedwiththeuseof
locks.Thelockusedwillbethesamelockwhichwaspassedwhentheconcreteservice
handlerwascreatedbyinstantiatingthe ACE_Svc_Handler templateclass.Thereason
that the locks are passed in this way is so that the programmer may tune his
application.Differentlockingmechanismsondifferentplatformshavedifferentamounts
ofoverhead. Ifrequired, theprogrammer maycreate his ownoptimized lock, which
obeystheACEinterfaceforalockandusethislockwiththeservicehandler.Thisisjust
anotherexampleofthekindofflexibility thattheprogrammercanachieve byusing
ACE.TheimportantthingthattheprogrammerMUSTbeawareofisthatadditional
threadsintheservicehandlingroutinesWILLcausesignificantlockingoverhead.To
keep this overhead down to a minimum, the programmer must design his program
100

carefully,ensuringthatsuchoverheadisminimized.Inparticular,theexampledescribed
aboveprobablywillhaveexcessiveoverheadandmaybeinfeasibleinmostsituations.
ACE_Task andthus ACE_Svc_Handler (astheservice handlerisatypeoftask)has
severalmethodswhichcanbeusedtoset,manipulate,enqueueanddequeuefromthe
underlyingqueue.Wewilldiscussonlyafewofthesemethodshere.Sinceapointerto
the message queue itself can be obtained in the service handler (by using the
msg_queue() method), all public methods on the underlying queue (i.e.
ACE_Message_Queue) may also be invoked directly. (For further details on all the
methodsthemessagequeueprovides,pleaseseethenextchapteronmessagequeues.)
Theunderlyingmessagequeuefortheservicehandler,asmentionedabove,isaninstance
ofACE_Message_Queue,whichiscreatedautomaticallybytheservicehandler.Inmost
casesitisnotnecessarytocalltheunderlyingmethodsofACE_Message_Queue,asmost
ofthemhavewrappersinthe ACE_Svc_Handler class.An ACE_Message_Queue isa
queue which enqueues and dequeues ACE_Message_Blocks. Each of these
ACE_Message_BlockscontainsapointertoareferencecountedACE_Data_Block,which
in turn points to the actual data stored in the block. (See next chapter on Message
Queues).ThisallowsforeasysharingofthedatabetweenACE_Message_Blocks.
Themainpurposeof ACE_Message_Blocks istoallowefficientmanipulationofdata
withoutmuchcopyingoverhead.Areadpointerandwritepointerareprovidedwitheach
messageblock.Thereadpointerwillbeincrementedforwardinthedatablockwhenever
wereadfromtheblock.Similarly,thewritepointermovesforwardwhenwewriteinto
theblock,muchlikeitwouldinastreamtypesystem.AnACE_Message_Blockscanbe
passedanallocatorthroughitsconstructorthatitthenusesforallocatingmemory(See
chapteronMemoryManagementformoreonAllocators).Forexample,itmayusethe
ACE_Cached_Allocation_Strategy, which preallocates memory and then will return
pointersinthememorypoolinsteadofactuallyallocatingmemoryontheheapwhenitis
required.Suchfunctionalityisusefulwhenpredictableperformanceisrequired,asin
realtimesystems.
Thefollowingexamplewillshowhowtousesomeofthemessagequeuesfunctionality.
Example7
#includeace/Reactor.h
#includeace/Svc_Handler.h
#includeace/Acceptor.h
#includeace/Synch.h
#includeace/SOCK_Acceptor.h
#includeace/Thread.h
#defineNETWORK_SPEED3
classMyServiceHandler;//forwarddeclaration
typedefACE_Singleton<ACE_Reactor,ACE_Null_Mutex>Reactor;
typedefACE_Acceptor<MyServiceHandler,ACE_SOCK_ACCEPTOR>Acceptor;
classMyServiceHandler:
publicACE_Svc_Handler<ACE_SOCK_STREAM,ACE_MT_SYNCH>{
//Themessagesenderandcreatorthreadsarehandledhere.
ACE_thread_tthread_names[2];

101

public:
intopen(void*){
ACE_DEBUG((LM_DEBUG,Acceptor:receivednewconnection\n));
//Registerwiththereactortorememberthishandler..
Reactor::instance()
>register_handler(this,ACE_Event_Handler::READ_MASK);
ACE_DEBUG((LM_DEBUG,Acceptor:ThreadID:(%t)open\n));
//Createtwonewthreadstocreateandsendmessagestothe
//remotemachine.
activate(THR_NEW_LWP,
2,//2newthreads
0,
ACE_DEFAULT_THREAD_PRIORITY,
1,
this,
0,
0,
0,
thread_names);//identifiersinthread_handles
//keeptheservicehandlerregisteredwiththeacceptor.
return0;
}
voidsend_message(void){
//Dequeuethemessageandsenditoff
ACE_DEBUG((LM_DEBUG,(%t)Sendingmessage::>>));
//dequeuethemessagefromthemessagequeue
ACE_Message_Block*mb;
ACE_ASSERT(this>getq(mb)!=1);
intlength=mb>length();
char*data=mb>rd_ptr();

//Sendthedatatotheremotepeer
ACE_DEBUG((LM_DEBUG,%s\n,data,length));
peer().send_n(data,length);
//SimulateverySLOWnetwork.
ACE_OS::sleep(NETWORK_SPEED);
//releasethemessageblock
mb>release();
}//endsend_message
intconstruct_message(void){
//Averyfastmessagecreationalgorithm
//wouldleadtotheneedforqueuingmessages..
//here.Thesemessagesarecreatedandthensent

102

//usingtheSLOWsend_message()routinewhichis
//runninginadifferentthreadsothatthemessage
//constructionthreadisntblocked.
ACE_DEBUG((LM_DEBUG,(%t)Constructingmessage::>>));

//Createanewmessagetosend
ACE_Message_Block*mb;
char*data=HelloConnector;
ACE_NEW_RETURN(mb,ACE_Message_Block(16,//Message16byteslong
ACE_Message_Block::MB_DATA,//Setheadertodata
0,//Nocontinuations.
data//Thedatawewanttosend
),0);
mb>wr_ptr(16);//Setthewritepointer.
//Enqueuethemessageintothemessagequeue
//weCOULDhavedoneatimedwaitforenqueuingincase
//someoneelseholdsthelocktothequeuesoitdoesntblock
//forever..
ACE_ASSERT(this>putq(mb)!=1);
ACE_DEBUG((LM_DEBUG,Enqueuedmsgsuccessfully\n));
}
intsvc(void){
ACE_DEBUG((LM_DEBUG,(%t)Svcthread\n));
//callthemessagecreatorthread
if(ACE_Thread::self()==thread_names[0])
while(1)construct_message();//createmessagesforever
else
while(1)send_message();//sendmessagesforever
return0;//keepthecompilerhappy.
}
inthandle_input(ACE_HANDLE){
ACE_DEBUG((LM_DEBUG,(%t)handle_input::));
char*data=newchar[13];
//Checkifpeerabortedtheconnection
if(peer().recv_n(data,12)==0){
printf(Peerprobablyabortedconnection);
return1;//deregisterfromtheReactor.
}
//Showwhatyougot..
ACE_OS::printf(<<%s\n,data);
//keepyourselfregistered
return0;
}
};

103

intmain(intargc,char*argv[]){
ACE_INET_Addraddr(10101);
ACE_DEBUG((LM_DEBUG,Thread:(%t)main));
//Preparetoacceptconnections
Acceptormyacceptor(addr,Reactor::instance());
//waitforsomethingtohappen.
while(1)
Reactor::instance()>handle_events();
return0;
}

Thisexampleillustratestheuseoftheputq()andgetq()methodstoenqueueanddequeue
messageblocksontothequeue.Italsoillustrateshowtocreateamessageblockandthen
howtosetitswritepointerandreadfromitsreadpointer.Notethattheactualdatainside
themessageblockstartsatthereadpointerofthemessageblock.Thelength()member
function ofthemessageblockreturnsthelengthoftheunderlyingdatastoredinthe
messageblockanddoesnotincludethepartsofACE_Message_Blockwhichareusedfor
bookkeepingpurposes.Inaddition,wealsoshowhowtoreleasethemessageblock
(mb)usingtherelease()method.
Toreadmoreabouthowtousemessageblocks,datablocksorthemessagequeue,please
readthesectionsinthismanualonmessagequeuesandtheASXframework.Also,see
therelevantsectionsinthereferencemanual.

HOW THE ACCEPTOR AND CONNECTOR PATTERNS WORK


Boththeacceptorandconnectorfactories,i.e.ACE_ConnectorandACE_Acceptor,have
averysimilaroperationalstructure.Theirworkingscanberoughlydividedintothree
stages.

Endpoint or connection initialization phase

Service Initialization phase

Service Processing phase

Endpoint or connection initialization phase


Inthecaseoftheacceptor,theapplicationlevelprogrammermayeithercalltheopen()
methodofthefactory,ACE_Acceptor,oritsdefaultconstructor(whichinfactWILLcall
theopen()method)tostarttopassivelylistentoconnections.Whentheopen()methodis
called on the acceptor factory, it first instantiates the Reactor singleton if it has not
already been instantiated. It then proceeds to call the underlying concrete acceptors
open()method.Theconcreteacceptorwillthengothroughthenecessaryinitializationit
needs to perform to listen for incoming connections. For example, in the case of
ACE_SOCK_Acceptor,itwillopenasocketandbindthesockettotheportandaddress
104

onwhichtheuserwishestolistenfornewconnections.Afterbindingtheport,itwill
proceedtoissuethelistencall.Theopenmethodthenregisterstheacceptorfactorywith
theReactor.Thuswhenanyincomingconnectionrequestsarereceived,thereactorwill
automaticallycallbacktheAcceptorfactories handle_input() method.Noticethatthe
Acceptor factory itself derives from the ACE_Event_Handler hierarchy for this very
reason, so that it can respond to ACCEPT events and can be called back from the
Reactor.
Inthecaseoftheconnector,theapplicationprogrammerwillcalltheconnect()methodor
theconnect_n()methodontheconnectorfactorytoinitiateaconnectiontothepeer.Both
thesemethodstake,amongotheroptions,theremoteaddresstowhichwewishtoconnect
andwhetherwewanttosynchronouslyorasynchronouslycompletetheconnection.We
wouldinitiateNUMBER_CONNconnectionseithersynchronouslyorasynchronouslyas:
//Synchronous
OurConnector.connect_n(NUMBER_CONN,ArrayofMySvcHandlers,Remote_Addr,0,
ACE_Synch_Options::synch);
//Asynchronous
OurConnector.connect_n(NUMBER_CONN,ArrayofMySvcHandlers,Remote_Addr,0,
ACE_Synch_Options::asynch);

Iftheconnectcallisissuedtobeasynchronous,thenthe ACE_Connector willregister


itselfwiththereactorawaitingtheconnectiontobeestablished(againACE_Connector
alsoisfromtheACE_Event_Handlerhierarchy).Oncetheconnectionisestablished,the
reactor will then automatically call back the connector. In the synchronous case,
however, the connect() call will block until either the connection is established or a
timeout value expires. The time out value can be specified by changing certain
ACE_Synch_Options.Fordetailspleaseseethereferencemanual.

Service Initialization Phase for the Acceptor


When an incoming request comes in on the specified address and port, the reactor
automaticallycallsbacktheACE_Acceptorfactory'shandle_input()method.
ThismethodisaTemplateMethod.Atemplatemethodisusedtodefinetheorderof
thestepsofanalgorithm,butallowvariationinhowcertainstepsareperformed.This
variation is achieved by allowing subclasses to define the implementation of these
methods.(FormoreontheTemplateMethodseethereferenceonDesignPatterns).
InthiscasetheTemplatemethoddefinesthealgorithmas

make_svc_handler(): Creates the Service Handler.


accept_svc_handler(): Accept the connection into the created Service Handler
from the previous step.
activate_svc_handler(): Start the new service handler up.

Eachofthesemethodscanberewrittentoprovideflexibilityinhowtheseoperationsare
actuallyperformed.

105

Thusthehandle_input()willfirstcallthemake_svc_handler()method,whichcreatesthe
servicehandleroftheappropriatetype(thetypeoftheservicehandlerispassedinbythe
applicationprogrammerwhenthe ACE_Acceptor templateisinstantiated,aswesawin
theexamplesabove).Inthedefaultcase,themake_svc_handler()methodjustinstantiates
thecorrectservicehandler.However,themake_svc_handler()isabridgemethodthat
canbeoverloadedtoprovidemorecomplexfunctionality.(Abridgeisadesignpattern
which decouples the interface of a hierarchy of classes from the implementation
hierarchyreadmoreaboutthisinthereferenceonDesignPatterns).Forexample,the
servicehandlecanbecreatedsothatitisaprocesswideorthreadspecificsingleton,orit
canbedynamicallylinkedinfromalibrary,loadedfromdiskorevencreatedbydoing
somethingascomplicatedasfindingandobtainingtheservicehandlerinadatabaseand
thenbringingitintomemory.
Aftertheservicehandlerhasbeencreated,the handle_input() methodproceedstocall
accept_svc_handler().Thismethodacceptstheconnectionintotheservicehandler.
Thedefaultcaseistocalltheunderlyingconcreteacceptor'saccept()method.Inthecase
thatACE_SOCK_Acceptorisbeingusedastheconcreteacceptor,itproceedstocallthe
BSDaccept()routinewhichestablishestheconnection(acceptstheconnection).After
theconnectionisestablished,thehandletotheconnectionisautomaticallysetinsidethe
servicehandlerwhichwaspreviouslycreated bycalling make_svc_handler() (accepts
into the service handler). This method can also be overloaded to provide more
complicatedfunctionality.Forexample,insteadofactuallycreatinganewconnection,an
olderconnectioncouldberecycled.Thiswillbediscussedinmoredetailwhenwe
showvariousdifferentacceptingandconnectingstrategies.

Service Initialization Phase for the Connector


Theconnect()method,whichisissuedbytheapplication,issimilartothehandle_input()
methodintheAcceptorfactory,i.e.itisaTemplateMethod.
Inthiscase,theTemplatemethod connect() definesthefollowingsteps,whichcanbe
redefined.

make_svc_handler(): Creates the Service Handler.


connect_svc_handler(): Accept the connection into the created Service
Handler from the previous step.
activate_svc_handler(): Start the new service handler up.

Eachofthesemethodscanberewrittentoprovideflexibilityinhowtheseoperationsare
actuallyperformed.
Thusaftertheconnect()callisissuedbytheapplication,theconnectorfactoryproceeds
toinstantiatethecorrectservicehandlerbycallingthemake_svc_handler()call,exactly
as it does in the case of the acceptor. The default behavior is to just instantiate the
appropriateclass.Thiscanbeoverloadedexactlyinthesamemanneraswasdiscussed
fortheAcceptor.Thereasonsfordoingsuchanoverloadwouldprobablyverysimilarto
theonesmentionedabove.
106

Aftertheservicehandlerhasbeencreated,theconnect()calldeterminesiftheconnectis
to be asynchronous or synchronous. If it is asynchronous, it registers itself with the
reactor before continuing on to the next step. It then proceeds to call the
connect_svc_handler() method.Thismethod,bydefault,callstheunderlyingconcrete
connector's connect() method.Inthecaseof ACE_SOCK_Connector thiswouldmean
issuingtheBSDconnect()callwiththecorrectoptionsforblockingornonblockingI/O.
If the connection was specified to be synchronous, this call will block until the
connectionhasbeenestablished.Inthiscase,whentheconnectionhasbeenestablished,
itwillproceedtosetthehandleintheservicehandlersothatitcancommunicatewiththe
peeritisnowconnectedto(thisisthehandlestoredinthestreamwhichisobtainedby
callingthepeer()methodinsidetheservicehandler,seeexamplesabove).Aftersetting
thehandleintheservicehandler,theconnectorpatternwouldthencontinuetothefinal
stageofserviceprocessing.
However, if the connection is specified to be asynchronous, the call to
connect_svc_handler() willreturn immediately afterissuinganonblocking connect()
calltotheunderlyingconcreteconnector.Inthecaseof ACE_SOCK_Connector, this
would mean a nonblocking BSD connect() call. When the connection is in fact
established at a later time, the reactor will call back the ACE_Connector factory's
handle_output()method,whichwouldsetthenewhandleintheservicehandlerthatwas
createdwiththe make_svc_handler() method.Thefactorywouldthencontinuetothe
nextstageofserviceprocessing.
Aswasinthecaseofthe accept_svc_handler(), connect_svc_handler() isabridge
methodthatcanbeoverloadedtoprovidevaryingfunctionality.

Service Processing
Oncetheservicehandlerhasbeencreated,theconnectionhasbeenestablished,andthe
handlehasbeensetintheservicehandler,thehandle_input()methodofACE_Acceptor
(or handle_output() or connect_svc_handler() inthecaseof ACE_Connector)willcall
theactivate_svc_handler()method.Thismethodwillthenproceedtoactivatetheservice
handler,i.e.towillstartitrunning.Thedefaultmethodisjusttocalltheopen()method
astheentrypointintotheservicehandler.Aswesawintheexamplesabove,theopen()
methodwasindeedthefirstmethodwhichwascalledwhentheservicehandlerstarted
running.Itwasherethatwecalledthe activate() methodtocreatemultiplethreadsof
control and also registered the service handler with the reactor so that it was
automaticallycalledbackwhennewdataarrivedontheconnection.Thismethodisalsoa
bridgemethodandcanbeoverloadedtoprovidemorecomplicatedfunctionality.In
particular, this overloaded method may provide for a more complicated concurrency
strategy,suchasrunningtheservicehandlerinadifferentprocess.

107

TUNING THE ACCEPTOR AND CONNECTOR POLICIES


As mentioned above, the acceptor and connector can be easily tuned because ofthe
bridgemethodswhichcanbeoverloaded.Thebridgemethodsallowtuningof:

Creation Strategy for the Service Handler: By overloading the


make_svc_handler() method in either the acceptor or connector. For example, this
could mean re-using an existing service handler or using some complicated
method to obtain the service handler, as was discussed above.
Connection Strategy: The connection creation strategy can be changed by
overloading the connect_svc_handler() or accept_svc_handler() methods.
Concurrency Strategy for the Service Handler: The concurrency strategy
for the service handler can be changed by overloading the activate_svc_handler()
method. For example, the service handler can be created in a different process.

Asnotedabove,thetuningisdonebyoverloadingbridgemethodsintheACE_Acceptor
orACE_Connectorclasses.ACEhasbeendesignedsothatsuchoverloadingandtuning
canbedoneveryeasily.

The ACE_Strategy_Connector and ACE_Strategy_Acceptor


classes
Tofacilitatethetuningoftheacceptorandconnectorpatternsalongthelinesmentioned
above,ACEprovidestwospecialtunableacceptorandconnectorfactoriesthatarevery
similarto ACE_Acceptor and ACE_Connector.Theseare ACE_Strategy_Acceptor and
ACE_Strategy_Connector.TheseclassesmakeuseoftheStrategyPattern.
Thestrategypatternisusedtodecouplealgorithmicbehaviorfromtheinterfaceofa
class.Thebasicideaistoallowtheunderlyingalgorithmsofaclass(callittheContext
Class)tovaryindependentlyfromtheclientsthatusetheclass.Thisisdonewiththehelp
of concrete strategy classes. Concrete strategy classes encapsulate an algorithm or
methodtoperformanoperation.Theseconcretestrategyclassesarethenusedbythe
context class to perform operations (The context class delegates the work to the
concretestrategyclass).Sincethecontextclassdoesntperformanyoftheoperations
directly,itdoesnothavetobemodifiedwhenfunctionalityistobechanged.Theonly
modificationtothecontextclassisthatadifferentconcretestrategyclasswillbeusedto
performthenowchangedoperation.(ToreadmoreabouttheStrategyPatternreadthe
appendixonDesignPatterns).
InthecaseofACE,the ACE_Strategy_Connector andthe ACE_Strategy_Acceptor are
StrategyPatternclasseswhichuseseveralconcretestrategyclassestovarythealgorithms
for creatingservicehandlers,establishingconnectionsandforsettingtheconcurrency
methodforservicehandlers.Asyoumayhaveguessed,the ACE_Strategy_Connector
andACE_Strategy_Acceptorexploitthetunabilityprovidedbythebridgemethodswhich
werementionedabove.
108

Using the Strategy Acceptor and Connector


SeveralconcretestrategyclassesarealreadyavailableinACEthatcanbeusedtotune
theStrategyAcceptorandConnector.TheyarepassedinasparameterstoeitherStrategy
AcceptororConnectorwhentheclassisinstantiated.Thefollowingtableshowssomeof
theclassesthatcanbeusedtotunetheStrategyAcceptorandConnectorclasses.

To modify the

Concrete Strategy Class

Creation Strategy
ACE_NOOP_Creation_
(overrides make_svc_handler()) Strategy

Description
This concrete strategy does NOT instantiate the
service handler and is just a no-op.

ACE_Singleton_
Strategy
ACE_DLL_Strategy

Ensures that the service handler that is created is


a singleton. That is, all connections will effectively
use the same service handling routine.
Creates a service handler by dynamically linking it
from a dynamic link library.
Checks to see if there is already a service handler
connected to a particular remote address which
isnt being used. If there is such a service handler,
then it will re-use that old service handler.
A do-nothing concurrency strategy. Will NOT even
call the open() method of the service handler.

Connection Strategy
(overrides
connect_svc_handler())

ACE_Cached_Connect_
Strategy

Concurrency Strategy
(overrides
activate_svc_handler())

ACE_NOOP_Concurrency_
Strategy
Create the service handler in a different process
ACE_Process_Strategy
and call its open() hook.

ACE_Reactive_Strategy First register the service handler with the reactor


ACE_Thread_Strategy

and then call its open() hook.


First call the service handlers open method and
then call its activate() method so that another
thread starts the svc() method of the service
handler.

Someexampleswillhelpillustratetheuseofthestrategyacceptorandconnectorclasses.
Example8
#includeace/Reactor.h
#includeace/Svc_Handler.h
#includeace/Acceptor.h
#includeace/Synch.h
#includeace/SOCK_Acceptor.h
#definePORT_NUM10101
#defineDATA_SIZE12
//forwarddeclaration
classMy_Svc_Handler;
//instantiateastrategyacceptor
typedefACE_Strategy_Acceptor<My_Svc_Handler,ACE_SOCK_ACCEPTOR>MyAcceptor;
//instantiateaconcurrencystrategy
typedefACE_Process_Strategy<My_Svc_Handler>Concurrency_Strategy;
//DefinetheServiceHandler
classMy_Svc_Handler:
publicACE_Svc_Handler<ACE_SOCK_STREAM,ACE_NULL_SYNCH>{

109

private:
char*data;
public:
My_Svc_Handler(){
data=newchar[DATA_SIZE];
}
My_Svc_Handler(ACE_Thread_Manager*tm){
data=newchar[DATA_SIZE];
}
intopen(void*){
cout<<Connectionestablished<<endl;
//Registerwiththereactor
ACE_Reactor::instance()>register_handler(this,
ACE_Event_Handler::READ_MASK);
return0;
}
inthandle_input(ACE_HANDLE){
peer().recv_n(data,DATA_SIZE);
ACE_OS::printf(<<%s\n,data);
//keepyourselfregisteredwiththereactor
return0;
}
};
intmain(intargc,char*argv[]){
ACE_INET_Addraddr(PORT_NUM);
//ConcurrencyStrategy
Concurrency_Strategymy_con_strat;
//Instantiatetheacceptor
MyAcceptoracceptor(addr,//addresstoaccepton
ACE_Reactor::instance(),//thereactortouse
0,//dontcareaboutcreationstrategy
0,//dontcareaboutconnectionestb.strategy
&my_con_strat);//useournewprocessconcurrencystrategy
while(1)/*Startthereactorseventloop*/
ACE_Reactor::instance()>handle_events();
}

This example is based on example 2 above. The only difference is that it uses the
ACE_Strategy_Acceptor instead of using the ACE_Acceptor, and uses the
ACE_Process_Strategy as the concurrency strategy for the service handler. This
concurrencystrategyensuresthattheservicehandlerisinstantiatedinaseparateprocess
oncetheconnectionhasbeenestablished.Iftheloadonacertainserviceisgoingtobe
extremelyhigh,itmaybeagoodideatousetheACE_Process_Strategy.Inmostcases,
however, using the ACE_Process_Strategy would be too expensive, and
ACE_Thread_Strategywouldprobablybethebetterconcurrencystrategytouse.
110

Using the ACE_Cached_Connect_Strategy for Connection caching


Inmanyapplications,clientsconnectandthenreconnecttothesameserverseveraltimes,
eachtimeestablishingtheconnection,performingsomeworkandthentearingdownthe
connection(suchasisdoneinWebclients).Needlesstosay,thisisveryinefficientand
expensiveasconnectionestablishmentandteardownareexpensiveoperations.Abetter
strategyinsuchacasewouldbefortheconnectortorememberoldconnectionsandnot
tearthemdownuntilitissufficientlysurethattheclientwillnottrytoreestablisha
connection again. The ACE_Cached_Connect_Strategy provides just such a caching
strategy. This strategy object is used by the ACE_Strategy_Connector to provide for
cachebased connection establishment. If a connection already exists, the
ACE_Strategy_Connectorwillreuseitinsteadofcreatinganewconnection.
Whentheclienttriestoreestablishaconnectiontoaserverthatithadpreviouslyformed
aconnectionwith,the ACE_Cached_Connect_Strategy ensuresthattheoldconnection
andtheoldservicehandlerarereusedinsteadofcreatinganewconnectionandnew
servicehandler.Thus,intruth,theACE_Cached_Connect_Strategynotonlymanagesthe
connectionestablishmentstrategy,italsomanagestheservicehandlercreationstrategy.
Sinceinthiscase,theuserdoesNOTwanttocreatenewservicehandlers,wepassthe
ACE_Strategy_Connector an ACE_Null_Creation_Strategy.Iftheconnectionhasnever
been established before, then ACE_Cached_Connect_Strategy will automatically
instantiatethecorrectservicehandlerthatwaspassedtoitwhenthistemplateclassis
instantiatedusinganinternalcreationstrategy.Thisstrategycanbesettoanystrategythe
userwishestouse.Besidesthis,theACE_Cached_Connect_Strategyitselfcanbepassed
thecreation,concurrencyandrecyclingstrategiesitusesinitsconstructor.Thefollowing
exampleillustratestheseideas.
Example9
#includeace/Reactor.h
#includeace/Svc_Handler.h
#includeace/Connector.h
#includeace/Synch.h
#includeace/SOCK_Connector.h
#includeace/INET_Addr.h
#definePORT_NUM10101
#defineDATA_SIZE16
//forwarddeclaration
classMy_Svc_Handler;
//Functionprototype
staticvoidmake_connections(void*arg);
//Templatespecializationsforthehashingfunctionforthe
//hash_mapwhichisusedbythecache.Thecacheisusedinternallybythe
//CachedConnectionStrategy.HereweuseACE_Hash_Addr
//asourexternalidentifier.Thisutilityclasshasalready
//overloadedthe==operatorandthehash()method.(The
//hashingfunction).Thehash()methoddelegatestheworkto

111

//hash_i()andweusetheIPaddressandporttogeta
//auniqueintegerhashvalue.
size_t
ACE_Hash_Addr<ACE_INET_Addr>::hash_i(constACE_INET_Addr&addr)const
{
returnaddr.get_ip_address()+addr.get_port_number();
}
//instantiateastrategyacceptor
typedefACE_Strategy_Connector<My_Svc_Handler,ACE_SOCK_CONNECTOR>
STRATEGY_CONNECTOR;
//InstantiatetheCreationStrategy
typedefACE_NOOP_Creation_Strategy<My_Svc_Handler>
NULL_CREATION_STRATEGY;
//InstantiatetheConcurrencyStrategy
typedefACE_NOOP_Concurrency_Strategy<My_Svc_Handler>
NULL_CONCURRENCY_STRATEGY;
//InstantiatetheConnectionStrategy
typedefACE_Cached_Connect_Strategy<My_Svc_Handler,
ACE_SOCK_CONNECTOR,
ACE_SYNCH_RW_MUTEX>
CACHED_CONNECT_STRATEGY;

classMy_Svc_Handler:
publicACE_Svc_Handler<ACE_SOCK_STREAM,ACE_MT_SYNCH>{
private:
char*data;
public:
My_Svc_Handler(){
data=newchar[DATA_SIZE];
}
My_Svc_Handler(ACE_Thread_Manager*tm){
data=newchar[DATA_SIZE];
}
//Calledbeforetheservicehandlerisrecycled..
int
recycle(void*a=0){
ACE_DEBUG((LM_DEBUG,
(%P|%t)recyclingSvc_Handler%dwithhandle%d\n,
this,this>peer().get_handle()));
return0;
}
intopen(void*){
ACE_DEBUG((LM_DEBUG,(%t)Connectionestablished\n));

//Registertheservicehandlerwiththereactor
ACE_Reactor::instance()

112

>register_handler(this,ACE_Event_Handler::READ_MASK);
activate(THR_NEW_LWP|THR_DETACHED);
return0;
}
inthandle_input(ACE_HANDLE){
ACE_DEBUG((LM_DEBUG,Gotinputinthread:(%t)\n));
peer().recv_n(data,DATA_SIZE);
ACE_DEBUG((LM_DEBUG,<<%s\n,data));
//keepyourselfregisteredwiththereactor
return0;
}
intsvc(void){
//sendafewmessagesandthenmarkconnectionasidlesothatitcan //berecycled
later.
ACE_DEBUG((LM_DEBUG,Startedtheserviceroutine\n));
for(inti=0;i<3;i++){
ACE_DEBUG((LM_DEBUG,(%t)>>HelloWorld\n));
ACE_OS::fflush(stdout);
peer().send_n(HelloWorld,sizeof(HelloWorld));
}
//Marktheservicehandlerasbeingidlenowandletthe
//otherthreadsreusethisconnection
this>idle(1);
//Waitforthethreadtodie
this>thr_mgr()>wait();
return0;
}
};
ACE_INET_Addr*addr;
intmain(intargc,char*argv[]){
addr=newACE_INET_Addr(PORT_NUM,argv[1]);
//CreationStrategy
NULL_CREATION_STRATEGYcreation_strategy;

//ConcurrencyStrategy
NULL_CONCURRENCY_STRATEGYconcurrency_strategy;
//ConnectionStrategy
CACHED_CONNECT_STRATEGYcaching_connect_strategy;

//instantiatetheconnector
STRATEGY_CONNECTORconnector(
ACE_Reactor::instance(),//thereactortouse
&creation_strategy,
&caching_connect_strategy,

113

&concurrency_strategy);
//Usethethreadmanagertospawnasinglethread
//toconnectmultipletimespassingittheaddress
//ofthestrategyconnector
if(ACE_Thread_Manager::instance()>spawn(
(ACE_THR_FUNC)make_connections,
(void*)&connector,
THR_NEW_LWP)==1)
ACE_ERROR((LM_ERROR,(%P|%t)%p\n%a,clientthreadspawnfailed));
while(1)/*Startthereactorseventloop*/
ACE_Reactor::instance()>handle_events();
}
//Connectionestablishmentfunction,triestoestablishconnections
//tothesameserveragainandreusestheconnectionsfromthecache
voidmake_connections(void*arg){
ACE_DEBUG((LM_DEBUG,(%t)Preparedtoconnect\n));
STRATEGY_CONNECTOR*connector=(STRATEGY_CONNECTOR*)arg;
for(inti=0;i<10;i++){
My_Svc_Handler*svc_handler=0;
//PerformablockingconnecttotheserverusingtheStrategy
//Connectorwithaconnectioncachingstrategy.Sinceweare
//connectingtothesame<server_addr>thesecallswillreturnthe
//samedynamicallyallocated<Svc_Handler>foreach<connect>call.
if(connector>connect(svc_handler,*addr)==1){
ACE_ERROR((LM_ERROR,(%P|%t)%p\n,connectionfailed\n));
return;
}
//Restforafewsecondssothattheconnectionhasbeenfreedup
ACE_OS::sleep(5);
}
}

Intheaboveexample,theCachedConnectionStrategyisusedtocacheconnections.To
usethisstrategy,alittleextraeffortisrequiredtodefinethehash()methodonthehash
mapmanagerthatisusedinternally by ACE_Cached_Connect_Strategy.The hash()
methodisthehashingfunction,whichisusedtohashintothecachemapofservice
handlersandconnectionsthatisusedinternallybytheACE_Cached_Connect_Strategy.
ItsimplyusesthesumoftheIPaddressandportnumberasthehashingfunction,which
isprobablynotaverygoodhashfunction.
Theexampleisalsoalittlemorecomplicatedthentheonesthathavebeenshownsofar
andthuswarrantsalittleextradiscussion.
We use a noop concurrency and creation strategy with the ACE_Strategy_Acceptor.
UsinganoopcreationstrategyISnecessary,aswasexplainedabove,ifthisisnotsetto
aACE_NOOP_Creation_Strategy,theACE_Cached_Connection_Strategywillcausean
assertion failure. When using the ACE_Cached_Connect_Strategy, however, any
114

concurrencystrategycanbeusedwiththestrategyacceptor.Aswasmentionedabove,
theunderlyingcreationstrategyusedbytheACE_Cached_Connect_Strategy canbeset
bytheuser.Therecyclingstrategycanalsobeset.Thisisdonewheninstantiatingthe
caching_connect_strategybypassingitsconstructorthestrategyobjectsforthe
requiredcreationandrecyclingstrategies.Here,wehavenotdoneso,andareusingboth
thedefaultcreationandrecyclingstrategy.
Aftertheconnectorhasbeensetupappropriately,weusetheThread_Managertospawn
anewthreadwiththemake_connections()methodasitsentrypoint.Thismethoduses
our new strategy connector to connect to a remote site. After the connection is
established,thisthreadgoestosleepforfivesecondsandthentriestorecreatethesame
connectionusingourcachedconnector.Thisthreadshouldthen,initsnextattempt,find
theconnectionintheconnectorscacheandreuseit.
Ourservicehandler(My_Svc_Handler)iscalledbackbythereactor,asusual,oncethe
connectionhasbeenestablished.The open() methodof My_Svc_Handler thenmakes
itself into an active object by calling its activate() method. The svc() method then
proceedstosendthreemessagestotheremotehostandthenmarkstheconnectionidleby
callingthe idle() methodoftheservicehandler.Notethecalltothethreadmanager,
askingittowait (this>thr_mgrwait()) forallthreads inthethreadmanager to
terminate.Ifyoudonotaskthethreadmanagertowaitfortheotherthreads,thenthe
semanticshavebeensetupinACEsuchthatoncethethreadinanACE_Task(orinthis
casetheACE_Svc_HandlerwhichisatypeofACE_Task)isterminated,theACE_Task
object(orinthiscasethe ACE_My_Svc_Handler)willautomaticallybedeleted.Ifthis
happens,then,whenthe Cache_Connect_Strategy goeslookingforpreviouslycached
connections,itwillNOTfind My_Svc_Handler asweexpectittoo,as,ofcourse,this
hasbeendeleted.
The recycle() method in ACE_Svc_Handler has also been overloaded in
My_Svc_Handler.Thismethodisautomaticallycalledbackwhenanoldconnectionis
foundbytheACE_Cache_Connect_Strategy,sothattheservicehandlermaydorecycle
specificoperationsinthismethod.Inourcase,wejustprintouttheaddressofthe this
pointerofthehandlerwhichwasfoundinthecache.Whentheprogramisrun,wenotice
thattheaddressofthehandlebeingusedaftereachconnectionisestablishedisthesame,
indicatingthatthecachingisworkingcorrectly.

Using Simple Event Handlers with the Acceptor and


Connector patterns
Attimes,usingtheheavyweight ACE_Svc_Handler asthehandlerforacceptorsand
connectorsmaybeunwarrantedandcausecodebloat.Inthesecases,theusermayusethe
lighterACE_Event_Handlermethodastheclasswhichiscalledbackbythereactoronce
theconnectionhasbeenestablished.Todoso,oneneedstooverloadthe get_handle()
methodandalsoincludeaconcreteunderlyingstreamwhichwillbeusedbytheevent
115

handler.Anexampleshouldhelpillustratethesechanges.Herewehavealsowrittena
new peer() methodwhichreturnsareferencetotheunderlyingstream,asitdidinthe
ACE_Svc_Handlerclass.
Example10
#includeace/Reactor.h
#includeace/Svc_Handler.h
#includeace/Acceptor.h
#includeace/Synch.h
#includeace/SOCK_Acceptor.h
#definePORT_NUM10101
#defineDATA_SIZE12
//forwarddeclaration
classMy_Event_Handler;
//CreatetheAcceptorclass
typedefACE_Acceptor<My_Event_Handler,ACE_SOCK_ACCEPTOR>
MyAcceptor;
//Createaneventhandlersimilartoasseeninexample2.Wehaveto//overloadthe
get_handle()methodandwritethepeer()method.Wealso//providethedatamemberpeer_
astheunderlyingstreamwhichis//used.
classMy_Event_Handler:
publicACE_Event_Handler{
private:
char*data;
//Addanewattributefortheunderlyingstreamwhichwillbeusedby//theEventHandler
ACE_SOCK_Streampeer_;
public:
My_Event_Handler(){
data=newchar[DATA_SIZE];
}
int
open(void*){
cout<<Connectionestablished<<endl;
//Registertheeventhandlerwiththereactor
ACE_Reactor::instance()>register_handler(this,
ACE_Event_Handler::READ_MASK);
return0;
}
int
handle_input(ACE_HANDLE){
//Afterusingthepeer()methodofourACE_Event_Handlertoobtaina
//referencetotheunderlyingstreamoftheservicehandlerclasswe
//callrecv_n()onittoreadthedatawhichhasbeenreceived.This
//dataisstoredinthedataarrayandthenprintedout
peer().recv_n(data,DATA_SIZE);
ACE_OS::printf(<<%s\n,data);

116

//keepyourselfregisteredwiththereactor
return0;
}
//newmethodwhichreturnsthehandletothereactorwhenit
//asksforit.
ACE_HANDLE
get_handle(void)const{
returnthis>peer_.get_handle();
}
//newmethodwhichreturnsareferencetothepeerstream
ACE_SOCK_Stream&
peer(void)const{
return(ACE_SOCK_Stream&)this>peer_;
}
};
intmain(intargc,char*argv[]){
ACE_INET_Addraddr(PORT_NUM);
//createtheacceptor
MyAcceptoracceptor(addr,//addresstoaccepton
ACE_Reactor::instance());//thereactortouse
while(1)/*Startthereactorseventloop*/
ACE_Reactor::instance()>handle_events();

117

The Service Configurator


Chapter

A pattern for Dynamic Configuration of Services


Manydistributedsystemscontainacollectionofglobalservices.Theseservicescanbe
calleduponbyanapplicationdevelopertoassistinhiminhisdistributeddevelopment
needs.Globalservices,suchasnameservices,remoteterminalaccessservices,logging
and time services are needed when constructing distrbuted applications. One way to
constructtheseservicesistowriteeachserviceas aseparateprogram.Theseservice
programsarethenexecutedandrunintheirownprivateprocesses.However,thisleads
toconfigurationnightmares.Anadministratorwouldhavetogotoeachnodeandexecute
theserviceprogramsinaccordancewithcurrentuserneedsandpolicies.Ifnewservices
havetobeaddedorolderonesremoved,thentheadministratorhastospendthetimeto
gotoeachmachineagainandreconfigure.Further,suchconfigurationisstatic.Tore
configure,theadminstratormanuallystopsaservice(bykill()ingtheserviceprocess)and
thenrestartsareplacementservice.Also,servicesmayberunningonmachineswhere
theyareneverusedby any application.Obviously,suchanapproachisinefficientand
undesirable.
Itwouldbemuchmoreconvenientiftheservicescouldbedynamicallystarted,removed,
suspendedandresumed.Thus,theservicedeveloperdoesnthavetoworryabouthowthe
servicewillbeconfigured.Allhecaresaboutishowtheservicegetsthejobdone.The
adminstrator should be able to add and replace new services within the application
withoutrecomplingorshuttingdowntheservicesserverprocess.
TheServiceConfiguratorpatternisapatternwhichallowsallthis.Itdecoupleshowa
service is implented from how it is configured. New services can be added in an
applicationandoldservicescanberemovedwithoutshuttingdowntheserver.Mostof
thetimetheserverwhichprovidesservicesisimplementedasadaemonprocess.

Framework Components
TheServiceConfiguratorinACEconsistsofthefollowingcomponents:

An abstract class named ACE_Service_Object from which the application


developer must subclass to create his own application-specific concrete Service
Object.
118

The application-specific concrete service objects.


A Service Repository, ACE_Service_Repository, which records the
services that the server has running in it or knows about.
ACE_Service_Configwhich serves as an application developers interface to
the entire service configuration framework.

A service configuration file. This file contains configuration information for all
of the service objects. By default it is named svc.conf. When your application
issues an open() call on the ACE_Service_Config, the service configurator
framework will read and process all configuration information that you write
within the file. The application will be configured accordingly.

ACE_Service_Object
ACE_Service_Repository

init()
fini()
suspend()
resume()

initialize()
insert()
remove()
suspend()
resume()
ConcreteServiceObject1

ACE_Service_Config

init()
fini()
suspend()
resume()

ConcreteServiceObject2

init()
fini()
suspend()
resume()

initialize()
remove()
suspend()
resume()
reconfigure()

TheACE_Service_Objectincludes methods which are called by the framework when the


service is to start (init()), stop (fini()), suspend (suspend()) or is to be resumed
(resume()). The ACE_Service_Object derieves from ACE_Shared_Object and
ACE_Event_Handler. The ACE_Shared_Objectserves as the abstract base class when an
application wishes it to be loaded using the dynamic linking facilities of the operating system.
ACE_Event_Handler was introduced in the discussion of the Reactor. A developer
subclasses his class from here when he wants it to respond to events from the Reactor.
119

Why does the Service Object inherit from ACE_Event_Handler? One way to initiate a
reconfiguration is for the user to generate a signal. When such a signal event occurs, the
reactor is used to handle the signal and issue a reconfiguration request to the
ACE_Service_Config. Besides this, reconfiguration of the software will probably happen
after an event has occurred. Thus all Service Objects are built so that they can handle events.
Theserviceconfigurationfilehasitsownsimplescriptfordescribinghowyouwanta
servicetobestartedandthenrun.Youcandefinewhetheryouwanttoaddanewservice
ortosuspend,resumeorremoveanexistingserviceintheapplication.Parameterscan
alsobesenttotheseservices.TheServiceConfiguratoralsoallowsthereconfiguration
ofACEbasedstreams.WewilltalkaboutthismorewhenwehavediscussedtheACE
streamsframework.

Specifying the configuration file


Theserviceconfigurationfilespecifieswhichservicestoloadandstartinanapplication.
Inaddition,youcanspecifywhichservicesaretobestopped,suspendedorresumed.
Parameterscanalsobesenttoyourserviceobjectsinit()method.
Starting a service
Servicescanbestartedupstaticallyordynamically. Iftheserviceistobestartedup
dynamically, the service configurator will actually load up the service object from a
shared library object (i.e. a dynamic link library). In order to do this, the service
configuratorneedstoknowwhichlibrarycontainstheobjectandalsoitneedstoknow
thenameoftheobjectinthatlibrary.Thus,inyourcodefileyoumustalsoinstantiatethe
serviceobjectwithanamethatyouwillremember.Thusadynamicserviceisconfigured
as:
dynamicservice_nametype_of_service*location_of_shared_lib:name_of_objectparameters

Astaticserviceisinitializedas:
staticservice_nameparameters_sent_to_service_object

Suspending or resuming a service


Whenyoustartaserviceyouassignitaname,asIjustmentioned.Thisnameisthen
usedtosuspendorresumethatservice.Soallyouhavetodotosuspendaserviceisto
specify:
suspendservice_name

inthesvc.conffile.Thiscausesthesuspend()methodintheserviceobjecttobecalled.
Yourserviceobjectshouldthensuspenditself(basedonwhatever"suspend"meansfor
thatparticularservice).
120

Ifyouwanttothenresumethisserviceallyouhavetodoisspecify:
resumeservice_name
inthe svc.conf file.Thiscausesthe resume() methodintheserviceobjecttobecalled.
Yourserviceobjectshouldthensuspenditself(basedonwhatever"resume"meansfor
thatparticularservice).
Stopping a service
Stoppingandthenremovingaservice(ifithadbeendynamicallyloaded)isalsoasimple
operationthatcanbeachievedbyspecifyingthefollowinginyourconfigurationfile:
removeservice_name

Thiscausestheserviceconfiguratortocallthe fini()methodofyourapplication.This
methodshouldstoptheservice.Theserviceconfiguratoritselfwilltakecareofunlinking
adynamicobjectfromtheserversaddressspace.

Writing Services
WritingyourownservicefortheServiceConfiguratorisrelativelysimple.Youarefree
to have this service do whatever you want. The only constraint is that it should be
subclassedfrom ACE_Service_Object.Itmustthereforeimplementthe init() and fini()
methods. When ACE_Service_Config is open()d it reads the configuration file (i.e.
svc.conf)andtheninitializestheservicesaccordingtothefile.Onceitloadsupaservice
(bydynamicallylinkingitinifspecified),itwillcallthatServiceObjectsinit()method.
Similarily,iftheconfigurationasksforaservicetoberemoved,thefini()methodwillbe
called.Thesemethodsareresponsibleforstartinganddestroyinganyresourcesthatthe
servicemayneed,suchasmemory,connections,threads,etc.Theparametersthatare
specified(fieldthatisset)inthesvc.conffilearepassedinthroughtheinit()methodof
theserviceobject.
Thefollowingexampleillustratesaservicewhichderievesfrom ACE_Task_Base.The
ACE_Task_Baseclasscontainstheactivate()methodthatisusedtocreatethreadswithin
anobject.(ACE_Task, whichwasdiscussedinthechapterontasksandactiveobjects,
derievesfrom ACE_Task_Base andalsoincludesamessagequeueforcommunication
purposes.Sincewedontneedourservicetocommunicatewithanothertaskwejustuse
ACE_Task_Basetohelpusgetthejobdone.)Formoreonthis,readthechapteronTasks
andActiveObjects.Theserviceisadonothingservicewhichperiodicallybroadcasts
thetimeofthedayonceitisstartedup.
Example1a
//TheServicesHeaderFile.
#if!defined(MY_SERVICE_H)
#defineMY_SERVICE_H
#include"ace/OS.h"
#include"ace/Task.h"

121

#include"ace/Synch_T.h"
//ATimeServiceclass.ACE_Task_Basealreadyderivesfrom//ACE_Service_Objectandthus
wedonthavetosubclassfrom//ACE_Service_Objectinthiscase.
classTimeService:publicACE_Task_Base{
public:
virtualintinit(intargc,ASYS_TCHAR*argv[]);
virtualintfini(void);
virtualintsuspend(void);
virtualintresume(void);
virtualintsvc(void);
private:
intcanceled_;
ACE_Condition<ACE_Thread_Mutex>*cancel_cond_;
ACE_Thread_Mutex*mutex_;
};
#endif

The correspondingimplementation is as follows.When thetime servicereceives the


init() callit activate()sasinglethreadinthetask.Thiswillcauseanewthreadtobe
createdwhichtreatsthesvc()methodasitsentrypoint.Inthesvc()method,thisthread
willloopuntilitfindsthatthecanceled_flaghasbeenset.Thisflagissetwhenfini()is
calledbytheserviceconfigurationframework.The fini() method,however,hastobe
sure thattheunderlyingthreadisdeadBEFOREitreturns totheunderlyingservice
configurationframework.Why?Becausetheserviceconfigurationwillactuallyunload
the shared library which contains the TimeService. This will effectively delete the
TimeService object fromtheapplication process.Ifthethreadisntdead before that
happens, it will be issuing a call on code that has been vaporized by the service
configurator! Not what we want. To ensure that the thread dies before the service
configuratorvaporizestheTimeServiceobject,aconditionvariableisused.(Formore
onhowconditionvariablesareused,pleasereadthechapteronthreads).
Example1b
#include"Services.h"
intTimeService::init(intargc,char*argv[]){
ACE_DEBUG((LM_DEBUG,"(%t)StartingupthetimeService\n"));
mutex_=newACE_Thread_Mutex;
cancel_cond_=newACE_Condition<ACE_Thread_Mutex>(*mutex_);
activate(THR_NEW_LWP|THR_DETACHED);
return0;
}
intTimeService::fini(void){
ACE_DEBUG((LM_DEBUG,
"(%t)FINISH!ClosingdowntheTimeService\n"));

122

//Allofthefollowingcodeisheretomakesurethatthe
//threadinthetaskisdestroyedbeforetheserviceconfigurator
//deletesthisobject.
canceled_=1;
mutex_>acquire();
while(canceled_)
cancel_cond_>wait();
mutex_>release();
ACE_DEBUG((LM_DEBUG,"(%t)TimeServiceisexiting\n"));
return0;
}

//SuspendtheTimeService.
intTimeService::suspend(void){
ACE_DEBUG((LM_DEBUG,"(%t)TimeServicehasbeensuspended\n"));
intresult=ACE_Task_Base::suspend();
returnresult;
}
//ResumetheTimeService.
intTimeService::resume(void){
ACE_DEBUG((LM_DEBUG,"(%t)ResumingTimeService\n"));
intresult=ACE_Task_Base::resume();
returnresult;
}
//Theentryfunctionforthethread.Thetasksunderlyingthread
//startshereandkeepssendingoutmessages.Itstopswhen:
//a)itissuspeneded
//b)itisremovedbyfini().Thishappenswhenthefini()method
// setsthecancelled_flagtotrue.ThuscausestheTimeService
//
threadtofallthroughthewhileloopanddie.Beforedyingit
//
informsthemainthreadofitsimminentdeath.Themaintask
//
thatwaspreviouslyblockedinfini()canthencontinueintothe
//
frameworkanddestroytheTimeServiceobject.
intTimeService::svc(void){
char*time=newchar[36];
while(!canceled_){
ACE::timestamp(time,36);
ACE_DEBUG((LM_DEBUG,"(%t)Currenttimeis%s\n",time));
ACE_OS::fflush(stdout);
ACE_OS::sleep(1);
}
//SignaltheServiceConfiguratorinformingitthatthetaskisnow
//exitingsoitcandeleteit.
canceled_=0;
cancel_cond_>signal();
ACE_DEBUG((LM_DEBUG,
"SignalledmaintaskthatTimeServiceisexiting\n"));
return0;
}

123

//Definetheobjecthere
TimeServicetime_service;

Andhere is a simple configuration file that is currently set just to activate the time
service.Thecomment#markscanberemovedtosuspend,resumeorremovetheservice.
Example1c
#Toconfiguredifferentservices,simplyuncommenttheappropriate
#linesinthisfile!
#resumeTimeService
#suspendTimeService
#removeTimeService
#settodynamicallyconfiguretheTimeServiceobjectanddosowithout
#sendinganyparameterstoitsinitmethod
dynamicTimeServiceService_Object*./Server:time_service""

And,lastbutnotleast,hereisthepieceofcodethatstartstheserviceconfigurator.This
codealsosetsupasignalhandlerobjectthatisusedtoinitiatethereconfiguration.The
signalhandlerhasbeensetupsothatitrespondstoaSIGWINCH(signalthatisgenerated
whenawindowischanged).Afterstartingtheserviceconfigurator,theapplicationenters
intoareactiveloopwaitingforaSIGWINCHsignaleventtooccur.Thiswouldthencall
back the signal handler which would call reconfigure() on ACE_Service_Config. As
explained earlier, when this happens, the service configurator rereads the file and
processeswhatevernewdirectivestheuserhasputinthere.Forexample,afterissuingthe
dynamicstartfortheTimeService,inthisexampleyoucouldchangethesvc.conffileso
thatithasthesinglesuspendstatementinit.Whentheconfiguratorreadsthis,itwillcall
suspend on the TimeService which will cause it to suspend its underlying thread.
Similarily,iflateryouchangesvc.confagainsothatitasksfortheservicetoberesumed
thenthiswillcalltheTimeService::resume()method.Thisinturnresumesthethread
thathadbeensuspendedearlier.
Example1d
#include"ace/OS.h"
#include"ace/Service_Config.h"
#include"ace/Event_Handler.h"
#include<signal.h>
//TheSignalHandlerwhichisusedtoissuethereconfigure()
//callontheserviceconfigurator.
classSignal_Handler:publicACE_Event_Handler{
public:
intopen(){
//registertheSignalHandlerwiththeReactortohandle
//reconfigurationsignals
ACE_Reactor::instance()>register_handler(SIGWINCH,this);
return0;
}
inthandle_signal(intsignum,siginfo*,ucontext_t*){

124

if(signum==SIGWINCH)
ACE_Service_Config::reconfigure();
return0;
}
};
intmain(intargc,char*argv[]){
//InstantiateandstartuptheSignalHandler.Thisisusesto
//handlereconfigurationevents.
Signal_Handlersh;
sh.open();
if(ACE_Service_Config::open(argc,argv)==1)
ACE_ERROR_RETURN((LM_ERROR,
"%p\n","ACE_Service_Config::open"),1);
while(1)
ACE_Reactor::instance()>handle_events();
}

Using the Service Manager


The ACE_Service_Manager is a service that is can be used to remotely manage the
serviceconfigurator.Itcancurrentlyreceivetwotypesofrequests.First,youcansendit
a help message which will list all the services that are currently loaded into the
application.Second,youcansendtheservicemanagerareconfiguremessage.This
causestheserviceconfiguratortoreconfigureitself.
Following is an example which illustrates a client that sends these two types of
commandstotheservicemanager.
Example2
#include"ace/OS.h"
#include"ace/SOCK_Stream.h"
#include"ace/SOCK_Connector.h"
#include"ace/Event_Handler.h"
#include"ace/Get_Opt.h"
#include"ace/Reactor.h"
#include"ace/Thread_Manager.h"
#defineBUFSIZE128
classClient:publicACE_Event_Handler{
public:
~Client(){
ACE_DEBUG((LM_DEBUG,"Destructor\n"));
}

//Constructor
Client(intargc,char*argv[]):connector_(),stream_(){
//Theusermustspecifyaddressandportnumber

125

ACE_Get_Optget_opt(argc,argv,"a:p:");
for(intc;(c=get_opt())!=1;){
switch(c){
case'a':
addr_=get_opt.optarg;
break;
case'p':
port_=((u_short)ACE_OS::atoi(get_opt.optarg));
break;
default:
break;
}
}
address_.set(port_,addr_);
}

//Connecttotheremotemachine
intconnect(){
connector_.connect(stream_,address_);
ACE_Reactor::instance()>
register_handler(this,ACE_Event_Handler::READ_MASK);
return0;
}
//Sendalist_servicescommand
intlist_services(){
stream_.send_n("help",5);
return0;
}
//Sendthereconfigurationcommand
intreconfigure(){
stream_.send_n("reconfigure",12);
return0;
}

//Handlebothstandardinputandremotedatafromthe
//ACE_Service_Manager
inthandle_input(ACE_HANDLEh){
charbuf[BUFSIZE];
//Gotcommandfromtheuser
if(h==ACE_STDIN){
intresult=ACE_OS::read(h,buf,BUFSIZ);

if(result==1)
ACE_ERROR((LM_ERROR,"can'treadfromSTDIN"));
elseif(result>0){
//ConnecttotheServiceManager
this>connect();
if(ACE_OS::strncmp(buf,"list",4)==0)
this>list_services();

126

elseif(ACE_OS::strncmp(buf,"reconfigure",11)==0)
this>reconfigure();
}
return0;
}
//Wegotinputfromremote
else{

switch(stream_.recv(buf,BUFSIZE)){

case1:

//ACE_ERROR((LM_ERROR,
"Errorinreceivingfromremote\n"));
ACE_Reactor::instance()>remove_handler(this,
ACE_Event_Handler::READ_MASK);
return0;

case0:

return0;

default:

ACE_OS::printf("%s",buf);
return0;
}
}
}

//UsedbytheReactorFramework
ACE_HANDLEget_handle()const{
returnstream_.get_handle();
}
//Closedowntheunderlyingstream
inthandle_close(ACE_HANDLE,ACE_Reactor_Mask){
returnstream_.close();
}
private:
ACE_SOCK_Connectorconnector_;
ACE_SOCK_Streamstream_;
ACE_INET_Addraddress_;
char*addr_;
u_shortport_;
};

intmain(intargc,char*argv[]){
Clientclient(argc,argv);

//Registerthetheclienteventhandlerasthestandard

127

//inputhandler
ACE::register_stdin_handler(&client,
ACE_Reactor::instance(),
ACE_Thread_Manager::instance());
ACE_Reactor::run_event_loop();
}

Inthisexample,theClientclassisaneventhandlerwhichhandlestwotypesofevents.
StandardinputeventsfromtheuserandrepliesfromtheACE_Service_Manager.Ifthe
usertypesinalistorreconfigurecommand,thenthecorrespondingmessagesare
senttotheremoteACE_Service_Manager.TheServiceManagerinturnwillreplywitha
listofthecurrentlyconfiguredservicesorwithdone(indicatingthattheservicere
configurationisdone).SincetheACE_Service_Managerisaservice,itisaddedintoan
applicationusingtheserviceconfiguratorframework,i.e.youspecifywhetheryouwish
ittobeloadedstaticallyordynamicallyinthesvc.conffile.
Forexample,thiswillstaticallystartuptheservicemanageratport9876.
staticACE_Service_Managerp9876

128

Message Queues
Chapter

The use of Message Queues in ACE


Modernrealtime applications are usually constructed as aset ofcommunicating but
independent tasks. These tasks can communicate with each other through several
mechanisms, one which is commonly used is a message queue. The basic mode of
communicationinthiscaseisforasender(orproducer)tasktoenqueueamessageontoa
messagequeueandthereceiver(orconsumer)tasktodequeuethemessagefromthat
queue.Thisofcourseisjustoneofthewaysmessagequeuescanbeused.Wewillsee
severaldifferentexamplesofmessagequeueusageintheensuingdiscussion.
ThemessagequeueinACEhasbeenmodeledafterUNIXSystemVmessagequeues,
andareeasytopickupifyouarealreadyfamiliarwithSystemV.Thereareseveral
differenttypesofMessageQueuesavailableinACE.Eachofthesedifferentqueuesusea
differentschedulingalgorithmforqueueinganddequeingmessagesfromthequeue.

Message Blocks
Messagesareenqueuedontomessagequeuesas messageblocks inACE.Amessage
blockwrapstheactualmessagedatathatisbeingstoredandoffersseveraldatainsertion
andmanipulationoperations.Eachmessageblockcontainsaheaderandadatablock.
Notethatcontainsisusedinaloosesensehere.TheMessageBlockdoesnotalways
managethememoryassociatedwiththeDataBlock(althoughyoucanhaveitdothisfor
you) or with the Message Header. It only holds a pointer to both of them. The
containmentisonlylogical. Thedatablockinturnholdsapointertoanactualdata
buffer. This allows flexible sharing of data between multiple message blocks as
illustratedinthefigurebelow.Notethatinthefiguretwomessageblocksshareasingle
datablock.Thisallowsthequeueingofthesamedataontodifferentqueueswithoutdata
copyingoverhead.
The message block class is named ACE_Message_Block and the data block class is
ACE_Data_Block. Theconstructorsin ACE_Message_Block areaconvientwaytoto
actuallycreatemessageblocksanddatablocks
129

ACE_Message_Block1

ACE_Data_Block

Actual Data Buffer

ACE_Message_Block2

Constructing Message Blocks


The ACE_Message_Block class contains several different constructors. You can use
theseconstructorstohelpyoutomanagethemessagedatathatishiddenbehindthe
messageanddatablocks.TheACE_Message_Blockclasscanbeusedtocompletelyhide
theACE_Data_Blockandmanagethemessagedataforyouorifyouwantyoucancreate
andmanagedatablocksandmessagedataonyourown.Thenextsectiongoesoverhow
youcanuseACE_Message_Blocktomanagemessagememoryanddatablocksforyou.
We then go over how you can manage this on your own without relying on
ACE_Message_Blocksmanagementfeatures.
ACE_Message_Block allocates and manages the data memory.
Theeasiestwaytocreateamessageblockistopassinthesizeoftheunderlyingdata
segmenttotheconstructorofthe ACE_Message_Block.Thiscausesthecreationofan
ACE_Data_Blockandtheallocationofanemptymemoryregionformessagedata.After
creatingthemessageblockyoucanusetherd_ptr()andwr_ptr()manipulationmethods
toinsertandremovedatafromthemessageblock.Thechiefadvantageoflettingthe
ACE_Message_Blockcreatethememoryforthedataandthedatablockisthatitwillnow
correctly manage all of this memory for you. This can save you from many future
memoryleakheadaches.
The ACE_Message_Block constructor also allows the programmer to specify the
allocatorthatACE_Message_Blockshouldusewheneveritallocatesmemoryinternally.
Ifyoupassinanallocator themessageblockwilluseittoallocate memoryforthe
creationofthedatablockandmessagedataregions.Theconstructoris:
ACE_Message_Block(size_tsize,
ACE_Message_Typetype=MB_DATA,
ACE_Message_Block*cont=0,
constchar*data=0,

130

ACE_Allocator*allocator_strategy=0,
ACE_Lock*locking_strategy=0,
u_longpriority=0,
constACE_Time_Value&execution_time=ACE_Time_Value::zero,
constACE_Time_Value&deadline_time=ACE_Time_Value::max_time);

Theaboveconstructoriscalledwiththeparameters:
1. Thesizeofthedatabufferthatistobeassociatedwiththemessageblock.Note
thatthesizeofthemessageblockwillbesize,butthelengthwillbe0untilthe
wr_ptrisset.Thiswillbeexplainedfurtherlater.
2. Thetypeofthemessage.(Thereareseveraltypesavailableinthe
ACE_Message_Typeenumerationincludingdatamessages,whichisthedefault).
3. Apointertothenextmessageblockinthefragmentchain.Messageblockscan
actuallybelinkedtogethertoformchains.Eachofthesechainscanthenbe
enqueuedontoamessagequeueasifitwereonlyasinglemessageblock.This
defaultsto0,meaningthatchainingisnotusedforthisblock.
4. Apointertothedatabufferwhichistobestoredinthismessageblock.Ifthe
valueofthisparameteriszero,thenabufferofthesizespecifiedinthefirst
parameteriscreatedandmanagedbythemessageblock.Whenthemessageblock
isdeleted,thecorrespondingdatabufferisalsodeleted.However,ifthedata
bufferisspecifiedinthisparameter,i.e.theargumentisnotnull,thenthemessage
blockwillNOTdeletethedatabufferonceitisdestroyed.Thisisanimportant
distinctionandshouldbenotedcarefully.
5. Theallocator_strategytobeusedtoallocatethedatabuffer(ifneeded),i.e.if
the4thparameterwasnull(asexplainedabove).AnyoftheACE_Allocatorsub
classescanbeusedasthisargument.(SeethechapteronMemoryManagement
formoreonACE_Allocators).
6. Iflocking_strategyisnonzero,thenthisisusedtoprotectregionsofcodethat
accesssharedstate(e.g.referencecounting)fromraceconditions.
7. Thisandthenexttwoparametersareusedforschedulingfortherealtime
messagequeueswhichareavailablewithACE,andshouldbeleftattheirdefault
fornow.
User allocates and manages message memory
If you are using ACE_Message_Block you are not tied down to using it to allocate
memoryforyou.Theconstructorsofthemessageblockallowyouto

Createandpassinyourowndatablockthatpointstothemessagedata.
Passinapointertothemessagedataandthemessageblockwillcreateandsetupthe
underlyingdatablock.Themessageblockwillmanagethememoryforthedatablock
butnotforthemessagedata.
131

The example below illustrates how a message block can be passed a pointer to the
message data and ACE_Message_Block creates and manages the underlying
ACE_Data_Block.

//Thedata
chardata[size];
data=Thisismydata;
//Createamessageblocktoholdthedata
ACE_Message_Block*mb=newACE_Message_Block(data,//datathatisstored
//inthenewlycreateddata
//
blocksize);//sizeoftheblockthat
//istobestored.

Thisconstructorcreatesanunderlyingdatablockandsetsituptopointtothebeginning
ofthedatathatispassedtoit.Themessageblockthatiscreateddoesnotmakeacopyof
thedatanordoesitassumeownershipofit.Thismeansthatwhenthemessageblock mb
isdestroyed,theassociateddatabuffer data willNOTbedestroyed(i.e.thismemory
will not be deallocated). This makes sense, the message block didnt make a copy
therefore the memory was not allocated by the message block, so it shouldnt be
responsibleforitsdeallocation.

Inserting and manipulating data in a message block


Inadditiontotheconstructors,ACE_Message_Blockoffersseveralmethodstoinsertdata
intoamessageblockdirectly.Additionalmethodsarealsoavailabletomanipulatethe
datathatisalreadypresentinamessageblock.
Each ACE_Message_Block hastwounderlyingpointersthatareusedtoreadandwrite
data to and from a message block, aptly named the rd_ptr and wr_ptr. They are
accessibledirectlybycallingtherd_ptr()andwr_ptr()methods.Therd_ptrpointstothe
locationwheredataistobereadfromnext,andthewr_ptrpointstothelocationwhere
dataistobewrittentonext.Theprogrammermustcarefullymanagethesepointersto
insurethatbothofthemalwayspointtothecorrectlocation.Whendataisreadorwritten
using these pointers they must be advanced by the programmer, they do not update
automagically.Mostinternalmessageblockmethodsalsomakeuseofthesetwopointers
thereforemakingitpossibleforthemtochangestatewhenyoucallamethodonthe
messageblock.Itistheprogrammer'sresponsibilitytomakesurethathe/sheisawareof
whatisgoingonwiththesepointers.

132

Copying and Duplicating


Data can be copied into a message block by using the copy() method on
ACE_Message_Block.
intcopy(constchar*buf,size_tn);

Thecopymethodtakesapointertothebufferthatistobecopiedintothemessageblock
andthesizeofthedatatobecopiedasarguments.Thismethodusesthe wr_ptr and
begins writing from this point onwards till it reaches the end of the data buffer as
specifiedbythesizeargument.copy()willalsoensurethatthewr_ptrisupdatedsothat
ispointstothenewendofthebuffer.Notethatthismethodwillactuallyperforma
physicalcopy,andthusshouldbeusedwithcaution.
Thebase()andlength()methodscanbeusedinconjunctiontocopyouttheentiredata
bufferfromamessageblock.base()returnsapointerthatpointstothefirstdataitemon
thedatablockandlength()returnsthetotalsizeoftheenqueueddata.Addingthebase
andlengthgetsyouapointertotheendofthedatablock.Usingthesemethodstogether
you can write a routinue that takes the data from the message block and makes an
externalcopy.
Theduplicate()andclone()methodsareusedtomakeacopyofamessageblock.The
clone()methodasthenamesuggestsactuallycreatesafreshcopyoftheentiremessage
block including its data blocks and continuations, i.e. a deep copy. The duplicate()
method, on the other hand, uses the ACE_Message_Blocks reference counting
mechanism. It returns a pointer to the message block that is to be duplicated and
internallyincrementsaninternalreferencecount.

Releasing Message Blocks


Oncedonewithamessageblocktheprogrammercancalltherelease()methodonit.If
themessagedatamemorywasallocatedbythemessageblockthencallingtherelease()
methodwillalsodeallocatethatmemory.Ifthemessageblockwasreferencecounted,
thentherelease()willcausethecounttodecrementuntilthecountreacheszero,after
whichthemessageblockanditsassociateddatablocksareremovedfrommemory.

133

Message Queues in ACE


As mentioned earlier, ACE has several different types of message queues, which in
generalcanbedividedintotwocategories,staticanddynamic.Thestaticqueueisa
generalpurposemessagequeuenamedACE_Message_Queue(asifyoucouldntguess)
whereas the dynamic message queues (ACE_Dynamic_Message_Queue) are realtime
message queues. The major difference between these two types of queues is that
messagesonstaticqueueshavestaticpriority,i.e.oncethepriorityissetitdoesnot
change.Ontheotherhand,inthedynamicmessagequeues,thepriorityofmessagesmay
changedynamically,basedonparameterssuchasexecutiontimeanddeadline.
Thefollowingexampleillustrateshowtocreateasimplestaticmessagequeueandthen
howtoenqueueanddequeuemessageblocksontoit.
Example1a
#ifndefMQ_EG1_H_
#defineMQ_EG1_H_
#include"ace/Message_Queue.h"
classQTest
{
public:
//Constructorcreatesamessagequeuewithnosynchronization
QTest(intnum_msgs);
//Enqueuethenumofmessagesrequiredontothemessagemq.
intenq_msgs();
//Dequeueallthemessagespreviouslyenqueued.
intdeq_msgs();
private:
//Underlyingmessagequeue
ACE_Message_Queue<ACE_NULL_SYNCH>*mq_;
//Numberofmessagestoenqueue.
intno_msgs_;
};
#endif/*MQ_EG1.H_*/

Example1b
#include"mq_eg1.h"
QTest::QTest(intnum_msgs)
:no_msgs_(num_msgs)
{

134

ACE_TRACE("QTest::QTest");
//Firstcreateamessagequeueofdefaultsize.
if(!(this>mq_=newACE_Message_Queue<ACE_NULL_SYNCH>()))
ACE_DEBUG((LM_ERROR,"Errorinmessagequeueinitialization\n"));
}
int
QTest::enq_msgs()
{
ACE_TRACE("QTest::enq_msg");
for(inti=0;i<no_msgs_;i++)
{
//createanewmessageblockspecifyingexactlyhowlarge
//anunderlyingdatablockshouldbecreated.
ACE_Message_Block*mb;
ACE_NEW_RETURN(mb,
ACE_Message_Block(ACE_OS::strlen("Thisismessage1\n")),
1);

//Insertdataintothemessageblockusingthewr_ptr
ACE_OS::sprintf(mb>wr_ptr(),"Thisismessage%d\n",i);
//Becarefultoadvancethewr_ptrbythenecessaryamount.
//Notethattheargumentisoftype"size_t"thatismappedto
//bytes.
mb>wr_ptr(ACE_OS::strlen("Thisismessage1\n"));
//Enqueuethemessageblockontothemessagequeue
if(this>mq_>enqueue_prio(mb)==1)
{
ACE_DEBUG((LM_ERROR,"\nCouldnotenqueueontomq!!\n"));
return1;
}
ACE_DEBUG((LM_INFO,"EQ'ddata:%s\n",mb>rd_ptr()));
}//endfor
//Nowdequeueallthemessages
this>deq_msgs();
return0;
}
int
QTest::deq_msgs()
{
ACE_TRACE("QTest::dequeue_all");
ACE_DEBUG((LM_INFO,"No.ofMessagesonQ:%dBytesonQ:%d\n"
,mq_>message_count(),mq_>message_bytes()));
ACE_Message_Block*mb;
//dequeuetheheadofthemessagequeueuntilnomoremessagesare

135

//left.NotethatIamoverwritingthemessageblockmbandIsince
//Iamusingthedequeue_head()methodIdonthavetoworryabout
//resettingtherd_ptr()asIdidforthewrt_ptr()
for(inti=0;i<no_msgs_;i++)
{
mq_>dequeue_head(mb);
ACE_DEBUG((LM_INFO,"DQ'ddata%s\n",mb>rd_ptr()));
//Releasethememoryassociatedwiththemb
mb>release();
}
return0;
}
intmain(intargc,char*argv[])
{
if(argc<2)
ACE_ERROR_RETURN((LM_ERROR,"Usage%snum_msgs",argv[0]),1);
QTesttest(ACE_OS::atoi(argv[1]));
if(test.enq_msgs()==1)
ACE_ERROR_RETURN((LM_ERROR,"Programfailure\n"),1);
}

Theaboveexampleillustratesseveralmethodsofthemessagequeueclass.Theexample
consistsofasingleQTestclasswhichinstantiatesamessagequeueofdefaultsizewith
ACE_NULL_SYNCHlocking.Thelocks(amutexandaconditionvariable)areusedby
themessagequeueto

Ensurethesafetyofthereferencecountmaintainedbymessageblocksagainst
raceconditionswhenaccessedbymultiplethreads.

Towakeupallthreadsthataresleepingbecausethemessagequeuewasempty
orfull.

Inthisexample,sincethereisjustasinglethread,thetemplatesynchronizationparameter
for the message queue is set to null (ACE_NULL_SYNCH which means use
ACE_Null_MutexandACE_Null_Condition).Theenq_msgs()methodofQTestisthen
called,whichentersaloopthatcreatesandenqueuesmessagesontothemessagequeue.
Theconstructorof ACE_Message_Block ispassedthesizeofthemessagedata.Using
thisconstructorcausesthememorytobemanagedautomatically(i.e.thememorywillbe
releasedwhenthemessageblockisdeletedi.e.release()d).Thewr_ptristhenobtained
(usingthewr_ptr()accessormethod)anddataiscopiedintothemessageblock.Afterthis
thewr_ptrisadvancedforward.Theenqueue_prio()methodofthemessagequeueisthen
usedtoactuallyenqueuethemessageblockontotheunderlyingmessagequeue.
After no_msgs_ messageblockshavebeencreated,initialized andinsertedontothe
messagequeue,enq_msgs()callsthedeq_msgs()method.Thismethoddequeueseach
of the messages from the message queue using the dequeue_head() method of
136

ACE_Message_Queue. Afterdequeingamessageitsdataisdisplayedandthenthe
messageisrelease()d.

Water Marks
Watermarksareusedinmessagequeuestoindicatewhenthemessagequeuehastoo
much data on it (the message queue has reached the high water mark) or when the
messagequeuehasaninsufficientamountofdataonit(themessagequeuehasreached
its low water mark). Both these marks are used for flow control for example the
low_water_markmaybeusedtoavoidsituationslikethesillywindowsyndromein
TCPandthehigh_water_markmaybeusedtostemorslowdownasenderorproducer
ofdata.
ThemessagequeuesinACEachievethisfunctionalitybymaintainingacountofthetotal
amountofdatainbytesthathasbeenenqueued.Thus,wheneveranewmessageblockis
enqueuedontothemessagequeue,itwillfirstdetermineitslength,thencheckifitcan
enqueuethemessageblock(i.e.makesurethatthemessagequeuedoesntexceeditshigh
watermarkifthisnewmessageblockisenqueued).Ifthemessagequeuecannotenqueue
thedataanditpossessesalock(i.e.ACE_SYNCisusedandnotACE_NULL_SYNCH
asthetemplateparametertothemessagequeue),itwillblockthecalleruntilsufficient
roomisavailable,oruntilthetimeoutintheenqueuemethodexpires.Ifthetimeout
expiresorifthequeuepossessedanulllock,thentheenqueuemethodwillreturnwitha
valueof1,indicatingthatitwasunabletoenqueuethemessage.
Similarly,whenthedequeue_headmethodofACE_Message_Queueiscalled,itchecks
tomakesurethatafterdequeuingtheamountofdataleftismorethenthelowwater
mark.Ifthisisnotthecase, itblocksifthequeuehasalockotherwiseitreturns1,
indicatingfailure(thesamewaytheenqueuemethodswork).
Therearetwomethodswhichcanbeusedtosetandgetthewatermarksthatare
//getthehighwatermark
size_thigh_water_mark(void)
//setthehighwatermark
voidhigh_water_mark(size_thwm);
//getthelowwater_mark
size_tlow_water_mark(void)
//setthelowwater_mark
voidlow_water_mark(size_tlwm)

137

Using Message Queue Iterators


Asiscommonwithothercontainerclasses,forwardandreverseiteratorsareavailablefor
messagequeuesinACE.Theseiteratorsarenamed ACE_Message_Queue_Iterator and
ACE_Message_Queue_Reverse_Iterator. Each of these require a template parameter
whichisusedforsynchronizationwhiletraversingthemessagequeue.Ifmultiplethreads
areusingthemessagequeue,thenthisshouldbesettoACE_SYNCHotherwiseitmay
besettoACE_NULL_SYNCH.Whenaniteratorobjectiscreated,itsconstructormustbe
passedareferencetothemessagequeuewewishittoiterateover.
Thefollowingexampleillustratesthewatermarksandtheiterators:
Example2
#includeace/Message_Queue.h
#includeace/Get_Opt.h
#includeace/Malloc_T.h
#defineSIZE_BLOCK1
classArgs{
public:
Args(intargc,char*argv[],int&no_msgs,ACE_Message_Queue<ACE_NULL_SYNCH>*&mq){
ACE_Get_Optget_opts(argc,argv,h:l:t:n:xsd);
while((opt=get_opts())!=1)
switch(opt){
casen:
//setthenumberofmessageswewishtoenqueueanddequeue
no_msgs=ACE_OS::atoi(get_opts.optarg);
ACE_DEBUG((LM_INFO,NumberofMessages%d\n,no_msgs));
break;
caseh:
//setthehighwatermark
hwm=ACE_OS::atoi(get_opts.optarg);
mq>high_water_mark(hwm);
ACE_DEBUG((LM_INFO,HighWaterMark%dmsgs\n,hwm));
break;
casel:
//setthelowwatermark
lwm=ACE_OS::atoi(get_opts.optarg);
mq>low_water_mark(lwm);
ACE_DEBUG((LM_INFO,LowWaterMark%dmsgs\n,lwm));
break;
default:
ACE_DEBUG((LM_ERROR,
Usagen<no.messages>h<hwm>l<lwm>\n));
break;
}
}
private:
intopt;

138

inthwm;
intlwm;
};

classQTest{
public:
QTest(intargc,char*argv[]){
//Firstcreateamessagequeueofdefaultsize.
if(!(this>mq_=newACE_Message_Queue<ACE_NULL_SYNCH>()))
ACE_DEBUG((LM_ERROR,Errorinmessagequeueinitialization\n));
//Usetheargumentstosetthewatermarksandthenoofmessages
args_=newArgs(argc,argv,no_msgs_,mq_);
}
intstart_test(){
for(inti=0;i<no_msgs_;i++){
//Createanewmessageblockofdatabuffersize1
ACE_Message_Block*mb=newACE_Message_Block(SIZE_BLOCK);
//Insertdataintothemessageblockusingtherd_ptr
*mb>wr_ptr()=i;
//Becarefultoadvancethewr_ptr
mb>wr_ptr(1);
//Enqueuethemessageblockontothemessagequeue
if(this>mq_>enqueue_prio(mb)==1){
ACE_DEBUG((LM_ERROR,\nCouldnotenqueueontomq!!\n));
return1;
}
ACE_DEBUG((LM_INFO,EQddata:%d\n,*mb>rd_ptr()));
}
//Usetheiteratorstoread
this>read_all();
//Dequeueallthemessages
this>dequeue_all();
return0;
}
voidread_all(){
ACE_DEBUG((LM_INFO,No.ofMessagesonQ:%dBytesonQ:%d\n
,mq_>message_count(),mq_>message_bytes()));
ACE_Message_Block*mb;
//Usetheforwarditerator
ACE_DEBUG((LM_INFO,\n\nBeginningForwardRead\n));
ACE_Message_Queue_Iterator<ACE_NULL_SYNCH>mq_iter_(*mq_);
while(mq_iter_.next(mb)){
mq_iter_.advance();
ACE_DEBUG((LM_INFO,Readdata%d\n,*mb>rd_ptr()));

139

}
//Usethereverseiterator
ACE_DEBUG((LM_INFO,\n\nBeginningReverseRead\n));
ACE_Message_Queue_Reverse_Iterator<ACE_NULL_SYNCH>
mq_rev_iter_(*mq_);
while(mq_rev_iter_.next(mb)){
mq_rev_iter_.advance();
ACE_DEBUG((LM_INFO,Readdata%d\n,*mb>rd_ptr()));
}
}
voiddequeue_all(){
ACE_DEBUG((LM_INFO,\n\nBeginningDQ\n));
ACE_DEBUG((LM_INFO,No.ofMessagesonQ:%dBytesonQ:%d\n,
mq_>message_count(),mq_>message_bytes()));
ACE_Message_Block*mb;
//dequeuetheheadofthemessagequeueuntilnomoremessages
//areleft
for(inti=0;i<no_msgs_;i++){
mq_>dequeue_head(mb);
ACE_DEBUG((LM_INFO,DQddata%d\n,*mb>rd_ptr()));
}
}
private:
Args*args_;
ACE_Message_Queue<ACE_NULL_SYNCH>*mq_;
intno_msgs_;
};

intmain(intargc,char*argv[]){
QTesttest(argc,argv);
if(test.start_test()<0)
ACE_DEBUG((LM_ERROR,Programfailure\n));
}

ThisexampleusestheACE_Get_Optclass(SeeAppendixformoreonthisutilityclass)
toobtainthelowandhighwatermarks(intheArgsclass).Thelowandhighwatermarks
aresetusingthe low_water_mark()andhigh_water_mark()accessorfunctions.Besides
this,thereisa read_all() methodwhichusestheACE_Message_Queue_Iterator and
ACE_Message_Queue_Reverse_Iterator to first read in the forward and then in the
reversedirection.

140

Dynamic or Real-Time Message Queues


Aswasmentionedabove,dynamicmessagequeuesarequeuesinwhichthepriorityof
themessagesenqueuedchangewithtime.Suchmessagequeuesarethusinherentlymore
usefulinrealtimeapplications,wheresuchkindofbehaviorisdesirable.
ACEcurrentlyprovidesfortwotypesofdynamicmessagequeues,deadlinebasedand
laxitybased(see[IX]).Thedeadlinebasedmessagequeuesusethedeadlinesofeachof
themessagestosettheirpriority.Themessageblockonthequeuewhichhastheearliest
deadline willbe dequeuedfirst when the dequeue_head() method is called using the
earliestdeadlinefirstalgorithm.Thelaxitybasedmessagequeues,however,useboth
execution time and deadline together to calculate the laxity, which is then used to
prioritizeeachmessageblock.Thelaxityisuseful,aswhenschedulingbydeadlineit
maybepossiblethatataskisscheduledwhichhastheearliestdeadline,buthassucha
longexecutiontimethatitwillnotcompleteevenifitisscheduledimmediately.This
negativelyaffectsothertasks,sinceitmayblockouttaskswhichareschedulable.The
laxitywilltakeintoaccountthislongexecutiontimeandmakesurethatifthetaskwill
notcomplete,thatitisnotscheduled.Theschedulinginlaxityqueuesisbasedonthe
minimumlaxityfirstalgorithm.
Bothlaxitybasedmessagequeuesanddeadlinebasedmessagequeuesareimplemented
asACE_Dynamic_Message_Queues.ACEusestheSTRATEGYpatterntoprovidefor
dynamicqueueswithdifferentschedulingcharacteristics.Eachmessagequeueusesa
differentstrategyobjecttodynamicallysetprioritiesofthemessagesonthemessage
queue. These strategy objects each encapsulate a different algorithm to calculate
priorities based onexecution time, deadlines, etc., and are called to do so whenever
messages are enqueued or removed from the message queue. (For more on the
STRATEGYpatternpleaseseethereferenceDesignPatterns).Themessagestrategy
patterns derive from ACE_Dynamic_Message_Strategy and currently there are two
strategies

available:
ACE_Laxity_Message_Strategy
and
ACE_Deadline_Message_Strategy. Therefore, to create a laxitybased dynamic
message queue, an ACE_Laxity_Message_Strategy object must be created first.
Subsequently,anACE_Dynamic_Message_Queueobjectshouldbeinstantiated,whichis
passedthenewstrategyobjectasoneoftheparameterstoitsconstructor.

141

ACE_Dynamic_Message_Queue

ACE_Laxity_Message_Strategy

The Dynamic Message


Queue uses a Strategy
Object to set the dynamic
priorities in the queue.

Creating Message Queues


Tosimplifythecreationofthesedifferenttypesofmessagequeues,ACEprovidesfora
concrete message queue factory named ACE_Message_Queue_Factory, which creates
messagequeuesoftheappropriatetypeusingavariantofthe FACTORYMETHOD
pattern.(FormoreontheFACTORYMETHOD patternpleaseseereferenceDesign
Patterns).Themessagequeuefactoryhasthreestaticfactorymethodstocreatethree
differenttypesofmessagequeues:
staticACE_Message_Queue<ACE_SYNCH_USE>*
create_static_message_queue()
staticACE_Dynamic_Message_Queue<ACE_SYNCH_USE>*
create_deadline_message_queue();
staticACE_Dynamic_Message_Queue<ACE_SYNCH_USE>*
create_laxity_message_queue();
Eachofthesemethodsreturnsapointertothemessagequeueithasjustcreated.Notice
thatallmethodsarestaticandthatthecreate_static_message_queue()methodreturnsan
ACE_Message_Queue whereas the other two methods return an
ACE_Dynamic_Message_Queue.
This simple example illustrates the creation and use of dynamic and static message
queues.
Example3
#includeace/Message_Queue.h
#includeace/Get_Opt.h
#includeace/OS.h
classArgs{
public:
Args(intargc,char*argv[],int&no_msgs,int&time,ACE_Message_Queue<ACE_NULL_SYNCH>*&mq)
{
ACE_Get_Optget_opts(argc,argv,h:l:t:n:xsd);
while((opt=get_opts())!=1)
switch(opt){

142

caset:
time=ACE_OS::atoi(get_opts.optarg);
ACE_DEBUG((LM_INFO,Time:%d\n,time));
break;
casen:
no_msgs=ACE_OS::atoi(get_opts.optarg);
ACE_DEBUG((LM_INFO,NumberofMessages%d\n,no_msgs));
break;
casex:
mq=ACE_Message_Queue_Factory<ACE_NULL_SYNCH>::
create_laxity_message_queue();
ACE_DEBUG((LM_DEBUG,Creatinglaxityq\n));
break;
cased:
mq=ACE_Message_Queue_Factory<ACE_NULL_SYNCH>::
create_deadline_message_queue();
ACE_DEBUG((LM_DEBUG,Creatingdeadlineq\n));
break;
cases:
mq=ACE_Message_Queue_Factory<ACE_NULL_SYNCH>::
create_static_message_queue();
ACE_DEBUG((LM_DEBUG,Creatingstaticq\n));
break;
caseh:
hwm=ACE_OS::atoi(get_opts.optarg);
mq>high_water_mark(hwm);
ACE_DEBUG((LM_INFO,HighWaterMark%dmsgs\n,hwm));
break;
casel:
lwm=ACE_OS::atoi(get_opts.optarg);
mq>low_water_mark(lwm);
ACE_DEBUG((LM_INFO,LowWaterMark%dmsgs\n,lwm));
break;
default:
ACE_DEBUG((LM_ERROR,Usagespecifyqueuetype\n));
break;
}
}
private:
intopt;
inthwm;
intlwm;
};

classQTest{
public:
QTest(intargc,char*argv[]){
args_=newArgs(argc,argv,no_msgs_,time_,mq_);
array_=newACE_Message_Block*[no_msgs_];
}

143

intstart_test(){
for(inti=0;i<no_msgs_;i++){
ACE_NEW_RETURN(array_[i],ACE_Message_Block(1),1);
set_deadline(i);
set_execution_time(i);
enqueue(i);
}
this>dequeue_all();
return0;
}
//CalltheunderlyingACE_Message_Blockmethodmsg_deadline_time()to
//setthedeadlineofthemessage.
voidset_deadline(intmsg_no){
floattemp=(float)time_/(msg_no+1);
ACE_Time_Valuetv;
tv.set(temp);
ACE_Time_Valuedeadline(ACE_OS::gettimeofday()+tv);
array_[msg_no]>msg_deadline_time(deadline);
ACE_DEBUG((LM_INFO,EQdwithDLine%d:%d,,deadline.sec(),deadline.usec()));
}
//CalltheunderlyingACE_Message_Blockmethodtosettheexecutiontime
voidset_execution_time(intmsg_no){
floattemp=(float)time_/10*msg_no;
ACE_Time_Valuetv;
tv.set(temp);
ACE_Time_Valuextime(ACE_OS::gettimeofday()+tv);
array_[msg_no]>msg_execution_time(xtime);
ACE_DEBUG((LM_INFO,Xtime%d:%d,,xtime.sec(),xtime.usec()));
}
voidenqueue(intmsg_no){
//Setthevalueofdataatthereadposition
*array_[msg_no]>rd_ptr()=msg_no;
//Advancewritepointer
array_[msg_no]>wr_ptr(1);
//Enqueueonthemessagequeue
if(mq_>enqueue_prio(array_[msg_no])==1){
ACE_DEBUG((LM_ERROR,\nCouldnotenqueueontomq!!\n));
return;
}
ACE_DEBUG((LM_INFO,Data%d\n,*array_[msg_no]>rd_ptr()));
}
voiddequeue_all(){
ACE_DEBUG((LM_INFO,BeginningDQ\n));
ACE_DEBUG((LM_INFO,No.ofMessagesonQ:%dBytesonQ:%d\n,
mq_>message_count(),mq_>message_bytes()));
for(inti=0;i<no_msgs_;i++){
ACE_Message_Block*mb;

144

if(mq_>dequeue_head(mb)==1){
ACE_DEBUG((LM_ERROR,\nCouldnotdequeuefrommq!!\n));
return;
}
ACE_DEBUG((LM_INFO,DQddata%d\n,*mb>rd_ptr()));
}
}
private:
Args*args_;
ACE_Message_Block**array_;
ACE_Message_Queue<ACE_NULL_SYNCH>*mq_;
intno_msgs_;
inttime_;
};
intmain(intargc,char*argv[]){
QTesttest(argc,argv);
if(test.start_test()<0)
ACE_DEBUG((LM_ERROR,Programfailure\n));
}

The above example is very similar to the previous examples, but adds the dynamic
messagequeuesintothepicture.Inthe Argsclass,wehaveaddedoptionstocreateall
the different types of message queues using the ACE_Message_Queue_Factory.
Furthermore,twonewmethodshavebeenaddedtothe QTestclasstosetthedeadlines
and execution times of each of the message blocks as they are created
(set_deadline()and set_execution_time()). These methods use the
ACE_Message_Blockmethodsmsg_execution_time()andmsg_deadline_time().Notethat
thesemethodstaketheabsoluteandNOTtherelativetime,whichiswhytheyareusedin
conjunctionwiththeACE_OS::gettimeofday()method.
The deadlines and execution times are set with the help of a time parameter. The
deadlineissetsuchthatthefirstmessagewillhavethelatestdeadlineandshouldbe
scheduled last in the case of deadline message queues. Both the execution time and
deadlinearetakenintoaccountwhenusingthelaxityqueues,however.

145

146

Appendix: Utility Classes


Chapter

Utility classes used in ACE


Address Wrapper Classes
ACE_INET_Addr
The ACE_INET_Addr is a wrapper class around the Internet domain address family
(AF_INET).ThisclassderivesfromACE_AddrinACE.Thevariousconstructorsofthis
classcanbeusedtoinitializetheobjecttohaveacertainIPaddressandport.Besides
this, the class has several set and get methods and has overloaded the comparison
operationsi.e.,==operatorandthe!=operator.Forfurtherdetailsonhowtousethis
classseethereferencemanual.

ACE_UNIX_Addr
TheACE_UNIX_AddrclassisawrapperclassaroundtheUNIXdomainaddressfamily
(AF_UNIX)andalsoderivesfrom ACE_Addr. Thisclasshasfunctionalitysimilartothe
ACE_INET_Addrclass.Forfurtherdetailsseethereferencemanual.

Time wrapper classes


ACE_Time_Value
Logging with ACE_DEBUG and ACE_ERROR
TheACE_DEBUGandACE_ERRORmacrosareusefulmacrosforprintingandlogging
debuganderrorinformation.Theirusagehasbeenillustratedthroughoutthistutorial.
147

Thefollowingformatspecifiersareavailableforthesemacros.Eachoftheseoptionsis
prefixedby'%',asinprintfformatstrings:
FormatSpecifier

Description

'a'

exittheprogramatthispoint(varargumentistheexitstatus!)

'A'

printanACE_timer_tvalue

'c'

printacharacter

'i','d'

printadecimalnumber

'I',

indentaccordingtonestingdepth

'e','E','f','F','g','G'

printadouble

'l'

printlinenumberwhereanerroroccurred

'm'

printthemessagecorrespondingtoerrnovalue

'N'

printfilenamewheretheerroroccurred.

'n'

printthenameoftheprogram(or"<unknown>"ifnotset)

'o'

printasanoctalnumber

'P'

printoutthecurrentprocessid

'p'

printouttheappropriateerrnovaluefromsys_errlist

'Q'

printauint64number

'r'

callthefunctionpointedtobythecorrespondingargument

'R'

printreturnstatus

'S'

print out the appropriate _sys_siglist entry corresponding to


varargument.

's'

printoutacharacterstring

'T'

printtimestampinhour:minute:sec:usecformat.

'D'

print timestamp in month/day/year hour:minute:sec:usec


format.

't'

printthreadid(1ifsinglethreaded)

'u'

printasunsignedint

'w'

printawidecharacter

'W'

printawidecharacterstring

'X','x'

printasahexnumber

'%'

printoutasinglepercentsign,'%'

148

Obtaining command line arguments


ACE_Get_Opt
Thisutilityclassisusedtoobtainargumentsfromtheuserandisbasedonthe getopt()
<3c> functioninstdlib. Theconstructorofthisclassispassedinastringcalledthe
optstring,whichspecifiestheswitchesthattheapplicationwishestorespondto.Ifthe
switchletterisfollowedbyacolon,itmeansthattheswitchalsoexpectsanargument.
Forexample,iftheoptstringisab:c,thentheapplicationexpectaandcwithout
anargumentandbwithanargument.Forexample,theapplicationwouldberunas:
MyApplicationab10c

The() operatorhasbeenoverloadedandisusedtoscantheelementsof argv forthe


optionsspecifiedintheoptionstring.
Thefollowingexamplewillhelpmakeitclearhowtousethisclasstoobtainarguments
fromtheuser.
Example
#include"ace/Get_Opt.h"
intmain(intargc,char*argv[])
{
//Specifyoptionstringsothatswitchesb,d,fandhallexpect
//arguments.Switchesa,c,eandgexpectnoarguments.
ACE_Get_Optget_opt(argc,argv,"ab:cd:ef:gh:");
intc;

//Processthescannedoptionswiththehelpoftheoverloaded()
//operator.
while((c=get_opt())!=EOF)
switch(c)
{
case'a':
ACE_DEBUG((LM_DEBUG,"gota\n"));
break;
case'b':
ACE_DEBUG((LM_DEBUG,"gotbwitharg%s\n",
get_opt.optarg));
break;
case'c':
ACE_DEBUG((LM_DEBUG,"gotc\n"));
break;
case'd':
ACE_DEBUG((LM_DEBUG,"gotdwitharg%s\n",
get_opt.optarg));

149

break;
case'e':
ACE_DEBUG((LM_DEBUG,"gote\n"));
break;
case'f':
ACE_DEBUG((LM_DEBUG,"gotfwitharg%s\n",
get_opt.optarg));
break;
case'g':
ACE_DEBUG((LM_DEBUG,"gotg\n"));
break;
case'h':
ACE_DEBUG((LM_DEBUG,"gothwitharg%s\n",
get_opt.optarg));
break;
default:
ACE_DEBUG((LM_DEBUG,"got%c,whichisunrecognized!\n",
c));
break;
}
//optindindicateshowmuchofargvhasbeenscannedsofar,while
//get_opthasntreturnedEOF.Inthiscaseitindicatestheindexin
//argvfromwheretheoptionswitcheshavebeenfullyrecognizedandthe
//remainingelementsmustbescannedbythecalledhimself.
for(inti=get_opt.optind;i<argc;i++)
ACE_DEBUG((LM_DEBUG,"optind=%d,argv[optind]=%s\n",
i,argv[i]));

return0;
}

Forfurtherdetailsonusingthisutilitywrapperclass,pleaseseethereferencemanual.

ACE_Arg_Shifter
ThisADTshiftsknownarguments,oroptions,tothebackofthe argvvector,sodeeper
levelsofargumentparsingcanlocatetheyetunprocessedargumentsatthebeginningof
thevector.
TheACE_Arg_Shiftercopiesthepointersoftheargvvectorintoatemporaryarray.As
theACE_Arg_Shifteriteratesoverthetemp,itplacesknownargumentsintherearofthe
argvandunknownonesinthebeginning.So,afterhavingvisitedalltheargumentsin
thetempvector,ACE_Arg_Shifterhasplacedalltheunknownargumentsintheiroriginal
orderatthefrontofargv.
Thisclassisalsoveryusefulinparsingoptionsfromthecommandline.Thefollowing
examplewillhelpillustratethis:
Example
#include"ace/Arg_Shifter.h"
intmain(intargc,char*argv[]){

ACE_Arg_Shifterarg(argc,argv);

150

while(arg.is_anything_left()){
char*current_arg=arg.get_current();

if(ACE_OS::strcmp(current_arg,"region")==0){

arg.consume_arg();

ACE_OS::printf("<region>=%s\n",arg.get_current());

}
elseif(ACE_OS::strcmp(current_arg,"tag")==0){

arg.consume_arg();

ACE_OS::printf("<tag>=%s\n",arg.get_current());

}
elseif(ACE_OS::strcmp(current_arg,"view_uuid")==0){

arg.consume_arg();
ACE_OS::printf("<view_uuid>=%s\n",arg.get_current());
}
arg.consume_arg();
}
for(inti=0;argv[i]!=NULL;i++)
ACE_DEBUG((LM_DEBUG,Resultantvector:%s\n",argv[i]));
}

151

Iftheaboveexampleisrunasfollows:
.../tests<330>argregionmissouritag10view_uuidsyyidteacherschmidtstudent
tim

Theresultsobtainedare:
<region>missouri
<tag>=10
<view_uuid>=syyid
ResultantVector:tim
ResultantVector:student
ResultantVector:schmidt
ResultantVector:teacher
ResultantVector:syyid
ResultantVector:view_uuid
ResultantVector:10
ResultantVector:tag
ResultantVector:missouri
ResultantVector:region
ResultantVector:./arg

152

References
Chapter

References and Bibliography


Thisisalistofthereferencesthathavebeenmentionedinthetext

153

[] An introduction to programming with threads, Andrew Birell, Digital Systems Research Center, 1989.
[ ] Active Object, An object behavioral Pattern for Concurrent Programming, Douglas C. Schmidt, Greg Lavender.
Pattern Languages of Programming Design 2, Addison Wesley 1996.
III
[ ] A model of Concurrent Computation in Distributed Systems. MIT Press, 1986.
(Also see: http://www-osl.cs.uiuc.edu/)
IV
[] A Polymorphic Future and First-Class Function Type for Concurrent Object-Oriented Programming in C++.
R.G. Lavender and D.G. Kafura. Forthcoming 1995.
(Also see: http://www.cs.utexas.edu/users/lavender/papers/futures.ps)
V
[] Foundation Patterns, Dwight Deugo. Proceedings PLoP 98.
VI
[] Design Patterns: Elements of reusable Object-Oriented Software, Erich Gamma, Richard Helm, Ralph Johnson
and John Vlissides. Addison Wesley Longman Publishing 1995.
VII
[] "Hashed and Hierarchical Timing Wheels: data structures to efficiently implement a timer facility", George
Varghese. IEEE Trans Networking, Dec 97. Revised from a 1984 SOSP paper; used in X kernel, Siemens and DEC OS
etc.
VIII
[] The x-Kernel: An architecture for implementing network protocols. N. C. Hutchinson and L. L. Peterson. IEEE
Transactions on Software Engineering, 17(1):64-76, Jan. 1991.
( Also see http://www.cs.arizona.edu/xkernel)
IX
[ ] Evaluating Strategies for Real-Time CORBA Dynamic Scheduling, Douglas Schmidt, Chris Gill and David
Levine (updated June 20th), Submitted to the International Journal of Time-Critical Computing Systems, special issue
on Real-Time Middleware, guest editor Wei Zhao.
II

Вам также может понравиться