ACE Tutorial
ACE Tutorial
Umar Syyid
([email protected])
Acknowledgments
I would like to thank the following people for their assistance in making this tutorial possible,
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
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
..
..
..
..
.
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
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:
Memory management.
Timers
Signals
Thread management
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:
Each of these layers is shown in the figure below and described in the following sections.
..
..
..
..
.
The C++ Wrapper
Facade Layer
TheC++wrapperfacadelayerincludesC++classesthatcanbeusedtobuildhighly
portableandtypesafeC++applications.ThisisthelargestpartoftheACEtoolkitand
includesapproximately50%ofthetotalsourcecode.C++wrapperclassesareavailable
for:
..
..
..
.. 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.
..
..
..
.
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
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
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
ACE_SOCK_IO
ACE_SOCK_Stream
UsedtoprovideTCP(TransmissionControlProtocol)based
connectionoriented messaging service. Derives from
ACE_SOCK_IOandprovidesfurtherwrappermethods.
ACE_SOCK_CODgram
ACE_SOCK_Dgram_Mcast
ACE_SOCK_Dgram_Bcast
Inthefollowingsections,wewillillustratehowtheIPC_SAPwrapperclasssesareused
directlytohandleinterprocesscommunication.Rememberthatthisisjustthetipofthe
iceberginACE.Allthegoodpatternorientedtoolscomeinlaterchaptersofthistutorial.
{
//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.
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();
}
15
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);
}
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
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.
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
Lock Class
ACE_Malloc
Chunks maintained by
Allocator and divided
into blocks,for giving to
clients.
OS
Client
ACE_Malloc
Chunks for
Allocator
Actual
Allocation
Underlying
Memory
Pool
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
ACE_Lite_MMAP_
Memory_Pool
ACE_LITE_MMAP_MEMORY_POOL
25
reliability.
ACE_Sbrk_
Memory_Pool
ACE_SBRK_MEMORY_POOL
ACE_Shared_
Memory_Pool
ACE_SHARED_MEMORY_POOL
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;
}
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.
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;
}
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.
Description
ACE_Mutex
ACE_Thread_Mutex
ACE_Process_Mutex
ACE_Null_Mutex
ACE_RW_Mutex
writing,thusenablingmultiplereaderstoreadwhilenoone
iswriting.
ACE_RW_Thread_Mutex
ACE_RW_Process_Mutex
ACE_Semaphore
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
ACE_Lock
ACE_Lock_Adapter
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
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;
}
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
Description
ACE_Guard
ACE_Read_Guard
ACE_Write_Guard
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.
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.
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..
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
}
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).
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.
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
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.
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.
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.
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
Reactor Components
Application
Application
Specific
EventHandler
Application
Specific
Application
Specific
EventHandler
EventHandler
Application
Specific
EventHandler
Framework
Timer Queue
Handle Table
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 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()
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();
}
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()
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.
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()
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
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().
None.
Used with
register_handler()
register_handler()
register_handler()
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).
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.
Finegranularitytimersarerequired.
Thenumberofoutstandingtimersatanyonetimecanpotentiallybeverylarge.
Thealgorithmisimplementedusinghardwareinterruptswhicharetooexpensive.
ACEallowstheusertochoosefromamongseveraltimerswhichpreexistinACE,orto
developtheirowntimerstoaninterfacedefinedfortimers.Thedifferenttimersavailable
inACEaredetailedinthefollowingtable:
78
Timer
ACE_Timer_Heap
ACE_Timer_List
ACE_Timer_Hash
ACE_Timer_Wheel
Performance
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
84
Helper object
Helper object
Helper object
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;
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:
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;
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.
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
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.
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);
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.
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
Asnotedabove,thetuningisdonebyoverloadingbridgemethodsintheACE_Acceptor
orACE_Connectorclasses.ACEhasbeendesignedsothatsuchoverloadingandtuning
canbedoneveryeasily.
To modify the
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
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.
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
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.
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
Framework Components
TheServiceConfiguratorinACEconsistsofthefollowingcomponents:
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()
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.
Astaticserviceisinitializedas:
staticservice_nameparameters_sent_to_service_object
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
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();
}
//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
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
ACE_Message_Block2
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.
132
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.
133
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
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
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
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
ACE_UNIX_Addr
TheACE_UNIX_AddrclassisawrapperclassaroundtheUNIXdomainaddressfamily
(AF_UNIX)andalsoderivesfrom ACE_Addr. Thisclasshasfunctionalitysimilartothe
ACE_INET_Addrclass.Forfurtherdetailsseethereferencemanual.
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'
's'
printoutacharacterstring
'T'
printtimestampinhour:minute:sec:usecformat.
'D'
't'
printthreadid(1ifsinglethreaded)
'u'
printasunsignedint
'w'
printawidecharacter
'W'
printawidecharacterstring
'X','x'
printasahexnumber
'%'
printoutasinglepercentsign,'%'
148
//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
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