diff options
author | schmidt <douglascraigschmidt@users.noreply.github.com> | 1998-08-30 23:47:15 +0000 |
---|---|---|
committer | schmidt <douglascraigschmidt@users.noreply.github.com> | 1998-08-30 23:47:15 +0000 |
commit | 2a2e62e0f2b414fd6e2e154e0d8c60c6bf146487 (patch) | |
tree | f0bfddf4d121c1bff2d25b73b35392d69393b5c8 /docs/tutorials/007 | |
parent | 97b96ada24a87802fb354c374a57ae5654f2b309 (diff) | |
download | ATCD-2a2e62e0f2b414fd6e2e154e0d8c60c6bf146487.tar.gz |
*** empty log message ***
Diffstat (limited to 'docs/tutorials/007')
-rw-r--r-- | docs/tutorials/007/Makefile | 99 | ||||
-rw-r--r-- | docs/tutorials/007/client_acceptor.cpp | 67 | ||||
-rw-r--r-- | docs/tutorials/007/client_acceptor.h | 137 | ||||
-rw-r--r-- | docs/tutorials/007/client_handler.cpp | 209 | ||||
-rw-r--r-- | docs/tutorials/007/client_handler.h | 156 | ||||
-rwxr-xr-x | docs/tutorials/007/fix.Makefile | 60 | ||||
-rw-r--r-- | docs/tutorials/007/page01.html | 33 | ||||
-rw-r--r-- | docs/tutorials/007/page02.html | 197 | ||||
-rw-r--r-- | docs/tutorials/007/page03.html | 263 | ||||
-rw-r--r-- | docs/tutorials/007/page04.html | 133 | ||||
-rw-r--r-- | docs/tutorials/007/page05.html | 282 | ||||
-rw-r--r-- | docs/tutorials/007/page06.html | 332 | ||||
-rw-r--r-- | docs/tutorials/007/page07.html | 197 | ||||
-rw-r--r-- | docs/tutorials/007/page08.html | 509 | ||||
-rw-r--r-- | docs/tutorials/007/page09.html | 64 | ||||
-rw-r--r-- | docs/tutorials/007/server.cpp | 107 | ||||
-rw-r--r-- | docs/tutorials/007/thread_pool.cpp | 262 | ||||
-rw-r--r-- | docs/tutorials/007/thread_pool.h | 88 |
18 files changed, 3195 insertions, 0 deletions
diff --git a/docs/tutorials/007/Makefile b/docs/tutorials/007/Makefile new file mode 100644 index 00000000000..eaf503868ca --- /dev/null +++ b/docs/tutorials/007/Makefile @@ -0,0 +1,99 @@ +#---------------------------------------------------------------------------- +# $Id$ +#---------------------------------------------------------------------------- + +#---------------------------------------------------------------------------- +# Local macros +#---------------------------------------------------------------------------- + +# You can generally find a Makefile in the ACE examples, tests or the library +# itself that will satisfy our application needs. This one was taken from +# one of the examples. + + # Define the name of the binary we want to create. There has to be + # a CPP file $(BIN).cpp but it doesn't necessarily have to have your + # main() in it. Most of the time, though, it will. +BIN = server + + # Few applications will have a single source file. We use the FILES + # macro to build up a list of additional files to compile. Notice + # that we leave off the extension just as with BIN +FILES = +FILES += client_handler +FILES += client_acceptor +FILES += thread_pool + + # The BUILD macro is used by the ACE makefiles. Basically, it tells + # the system what to build. I don't really know what VBIN is other + # than it is constructed from the value of BIN. Just go with it... +BUILD = $(VBIN) + + # Here we use some GNU make extensions to build the SRC macro. Basically, + # we're just adding .cpp to the value of BIN and for each entry of the + # FILES macro. +SRC = $(addsuffix .cpp,$(BIN)) $(addsuffix .cpp,$(FILES)) + + # This is used by my Indent target below. It's not a part of standard + # ACE and you don't need it yourself. +HDR = *.h + +#---------------------------------------------------------------------------- +# Include macros and targets +#---------------------------------------------------------------------------- + + # This is where the real power lies! These included makefile components + # are similar to the C++ templates in ACE. That is, they do a tremendous + # amount of work for you and all you have to do is include them. + # As a matter of fact, in our project, I created a single file named + # "app.mk" that includes all of these. Our project makefiles then just + # need to include app.mk to get everything they need. + +include $(ACE_ROOT)/include/makeinclude/wrapper_macros.GNU +include $(ACE_ROOT)/include/makeinclude/macros.GNU +include $(ACE_ROOT)/include/makeinclude/rules.common.GNU +include $(ACE_ROOT)/include/makeinclude/rules.nonested.GNU +include $(ACE_ROOT)/include/makeinclude/rules.lib.GNU +include $(ACE_ROOT)/include/makeinclude/rules.bin.GNU +include $(ACE_ROOT)/include/makeinclude/rules.local.GNU + +#---------------------------------------------------------------------------- +# Local targets +#---------------------------------------------------------------------------- + + # Sometimes I like to reformat my code to make it more readable. This is + # more useful for the comments than anything else. Unfortunately, the + # "indent" program doesn't quite grok C++ so I have to post-process it's + # output just a bit. +Indent : # + for i in $(SRC) $(HDR) ; do \ + indent -npsl -l80 -fca -fc1 -cli0 -cdb < $$i | \ + sed -e 's/: :/::/g' \ + -e 's/^.*\(public:\)/\1/' \ + -e 's/^.*\(protected:\)/\1/' \ + -e 's/^.*\(private:\)/\1/' \ + -e 's/:\(public\)/ : \1/' \ + -e 's/:\(protected\)/ : \1/' \ + -e 's/:\(private\)/ : \1/' \ + > $$i~ ;\ + mv $$i~ $$i ;\ + done + + # One of the targets in the ACE makefiles is "depend". It will invoke + # your compiler in a way that will generate a list of dependencies for + # you. This is a great thing! Unfortunately, it puts all of that mess + # directly into the Makefile. I prefer my Makefile to stay clean and + # uncluttered. The perl script referenced here pulls the dependency + # stuff back out of the Makefile and into a file ".depend" which we then + # include just like the makefile components above. +Depend : depend + perl fix.Makefile + +#---------------------------------------------------------------------------- +# Dependencies +#---------------------------------------------------------------------------- + + # Don't put anything below here. Between the "depend" target and fix.Makefile + # it's guaranteed to be lost! + + # This is inserted by the fix.Makefile script +include .depend diff --git a/docs/tutorials/007/client_acceptor.cpp b/docs/tutorials/007/client_acceptor.cpp new file mode 100644 index 00000000000..6cc90612558 --- /dev/null +++ b/docs/tutorials/007/client_acceptor.cpp @@ -0,0 +1,67 @@ + +// $Id$ + +#include "client_acceptor.h" + +/* + Construct ourselves with the chosen concurrency strategy. Notice that we also + set our Thread_Pool reference to our private instance. + */ +Client_Acceptor::Client_Acceptor( int _concurrency ) + : concurrency_(_concurrency) + ,the_thread_pool_(private_thread_pool_) +{ +} + +/* + Construct ourselves with a reference to somebody else' Thread_Pool. Obvioulsy + our concurrency strategy is "thread_pool_" at this point. + */ +Client_Acceptor::Client_Acceptor( Thread_Pool & _thread_pool ) + : concurrency_(thread_pool_) + ,the_thread_pool_(_thread_pool) +{ +} + +/* + When we're destructed, we may need to cleanup after ourselves. If we're running + with a thread pool that we own, it is up to us to close it down. + */ +Client_Acceptor::~Client_Acceptor( void ) +{ + if( this->concurrency() == thread_pool_ && thread_pool_is_private() ) + { + thread_pool()->close(); + } +} + +/* + Similar to the destructor (and close() below) it is necessary for us to open the + thread pool in some circumstances. + + Notice how we delegate most of the open() work to the open() method of our baseclass. + */ +int Client_Acceptor::open( const ACE_INET_Addr & _addr, ACE_Reactor * _reactor, int _pool_size ) +{ + if( this->concurrency() == thread_pool_ && thread_pool_is_private() ) + { + thread_pool()->open(_pool_size); + } + + return inherited::open(_addr,_reactor); +} + +/* + Here again we find that we have to manage the thread pool. Like open() we also delegate + the other work to our baseclass. + */ +int Client_Acceptor::close(void) +{ + if( this->concurrency() == thread_pool_ && thread_pool_is_private() ) + { + thread_pool()->close(); + } + + return inherited::close(); +} + diff --git a/docs/tutorials/007/client_acceptor.h b/docs/tutorials/007/client_acceptor.h new file mode 100644 index 00000000000..e1c0eded6fd --- /dev/null +++ b/docs/tutorials/007/client_acceptor.h @@ -0,0 +1,137 @@ + +// $Id$ + +#ifndef CLIENT_ACCEPTOR_H +#define CLIENT_ACCEPTOR_H + +/* + The ACE_Acceptor<> template lives in the ace/Acceptor.h header file. You'll + find a very consitent naming convention between the ACE objects and the + headers where they can be found. In general, the ACE object ACE_Foobar will + be found in ace/Foobar.h. + */ + +#include "ace/Acceptor.h" + +/* + Since we want to work with sockets, we'll need a SOCK_Acceptor to allow the + clients to connect to us. + */ +#include "ace/SOCK_Acceptor.h" + +/* + The Client_Handler object we develop will be used to handle clients once + they're connected. The ACE_Acceptor<> template's first parameter requires + such an object. In some cases, you can get by with just a forward + declaration on the class, in others you have to have the whole thing. + */ +#include "client_handler.h" + +/* + Parameterize the ACE_Acceptor<> such that it will listen for socket + connection attempts and create Client_Handler objects when they happen. In + Tutorial 001, we wrote the basic acceptor logic on our own before we + realized that ACE_Acceptor<> was available. You'll get spoiled using the + ACE templates because they take away a lot of the tedious details! + */ +typedef ACE_Acceptor < Client_Handler, ACE_SOCK_ACCEPTOR > Client_Acceptor_Base; + +#include "thread_pool.h" + +/* + This time we've added quite a bit more to our acceptor. In addition to + providing a choice of concurrency strategies, we also maintain a Thread_Pool + object in case that strategy is chosen. The object still isn't very complex + but it's come a long way from the simple typedef we had in Tutorial 5. + + Why keep the thread pool as a member? If we go back to the inetd concept + you'll recall that we need several acceptors to make that work. We may have + a situation in which our different client types requre different resources. + That is, we may need a large thread pool for some client types and a smaller + one for others. We could share a pool but then the client types may have + undesirable impact on one another. + + Just in case you do want to share a single thread pool, there is a constructor + below that will let you do that. + */ +class Client_Acceptor : public Client_Acceptor_Base +{ +public: + typedef Client_Acceptor_Base inherited; + + /* + Now that we have more than two strategies, we need more than a boolean + to tell us what we're using. A set of enums is a good choice because + it allows us to use named values. Another option would be a set of + static const integers. + */ + enum concurrency_t + { + single_threaded_, + thread_per_connection_, + thread_pool_ + }; + + /* + The default constructor allows the programmer to choose the concurrency + strategy. Since we want to focus on thread-pool, that's what we'll use + if nothing is specified. + */ + Client_Acceptor( int _concurrency = thread_pool_ ); + + /* + Another option is to construct the object with an existing thread pool. + The concurrency strategy is pretty obvious at that point. + */ + Client_Acceptor( Thread_Pool & _thread_pool ); + + /* + Our destructor will take care of shutting down the thread-pool + if applicable. + */ + ~Client_Acceptor( void ); + + /* + Open ourselves and register with the given reactor. The thread pool size + can be specified here if you want to use that concurrency strategy. + */ + int open( const ACE_INET_Addr & _addr, ACE_Reactor * _reactor, + int _pool_size = Thread_Pool::default_pool_size_ ); + + /* + Close ourselves and our thread pool if applicable + */ + int close(void); + + /* + What is our concurrency strategy? + */ + int concurrency(void) + { return this->concurrency_; } + + /* + Give back a pointer to our thread pool. Our Client_Handler objects + will need this so that their handle_input() methods can put themselves + into the pool. Another alternative would be a globally accessible + thread pool. ACE_Singleton<> is a way to achieve that. + */ + Thread_Pool * thread_pool(void) + { return & this->the_thread_pool_; } + + /* + Since we can be constructed with a Thread_Pool reference, there are times + when we need to know if the thread pool we're using is ours or if we're + just borrowing it from somebody else. + */ + int thread_pool_is_private(void) + { return &the_thread_pool_ == &private_thread_pool_; } + +protected: + int concurrency_; + + Thread_Pool private_thread_pool_; + + Thread_Pool & the_thread_pool_; +}; + +#endif // CLIENT_ACCEPTOR_H diff --git a/docs/tutorials/007/client_handler.cpp b/docs/tutorials/007/client_handler.cpp new file mode 100644 index 00000000000..d14c8ce3144 --- /dev/null +++ b/docs/tutorials/007/client_handler.cpp @@ -0,0 +1,209 @@ + +// $Id$ + +/* + Since this is the third time we've seen most of this, I'm going to strip out almost + all of the comments that you've already seen. That way, you can concentrate on the + new items. + */ + +#include "client_acceptor.h" +#include "client_handler.h" + +/* + We're going to be registering and unregistering a couple of times. To make sure that + we use the same flags every time, I've created these handy macros. + */ +#define REGISTER_MASK ACE_Event_Handler::READ_MASK +#define REMOVE_MASK (ACE_Event_Handler::READ_MASK | ACE_Event_Handler::DONT_CALL) + +/* + Our constructor still doesn't really do anything. We simply initialize the acceptor + pointer to "null" and get our current thread id. The static self() method of ACE_Thread + will return you a thread id native to your platform. + */ +Client_Handler::Client_Handler (void) + : client_acceptor_(0) + ,creator_(ACE_Thread::self()) +{ +} + +Client_Handler::~Client_Handler (void) +{ +} + +/* + Query our acceptor for the concurrency strategy. Notice that we don't bother + to check that our acceptor pointer is valid. That is proably a bad idea... + */ +int Client_Handler::concurrency(void) +{ + return this->client_acceptor()->concurrency(); +} + +/* + And here we ask the acceptor about the thread pool. + */ +Thread_Pool * Client_Handler::thread_pool(void) +{ + return this->client_acceptor()->thread_pool(); +} + +/* + The destroy() method hasn't changed since we wrote it back in Tutorial 5. + */ +void Client_Handler::destroy (void) +{ + this->peer ().close (); + + this->reactor ()->remove_handler (this, REMOVE_MASK ); + + delete this; +} + +/* + Back to our open() method. This is straight out of Tutorial 6. There's + nothing additional here for the thread-pool implementation. + */ +int Client_Handler::open (void *_acceptor) +{ + client_acceptor( (Client_Acceptor *) _acceptor ); + + if( concurrency() == Client_Acceptor::thread_per_connection_ ) + { + return this->activate(); + } + + this->reactor (client_acceptor()->reactor ()); + + ACE_INET_Addr addr; + + if (this->peer ().get_remote_addr (addr) == -1) + { + return -1; + } + + if (this->reactor ()->register_handler (this, REGISTER_MASK) == -1) + { + ACE_ERROR_RETURN ((LM_ERROR, "(%P|%t) can't register with reactor\n"), -1); + } + + ACE_DEBUG ((LM_DEBUG, "(%P|%t) connected with %s\n", addr.get_host_name ())); + + return 0; +} + +/* + In the open() method, we registered with the reactor and requested to be + notified when there is data to be read. When the reactor sees that activity + it will invoke this handle_input() method on us. As I mentioned, the _handle + parameter isn't useful to us but it narrows the list of methods the reactor + has to worry about and the list of possible virtual functions we would have + to override. + + You've read that much before... Now we have to do some extra stuff in case + we're using the thread-pool implementation. If we're called by our creator + thread then we must be in the reactor. In that case, we arrange to be put + into the thread pool. If we're not in the creator thread then we must be + in the thread pool and we can do some work. + */ +int Client_Handler::handle_input (ACE_HANDLE _handle) +{ + ACE_UNUSED_ARG (_handle); + + /* + Check our strategy. If we're using the thread pool and we're in the creation + thread then we know we were called by the reactor. + */ + if( concurrency() == Client_Acceptor::thread_pool_ ) + { + if( ACE_Thread::self() == creator_ ) + { + /* + Remove ourselves from the reactor and ask to be put into the thread pool's + queue of work. (You should be able to use suspend_handler() but I've had + problems with that.) + */ + this->reactor()->remove_handler( this, REMOVE_MASK ); + return this->thread_pool()->enqueue(this); + } + } + + /* + Any strategy other than thread-per-connection will eventually get here. If we're in the + single-threaded implementation or the thread-pool, we still have to pass this way. + */ + + char buf[128]; + memset (buf, 0, sizeof (buf)); + + /* + Invoke the process() method to do the work but save it's return value instead + of returning it immediately. + */ + + int rval = this->process(buf,sizeof(buf)); + + /* + Now, we look again to see if we're in the thread-pool implementation. If so then we + need to re-register ourselves with the reactor so that we can get more work when it + is available. (If suspend_handler() worked then we would use resume_handler() here.) + */ + if( concurrency() == Client_Acceptor::thread_pool_ ) + { + if( rval != -1 ) + { + this->reactor()->register_handler( this, REGISTER_MASK ); + } + } + + /* + Return the result of process() + */ + return(rval); +} + +int Client_Handler::handle_close (ACE_HANDLE _handle, ACE_Reactor_Mask _mask) +{ + ACE_UNUSED_ARG (_handle); + ACE_UNUSED_ARG (_mask); + + this->destroy (); + return 0; +} + +int Client_Handler::svc(void) +{ + char buf[128]; + memset (buf, 0, sizeof (buf)); + + while( 1 ) + { + if( this->process(buf,sizeof(buf)) == -1 ) + { + return(-1); + } + } + + return(0); +} + +/* + Once again, we see that the application-level logic has not been at all affected + by our choice of threading models. Of course, I'm not sharing data between threads + or anything. We'll leave locking issues for a later tutorial. + */ +int Client_Handler::process (char *_rdbuf, int _rdbuf_len) +{ + switch (this->peer ().recv (_rdbuf, _rdbuf_len)) + { + case -1: + ACE_ERROR_RETURN ((LM_ERROR, "(%P|%t) %p bad read\n", "client"), -1); + case 0: + ACE_ERROR_RETURN ((LM_ERROR, "(%P|%t) closing daemon (fd = %d)\n", this->get_handle ()), -1); + default: + ACE_DEBUG ((LM_DEBUG, "(%P|%t) from client: %s", _rdbuf)); + } + + return 0; +} diff --git a/docs/tutorials/007/client_handler.h b/docs/tutorials/007/client_handler.h new file mode 100644 index 00000000000..c86012d4b7b --- /dev/null +++ b/docs/tutorials/007/client_handler.h @@ -0,0 +1,156 @@ + +// $Id$ + +#ifndef CLIENT_HANDLER_H +#define CLIENT_HANDLER_H + +/* + Our client handler must exist somewhere in the ACE_Event_Handler object + hierarchy. This is a requirement of the ACE_Reactor because it maintains + ACE_Event_Handler pointers for each registered event handler. You could + derive our Client_Handler directly from ACE_Event_Handler but you still have + to have an ACE_SOCK_Stream for the actually connection. With a direct + derivative of ACE_Event_Handler, you'll have to contain and maintain an + ACE_SOCK_Stream instance yourself. With ACE_Svc_Handler (which is a + derivative of ACE_Event_Handler) some of those details are handled for you. + */ + +#include "ace/Svc_Handler.h" +#include "ace/SOCK_Stream.h" + +class Client_Acceptor; +class Thread_Pool; + +/* + Another feature of ACE_Svc_Handler is it's ability to present the ACE_Task<> + interface as well. That's what the ACE_NULL_SYNCH parameter below is all + about. That's beyond our scope here but we'll come back to it in the next + tutorial when we start looking at concurrency options. + */ +class Client_Handler : public ACE_Svc_Handler < ACE_SOCK_STREAM, ACE_NULL_SYNCH > +{ +public: + + // Constructor... + Client_Handler (void); + + /* + The destroy() method is our preferred method of destruction. We could + have overloaded the delete operator but that is neither easy nor + intuitive (at least to me). Instead, we provide a new method of + destruction and we make our destructor protected so that only ourselves, + our derivatives and our friends can delete us. It's a nice + compromise. + */ + void destroy (void); + + /* + Most ACE objects have an open() method. That's how you make them ready + to do work. ACE_Event_Handler has a virtual open() method which allows us + to create this overrride. ACE_Acceptor<> will invoke this method after + creating a new Client_Handler when a client connects. Notice that the + parameter to open() is a void*. It just so happens that the pointer + points to the acceptor which created us. You would like for the parameter + to be an ACE_Acceptor<>* but since ACE_Event_Handler is generic, that + would tie it too closely to the ACE_Acceptor<> set of objects. In our + definition of open() you'll see how we get around that. + */ + int open (void *_acceptor); + + /* + When there is activity on a registered handler, the handle_input() method + of the handler will be invoked. If that method returns an error code (eg + -- -1) then the reactor will invoke handle_close() to allow the object to + clean itself up. Since an event handler can be registered for more than + one type of callback, the callback mask is provided to inform + handle_close() exactly which method failed. That way, you don't have to + maintain state information between your handle_* method calls. The _handle + parameter is explained below... + */ + int handle_close (ACE_HANDLE _handle, ACE_Reactor_Mask _mask); + + /* + When we register with the reactor, we're going to tell it that we want to + be notified of READ events. When the reactor sees that there is read + activity for us, our handle_input() will be invoked. The _handleg + provided is the handle (file descriptor in Unix) of the actual connection + causing the activity. Since we're derived from ACE_Svc_Handler<> and it + maintains it's own peer (ACE_SOCK_Stream) object, this is redundant for + us. However, if we had been derived directly from ACE_Event_Handler, we + may have chosen not to contain the peer. In that case, the _handleg + would be important to us for reading the client's data. + */ + int handle_input (ACE_HANDLE _handle); + +protected: + + /* + If the Client_Acceptor which created us has chosen a thread-per-connection + strategy then our open() method will activate us into a dedicate thread. + The svc() method will then execute in that thread performing some of the + functions we used to leave up to the reactor. + */ + int svc(void); + + /* + This has nothing at all to do with ACE. I've added this here as a worker + function which I will call from handle_input(). That allows me to + introduce concurrencly in later tutorials with a no changes to the worker + function. You can think of process() as application-level code and + everything elase as application-framework code. + */ + int process (char *_rdbuf, int _rdbuf_len); + + /* + We don't really do anything in our destructor but we've declared it to be + protected to prevent casual deletion of this object. As I said above, I + really would prefer that everyone goes through the destroy() method to get + rid of us. + */ + ~Client_Handler (void); + + /* + When we get to the definition of Client_Handler we'll see that there are + several places where we go back to the Client_Acceptor for information. + It is generally a good idea to do that through an accesor rather than + using the member variable directly. + */ + Client_Acceptor * client_acceptor( void ) + { return this->client_acceptor_; } + + /* + And since you shouldn't access a member variable directly, neither should you + set (mutate) it. Although it might seem silly to do it this way, you'll thank + yourself for it later. + */ + void client_acceptor( Client_Acceptor * _client_acceptor ) + { this->client_acceptor_ = _client_acceptor; } + + /* + The concurrency() accessor tells us the current concurrency strategy. It actually + queries the Client_Acceptor for it but by having the accessor in place, we could + change our implementation without affecting everything that needs to know. + */ + int concurrency(void); + + /* + Likewise for access to the Thread_Pool that we belong to. + */ + Thread_Pool * thread_pool(void); + + + Client_Acceptor * client_acceptor_; + + /* + For some reason I didn't create accessor/mutator methods for this. So much for + consistency.... + + This variable is used to remember the thread in which we were created: the "creator" + thread in other words. handle_input() needs to know if it is operating in the + main reactor thread (which is the one that created us) or if it is operating in + one of the thread pool threads. More on this when we get to handle_input(). + */ + ACE_thread_t creator_; +}; + +#endif // CLIENT_HANDLER_H diff --git a/docs/tutorials/007/fix.Makefile b/docs/tutorials/007/fix.Makefile new file mode 100755 index 00000000000..e99c194114a --- /dev/null +++ b/docs/tutorials/007/fix.Makefile @@ -0,0 +1,60 @@ +#!/usr/bin/perl + + # Open the Makefile that has been mangled by 'make depend' + # and suck it into a perl array. +open(IF,"<Makefile") || die; +@makefile = <IF>; +close(IF); + + # Now open our .depend file and a temporary Makefile. + # We'll split the original Makefile between these two. +open(DF,">.depend") || die; +open(MF,">Makefile.tmp") || die; + + # For each line we read out of the original file... +foreach (@makefile) { + + # If we're into the dependency section, write the line + # into the .depend file. + # + if( $depend ) { + print DF $_; + } + else { + # If we haven't gotten to the dependency section yet + # then see if the current line is the separator that + # "make depend" causes to be inserted. + # + if( m/^\Q# DO NOT DELETE THIS LINE -- g++dep uses it.\E/ ) { + + # If so, change our "mode" and skip this line. + ++$depend; + next; + } + + # Also skip the "include .depend" that we insert. If we + # don't do this, it is possible to have a bunch of these + # inserted into the output when we read an unmangled Makefile + next if( m/^include .depend/ ); + + # Print the non-dependency info to the temporary Makefile + print MF $_; + } +} + +# Tell our new Makefile to include the dependency file +print MF "include .depend\n"; + +# Close the two output files... +close(DF); +close(MF); + +# Unlink (remove) the original Makefile and rename our +# temporary file. There's obviously room for error checking +# here but we've got the Makefile checked into some revision +# control system anyway. Don't we? + +unlink("Makefile"); +rename("Makefile.tmp","Makefile"); + +exit(0); diff --git a/docs/tutorials/007/page01.html b/docs/tutorials/007/page01.html new file mode 100644 index 00000000000..9402426de3f --- /dev/null +++ b/docs/tutorials/007/page01.html @@ -0,0 +1,33 @@ +<HTML> +<HEAD> + <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> + <META NAME="GENERATOR" CONTENT="Mozilla/4.04 [en] (X11; I; Linux 2.0.32 i486) [Netscape]"> + <META NAME="Author" CONTENT="James CE Johnson"> + <META NAME="Description" CONTENT="A first step towards using ACE productively"> + <TITLE>ACE Tutorial 007</TITLE> +</HEAD> +<BODY TEXT="#000000" BGCOLOR="#FFFFFF" LINK="#000FFF" VLINK="#FF0F0F"> + +<CENTER><B><FONT SIZE=+2>ACE Tutorial 007</FONT></B></CENTER> + +<CENTER><B><FONT SIZE=+2>Creating a thread-pool server</FONT></B></CENTER> + + +<P> +<HR WIDTH="100%"> + +<P>In this tutorial, we're going to extend Tutorial 6 to add a third concurrency +strategy: thread-pool. Like Tutorail 6 did to Tutorial 5, we're +going to keep the existing strategies that we've already created and add +this one in as a "bonus". As you'll see, our basic objects will change +but not by a whole lot. To accomplish this, we'll introduce one new +major object that helps to abstract the thread pool concept. + +<P> +<HR WIDTH="100%"> +<CENTER>[<A HREF="..">Tutorial +Index</A>] [<A HREF="page02.html">Continue +This Tutorial</A>]</CENTER> + +</BODY> +</HTML> diff --git a/docs/tutorials/007/page02.html b/docs/tutorials/007/page02.html new file mode 100644 index 00000000000..c6e7bedec43 --- /dev/null +++ b/docs/tutorials/007/page02.html @@ -0,0 +1,197 @@ +<HTML> +<HEAD> + <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> + <META NAME="GENERATOR" CONTENT="Mozilla/4.04 [en] (X11; I; Linux 2.0.32 i486) [Netscape]"> + <META NAME="Author" CONTENT="James CE Johnson"> + <META NAME="Description" CONTENT="A first step towards using ACE productively"> + <TITLE>ACE Tutorial 007</TITLE> +</HEAD> +<BODY TEXT="#000000" BGCOLOR="#FFFFFF" LINK="#000FFF" VLINK="#FF0F0F"> + +<CENTER><B><FONT SIZE=+2>ACE Tutorial 007</FONT></B></CENTER> + +<CENTER><B><FONT SIZE=+2>Creating a thread-pool server</FONT></B></CENTER> + + +<P> +<HR WIDTH="100%"> + +<P>As usualy, we start with <A HREF="server.cpp">server.cpp</A> +<BR> +<HR WIDTH="100%"> + +<P><FONT FACE="Arial,Helvetica">// $Id: server.cpp,v 1.1 1998/08/30 16:04:12 +jcej Exp $</FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> We try to keep main() very +simple. One of the ways we do that is to push</FONT> +<BR><FONT FACE="Arial,Helvetica"> much of the complicated stuff +into worker objects. In this case, we only</FONT> +<BR><FONT FACE="Arial,Helvetica"> need to include the acceptor +header in our main source file. We let it</FONT> +<BR><FONT FACE="Arial,Helvetica"> worry about the "real work".</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> + +<P><FONT FACE="Arial,Helvetica">#include "client_acceptor.h"</FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> As before, we create a simple +signal handler that will set our finished</FONT> +<BR><FONT FACE="Arial,Helvetica"> flag. There are, of +course, more elegant ways to handle program shutdown</FONT> +<BR><FONT FACE="Arial,Helvetica"> requests but that isn't really +our focus right now, so we'll just do the</FONT> +<BR><FONT FACE="Arial,Helvetica"> easiest thing.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> + +<P><FONT FACE="Arial,Helvetica">static sig_atomic_t finished = 0;</FONT> +<BR><FONT FACE="Arial,Helvetica">extern "C" void handler (int)</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica"> finished = 1;</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> A server has to listen for +clients at a known TCP/IP port. The default ACE</FONT> +<BR><FONT FACE="Arial,Helvetica"> port is 10002 (at least on +my system) and that's good enough for what we</FONT> +<BR><FONT FACE="Arial,Helvetica"> want to do here. Obviously, +a more robust application would take a command</FONT> +<BR><FONT FACE="Arial,Helvetica"> line parameter or read from +a configuration file or do some other clever</FONT> +<BR><FONT FACE="Arial,Helvetica"> thing. Just like the +signal handler above, though, that's what we want to</FONT> +<BR><FONT FACE="Arial,Helvetica"> focus on, so we're taking +the easy way out.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> + +<P><FONT FACE="Arial,Helvetica">static const u_short PORT = ACE_DEFAULT_SERVER_PORT;</FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Finally, we get to main. +Some C++ compilers will complain loudly if your</FONT> +<BR><FONT FACE="Arial,Helvetica"> function signature doesn't +match the prototype. Even though we're not</FONT> +<BR><FONT FACE="Arial,Helvetica"> going to use the parameters, +we still have to specify them.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> + +<P><FONT FACE="Arial,Helvetica">int main (int argc, char *argv[])</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> In our earlier servers, we +used a global pointer to get to the reactor. I've</FONT> +<BR><FONT FACE="Arial,Helvetica"> never really liked that idea, +so I've moved it into main() this time. When</FONT> +<BR><FONT FACE="Arial,Helvetica"> we get to the Client_Handler +object you'll see how we manage to get a</FONT> +<BR><FONT FACE="Arial,Helvetica"> pointer back to this reactor.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> ACE_Reactor reactor;</FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> The acceptor +will take care of letting clients connect to us. It will</FONT> +<BR><FONT FACE="Arial,Helvetica"> also arrange +for a Client_Handler to be created for each new client.</FONT> +<BR><FONT FACE="Arial,Helvetica"> Since we're only +going to listen at one TCP/IP port, we only need one</FONT> +<BR><FONT FACE="Arial,Helvetica"> acceptor. +If we wanted, though, we could create several of these and</FONT> +<BR><FONT FACE="Arial,Helvetica"> listen at several +ports. (That's what we would do if we wanted to rewrite</FONT> +<BR><FONT FACE="Arial,Helvetica"> inetd for +instance.)</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> Client_Acceptor peer_acceptor;</FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Create an ACE_INET_Addr +that represents our endpoint of a connection. We</FONT> +<BR><FONT FACE="Arial,Helvetica"> then open our +acceptor object with that Addr. Doing so tells the acceptor</FONT> +<BR><FONT FACE="Arial,Helvetica"> where to listen +for connections. Servers generally listen at "well known"</FONT> +<BR><FONT FACE="Arial,Helvetica"> addresses. +If not, there must be some mechanism by which the client is</FONT> +<BR><FONT FACE="Arial,Helvetica"> informed of the +server's address.</FONT> + +<P><FONT FACE="Arial,Helvetica"> Note how ACE_ERROR_RETURN +is used if we fail to open the acceptor. This</FONT> +<BR><FONT FACE="Arial,Helvetica"> technique is +used over and over again in our tutorials.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> if (peer_acceptor.open (ACE_INET_Addr +(PORT), &reactor) == -1)</FONT> +<BR><FONT FACE="Arial,Helvetica"> ACE_ERROR_RETURN ((LM_ERROR, +"%p\n", "open"), -1);</FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Install our signal +handler. You can actually register signal handlers</FONT> +<BR><FONT FACE="Arial,Helvetica"> with the reactor. +You might do that when the signal handler is</FONT> +<BR><FONT FACE="Arial,Helvetica"> responsible for +performing "real" work. Our simple flag-setter doesn't</FONT> +<BR><FONT FACE="Arial,Helvetica"> justify deriving +from ACE_Event_Handler and providing a callback function</FONT> +<BR><FONT FACE="Arial,Helvetica"> though.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> ACE_Sig_Action sa ((ACE_SignalHandler) +handler, SIGINT);</FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Like ACE_ERROR_RETURN, +the ACE_DEBUG macro gets used quite a bit. It's a</FONT> +<BR><FONT FACE="Arial,Helvetica"> handy way to +generate uniform debug output from your program.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> ACE_DEBUG ((LM_DEBUG, "(%P|%t) +starting up server daemon\n"));</FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> This will loop +"forever" invoking the handle_events() method of our</FONT> +<BR><FONT FACE="Arial,Helvetica"> reactor. handle_events() +watches for activity on any registered handlers</FONT> +<BR><FONT FACE="Arial,Helvetica"> and invokes their +appropriate callbacks when necessary. Callback-driven</FONT> +<BR><FONT FACE="Arial,Helvetica"> programming is +a big thing in ACE, you should get used to it. If the</FONT> +<BR><FONT FACE="Arial,Helvetica"> signal handler +catches something, the finished flag will be set and we'll</FONT> +<BR><FONT FACE="Arial,Helvetica"> exit. Conveniently +enough, handle_events() is also interrupted by signals</FONT> +<BR><FONT FACE="Arial,Helvetica"> and will exit +back to the while() loop. (If you want your event loop to</FONT> +<BR><FONT FACE="Arial,Helvetica"> not be interrupted +by signals, checkout the <i>restart</i> flag on the</FONT> +<BR><FONT FACE="Arial,Helvetica"> open() method +of ACE_Reactor if you're interested.)</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> while (!finished)</FONT> +<BR><FONT FACE="Arial,Helvetica"> +reactor.handle_events ();</FONT> + +<P><FONT FACE="Arial,Helvetica"> ACE_DEBUG ((LM_DEBUG, "(%P|%t) shutting +down server daemon\n"));</FONT> +<BR><FONT FACE="Arial,Helvetica"> </FONT> +<BR><FONT FACE="Arial,Helvetica"> return 0;</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT> + +<P> +<HR WIDTH="100%"> + +<P>Hmmm... No change there. Maybe I should leave out comments +on the stuff I don't change. Let's take a lookt at client_acceptor.h. + +<P> +<HR WIDTH="100%"> +<CENTER>[<A HREF="..">Tutorial +Index</A>] [<A HREF="page03.html">Continue +This Tutorial</A>]</CENTER> + +</BODY> +</HTML> diff --git a/docs/tutorials/007/page03.html b/docs/tutorials/007/page03.html new file mode 100644 index 00000000000..694ed2505cc --- /dev/null +++ b/docs/tutorials/007/page03.html @@ -0,0 +1,263 @@ +<HTML> +<HEAD> + <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> + <META NAME="GENERATOR" CONTENT="Mozilla/4.04 [en] (X11; I; Linux 2.0.32 i486) [Netscape]"> + <META NAME="Author" CONTENT="James CE Johnson"> + <META NAME="Description" CONTENT="A first step towards using ACE productively"> + <TITLE>ACE Tutorial 007</TITLE> +</HEAD> +<BODY TEXT="#000000" BGCOLOR="#FFFFFF" LINK="#000FFF" VLINK="#FF0F0F"> + +<CENTER><B><FONT SIZE=+2>ACE Tutorial 007</FONT></B></CENTER> + +<CENTER><B><FONT SIZE=+2>Creating a thread-pool server</FONT></B></CENTER> + + +<P> +<HR WIDTH="100%"> + +<P>Let's see what things we've had to add to <A HREF="client_acceptor.h">client_acceptor.h</A>. + +<P> +<HR WIDTH="100%"> +<BR><FONT FACE="Arial,Helvetica">// $Id: client_acceptor.h,v 1.1 1998/08/30 +16:04:11 jcej Exp $</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">#ifndef CLIENT_ACCEPTOR_H</FONT> +<BR><FONT FACE="Arial,Helvetica">#define CLIENT_ACCEPTOR_H</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> The ACE_Acceptor<> template +lives in the ace/Acceptor.h header file. You'll</FONT> +<BR><FONT FACE="Arial,Helvetica"> find a very consitent naming +convention between the ACE objects and the</FONT> +<BR><FONT FACE="Arial,Helvetica"> headers where they can be +found. In general, the ACE object ACE_Foobar will</FONT> +<BR><FONT FACE="Arial,Helvetica"> be found in ace/Foobar.h.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">#include "ace/Acceptor.h"</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Since we want to work with +sockets, we'll need a SOCK_Acceptor to allow the</FONT> +<BR><FONT FACE="Arial,Helvetica"> clients to connect to us.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">#include "ace/SOCK_Acceptor.h"</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> The Client_Handler object +we develop will be used to handle clients once</FONT> +<BR><FONT FACE="Arial,Helvetica"> they're connected. +The ACE_Acceptor<> template's first parameter requires</FONT> +<BR><FONT FACE="Arial,Helvetica"> such an object. In +some cases, you can get by with just a forward</FONT> +<BR><FONT FACE="Arial,Helvetica"> declaration on the class, +in others you have to have the whole thing.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">#include "client_handler.h"</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Parameterize the ACE_Acceptor<> +such that it will listen for socket</FONT> +<BR><FONT FACE="Arial,Helvetica"> connection attempts and create +Client_Handler objects when they happen. In</FONT> +<BR><FONT FACE="Arial,Helvetica"> Tutorial 001, we wrote the +basic acceptor logic on our own before we</FONT> +<BR><FONT FACE="Arial,Helvetica"> realized that ACE_Acceptor<> +was available. You'll get spoiled using the</FONT> +<BR><FONT FACE="Arial,Helvetica"> ACE templates because they +take away a lot of the tedious details!</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">typedef ACE_Acceptor < Client_Handler, +ACE_SOCK_ACCEPTOR > Client_Acceptor_Base;</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">#include "thread_pool.h"</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> This time we've added quite +a bit more to our acceptor. In addition to</FONT> +<BR><FONT FACE="Arial,Helvetica"> providing a choice of concurrency +strategies, we also maintain a Thread_Pool</FONT> +<BR><FONT FACE="Arial,Helvetica"> object in case that strategy +is chosen. The object still isn't very complex</FONT> +<BR><FONT FACE="Arial,Helvetica"> but it's come a long way +from the simple typedef we had in Tutorial 5.</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> Why keep the thread pool as +a member? If we go back to the inetd concept</FONT> +<BR><FONT FACE="Arial,Helvetica"> you'll recall that we need +several acceptors to make that work. We may have</FONT> +<BR><FONT FACE="Arial,Helvetica"> a situation in which our +different client types requre different resources.</FONT> +<BR><FONT FACE="Arial,Helvetica"> That is, we may need a large +thread pool for some client types and a smaller</FONT> +<BR><FONT FACE="Arial,Helvetica"> one for others. We +could share a pool but then the client types may have</FONT> +<BR><FONT FACE="Arial,Helvetica"> undesirable impact on one +another.</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> Just in case you do want to +share a single thread pool, there is a constructor</FONT> +<BR><FONT FACE="Arial,Helvetica"> below that will let you do +that.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">class Client_Acceptor : public Client_Acceptor_Base</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica">public:</FONT> +<BR><FONT FACE="Arial,Helvetica"> +typedef Client_Acceptor_Base inherited;</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Now that we have more than two strategies, we need more than a boolean</FONT> +<BR><FONT FACE="Arial,Helvetica"> +to tell us what we're using. A set of enums is a good choice because</FONT> +<BR><FONT FACE="Arial,Helvetica"> +it allows us to use named values. Another option would be a set of</FONT> +<BR><FONT FACE="Arial,Helvetica"> +static const integers.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +enum concurrency_t</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +single_threaded_,</FONT> +<BR><FONT FACE="Arial,Helvetica"> +thread_per_connection_,</FONT> +<BR><FONT FACE="Arial,Helvetica"> +thread_pool_</FONT> +<BR><FONT FACE="Arial,Helvetica"> +};</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +The default constructor allows the programmer to choose the concurrency</FONT> +<BR><FONT FACE="Arial,Helvetica"> +strategy. Since we want to focus on thread-pool, that's what we'll +use</FONT> +<BR><FONT FACE="Arial,Helvetica"> +if nothing is specified.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Client_Acceptor( int _concurrency = thread_pool_ );</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Another option is to construct the object with an existing thread pool.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +The concurrency strategy is pretty obvious at that point.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Client_Acceptor( Thread_Pool & _thread_pool );</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Our destructor will take care of shutting down the thread-pool</FONT> +<BR><FONT FACE="Arial,Helvetica"> +if applicable.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +~Client_Acceptor( void );</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Open ourselves and register with the given reactor. The thread pool +size</FONT> +<BR><FONT FACE="Arial,Helvetica"> +can be specified here if you want to use that concurrency strategy.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +int open( const ACE_INET_Addr & _addr, ACE_Reactor * _reactor,</FONT> +<BR><FONT FACE="Arial,Helvetica"> +int _pool_size = Thread_Pool::default_pool_size_ );</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Close ourselves and our thread pool if applicable</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +int close(void);</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +What is our concurrency strategy?</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +int concurrency(void)</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{ return this->concurrency_; }</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Give back a pointer to our thread pool. Our Client_Handler objects</FONT> +<BR><FONT FACE="Arial,Helvetica"> +will need this so that their handle_input() methods can put themselves</FONT> +<BR><FONT FACE="Arial,Helvetica"> +into the pool. Another alternative would be a globally accessible</FONT> +<BR><FONT FACE="Arial,Helvetica"> +thread pool. ACE_Singleton<> is a way to achieve that.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Thread_Pool * thread_pool(void)</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{ return & this->the_thread_pool_; }</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Since we can be constructed with a Thread_Pool reference, there are times</FONT> +<BR><FONT FACE="Arial,Helvetica"> +when we need to know if the thread pool we're using is ours or if we're</FONT> +<BR><FONT FACE="Arial,Helvetica"> +just borrowing it from somebody else.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +int thread_pool_is_private(void)</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{ return &the_thread_pool_ == &private_thread_pool_; }</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">protected:</FONT> +<BR><FONT FACE="Arial,Helvetica"> +int concurrency_;</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +Thread_Pool private_thread_pool_;</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +Thread_Pool & the_thread_pool_;</FONT> +<BR><FONT FACE="Arial,Helvetica">};</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">#endif // CLIENT_ACCEPTOR_H</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P> +<HR WIDTH="100%"> + +<P>Well, except for the new Thread_Pool member variable, most of the changes +are informational. + +<P> +<HR WIDTH="100%"> +<CENTER>[<A HREF="..">Tutorial Index</A>] [<A HREF="page04.html">Continue +This Tutorial</A>]</CENTER> + +</BODY> +</HTML> diff --git a/docs/tutorials/007/page04.html b/docs/tutorials/007/page04.html new file mode 100644 index 00000000000..c74bcdf7dae --- /dev/null +++ b/docs/tutorials/007/page04.html @@ -0,0 +1,133 @@ +<HTML> +<HEAD> + <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> + <META NAME="GENERATOR" CONTENT="Mozilla/4.04 [en] (X11; I; Linux 2.0.32 i486) [Netscape]"> + <META NAME="Author" CONTENT="James CE Johnson"> + <META NAME="Description" CONTENT="A first step towards using ACE productively"> + <TITLE>ACE Tutorial 007</TITLE> +</HEAD> +<BODY TEXT="#000000" BGCOLOR="#FFFFFF" LINK="#000FFF" VLINK="#FF0F0F"> + +<CENTER><B><FONT SIZE=+2>ACE Tutorial 007</FONT></B></CENTER> + +<CENTER><B><FONT SIZE=+2>Creating a thread-pool server</FONT></B></CENTER> + + +<P> +<HR WIDTH="100%"> + +<P>Something new this time is <A HREF="client_acceptor.cpp">client_acceptor.cpp</A>. +I finally had enough code to move it out of the header. + +<P> +<HR WIDTH="100%"> +<BR><FONT FACE="Arial,Helvetica">// $Id: client_acceptor.cpp,v 1.1 1998/08/30 +16:04:11 jcej Exp $</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">#include "client_acceptor.h"</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Construct ourselves with +the chosen concurrency strategy. Notice that we also</FONT> +<BR><FONT FACE="Arial,Helvetica"> set our Thread_Pool reference +to our private instance.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">Client_Acceptor::Client_Acceptor( int +_concurrency )</FONT> +<BR><FONT FACE="Arial,Helvetica"> : concurrency_(_concurrency)</FONT> +<BR><FONT FACE="Arial,Helvetica"> ,the_thread_pool_(private_thread_pool_)</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Construct ourselves with +a reference to somebody else' Thread_Pool. Obvioulsy</FONT> +<BR><FONT FACE="Arial,Helvetica"> our concurrency strategy +is "thread_pool_" at this point.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">Client_Acceptor::Client_Acceptor( Thread_Pool +& _thread_pool )</FONT> +<BR><FONT FACE="Arial,Helvetica"> : concurrency_(thread_pool_)</FONT> +<BR><FONT FACE="Arial,Helvetica"> ,the_thread_pool_(_thread_pool)</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> When we're destructed, we +may need to cleanup after ourselves. If we're running</FONT> +<BR><FONT FACE="Arial,Helvetica"> with a thread pool that we +own, it is up to us to close it down.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">Client_Acceptor::~Client_Acceptor( void +)</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +if( this->concurrency() == thread_pool_ && thread_pool_is_private() +)</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +thread_pool()->close();</FONT> +<BR><FONT FACE="Arial,Helvetica"> +}</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Similar to the destructor +(and close() below) it is necessary for us to open the</FONT> +<BR><FONT FACE="Arial,Helvetica"> thread pool in some circumstances.</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> Notice how we delegate most +of the open() work to the open() method of our baseclass.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">int Client_Acceptor::open( const ACE_INET_Addr +& _addr, ACE_Reactor * _reactor, int _pool_size )</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +if( this->concurrency() == thread_pool_ && thread_pool_is_private() +)</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +thread_pool()->open(_pool_size);</FONT> +<BR><FONT FACE="Arial,Helvetica"> +}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +return inherited::open(_addr,_reactor);</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Here again we find that we +have to manage the thread pool. Like open() we also delegate</FONT> +<BR><FONT FACE="Arial,Helvetica"> the other work to our baseclass.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">int Client_Acceptor::close(void)</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +if( this->concurrency() == thread_pool_ && thread_pool_is_private() +)</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +thread_pool()->close();</FONT> +<BR><FONT FACE="Arial,Helvetica"> +}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +return inherited::close();</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT> +<BR><FONT FACE="Arial,Helvetica"></FONT> <FONT FACE="Arial,Helvetica"></FONT> + +<P> +<HR WIDTH="100%"> + +<P>Nothing really surprising here. Most of it just manages the Thread_Pool. + +<P> +<HR WIDTH="100%"> +<CENTER>[<A HREF="..">Tutorial Index</A>] [<A HREF="page05.html">Continue +This Tutorial</A>]</CENTER> + +</BODY> +</HTML> diff --git a/docs/tutorials/007/page05.html b/docs/tutorials/007/page05.html new file mode 100644 index 00000000000..7e8df23e750 --- /dev/null +++ b/docs/tutorials/007/page05.html @@ -0,0 +1,282 @@ +<HTML> +<HEAD> + <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> + <META NAME="GENERATOR" CONTENT="Mozilla/4.04 [en] (X11; I; Linux 2.0.32 i486) [Netscape]"> + <META NAME="Author" CONTENT="James CE Johnson"> + <META NAME="Description" CONTENT="A first step towards using ACE productively"> + <TITLE>ACE Tutorial 007</TITLE> +</HEAD> +<BODY TEXT="#000000" BGCOLOR="#FFFFFF" LINK="#000FFF" VLINK="#FF0F0F"> + +<CENTER><B><FONT SIZE=+2>ACE Tutorial 007</FONT></B></CENTER> + +<CENTER><B><FONT SIZE=+2>Creating a thread-pool server</FONT></B></CENTER> + + +<P> +<HR WIDTH="100%"> + +<P>As you might expect, <A HREF="client_handler.h">client_handler.h</A> +is next. + +<P> +<HR WIDTH="100%"> +<BR><FONT FACE="Arial,Helvetica"></FONT> <FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">// $Id: client_handler.h,v 1.1 1998/08/30 +16:04:12 jcej Exp $</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">#ifndef CLIENT_HANDLER_H</FONT> +<BR><FONT FACE="Arial,Helvetica">#define CLIENT_HANDLER_H</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Our client handler must exist +somewhere in the ACE_Event_Handler object</FONT> +<BR><FONT FACE="Arial,Helvetica"> hierarchy. This is +a requirement of the ACE_Reactor because it maintains</FONT> +<BR><FONT FACE="Arial,Helvetica"> ACE_Event_Handler pointers +for each registered event handler. You could</FONT> +<BR><FONT FACE="Arial,Helvetica"> derive our Client_Handler +directly from ACE_Event_Handler but you still have</FONT> +<BR><FONT FACE="Arial,Helvetica"> to have an ACE_SOCK_Stream +for the actually connection. With a direct</FONT> +<BR><FONT FACE="Arial,Helvetica"> derivative of ACE_Event_Handler, +you'll have to contain and maintain an</FONT> +<BR><FONT FACE="Arial,Helvetica"> ACE_SOCK_Stream instance +yourself. With ACE_Svc_Handler (which is a</FONT> +<BR><FONT FACE="Arial,Helvetica"> derivative of ACE_Event_Handler) +some of those details are handled for you.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">#include "ace/Svc_Handler.h"</FONT> +<BR><FONT FACE="Arial,Helvetica">#include "ace/SOCK_Stream.h"</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">class Client_Acceptor;</FONT> +<BR><FONT FACE="Arial,Helvetica">class Thread_Pool;</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Another feature of ACE_Svc_Handler +is it's ability to present the ACE_Task<></FONT> +<BR><FONT FACE="Arial,Helvetica"> interface as well. +That's what the ACE_NULL_SYNCH parameter below is all</FONT> +<BR><FONT FACE="Arial,Helvetica"> about. That's beyond +our scope here but we'll come back to it in the next</FONT> +<BR><FONT FACE="Arial,Helvetica"> tutorial when we start looking +at concurrency options.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">class Client_Handler : public ACE_Svc_Handler +< ACE_SOCK_STREAM, ACE_NULL_SYNCH ></FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica">public:</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> // Constructor...</FONT> +<BR><FONT FACE="Arial,Helvetica"> Client_Handler (void);</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> The destroy() +method is our preferred method of destruction. We could</FONT> +<BR><FONT FACE="Arial,Helvetica"> have overloaded +the delete operator but that is neither easy nor</FONT> +<BR><FONT FACE="Arial,Helvetica"> intuitive (at +least to me). Instead, we provide a new method of</FONT> +<BR><FONT FACE="Arial,Helvetica"> destruction and +we make our destructor protected so that only ourselves,</FONT> +<BR><FONT FACE="Arial,Helvetica"> our derivatives +and our friends can delete us. It's a nice</FONT> +<BR><FONT FACE="Arial,Helvetica"> compromise.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> void destroy (void);</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Most ACE objects +have an open() method. That's how you make them ready</FONT> +<BR><FONT FACE="Arial,Helvetica"> to do work. +ACE_Event_Handler has a virtual open() method which allows us</FONT> +<BR><FONT FACE="Arial,Helvetica"> to create this +overrride. ACE_Acceptor<> will invoke this method after</FONT> +<BR><FONT FACE="Arial,Helvetica"> creating a new +Client_Handler when a client connects. Notice that the</FONT> +<BR><FONT FACE="Arial,Helvetica"> parameter to +open() is a void*. It just so happens that the pointer</FONT> +<BR><FONT FACE="Arial,Helvetica"> points to the +acceptor which created us. You would like for the parameter</FONT> +<BR><FONT FACE="Arial,Helvetica"> to be an ACE_Acceptor<>* +but since ACE_Event_Handler is generic, that</FONT> +<BR><FONT FACE="Arial,Helvetica"> would tie it +too closely to the ACE_Acceptor<> set of objects. In our</FONT> +<BR><FONT FACE="Arial,Helvetica"> definition of +open() you'll see how we get around that.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> int open (void *_acceptor);</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> When there is +activity on a registered handler, the handle_input() method</FONT> +<BR><FONT FACE="Arial,Helvetica"> of the handler +will be invoked. If that method returns an error code (eg</FONT> +<BR><FONT FACE="Arial,Helvetica"> -- -1) then the +reactor will invoke handle_close() to allow the object to</FONT> +<BR><FONT FACE="Arial,Helvetica"> clean itself +up. Since an event handler can be registered for more than</FONT> +<BR><FONT FACE="Arial,Helvetica"> one type of callback, +the callback mask is provided to inform</FONT> +<BR><FONT FACE="Arial,Helvetica"> handle_close() +exactly which method failed. That way, you don't have to</FONT> +<BR><FONT FACE="Arial,Helvetica"> maintain state +information between your handle_* method calls. The _handle</FONT> +<BR><FONT FACE="Arial,Helvetica"> parameter is +explained below...</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> int handle_close (ACE_HANDLE _handle, +ACE_Reactor_Mask _mask);</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> When we register +with the reactor, we're going to tell it that we want to</FONT> +<BR><FONT FACE="Arial,Helvetica"> be notified of +READ events. When the reactor sees that there is read</FONT> +<BR><FONT FACE="Arial,Helvetica"> activity for +us, our handle_input() will be invoked. The _handleg</FONT> +<BR><FONT FACE="Arial,Helvetica"> provided is the +handle (file descriptor in Unix) of the actual connection</FONT> +<BR><FONT FACE="Arial,Helvetica"> causing the activity. +Since we're derived from ACE_Svc_Handler<> and it</FONT> +<BR><FONT FACE="Arial,Helvetica"> maintains it's +own peer (ACE_SOCK_Stream) object, this is redundant for</FONT> +<BR><FONT FACE="Arial,Helvetica"> us. However, +if we had been derived directly from ACE_Event_Handler, we</FONT> +<BR><FONT FACE="Arial,Helvetica"> may have chosen +not to contain the peer. In that case, the _handleg</FONT> +<BR><FONT FACE="Arial,Helvetica"> would be important +to us for reading the client's data.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> int handle_input (ACE_HANDLE _handle);</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">protected:</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> If the Client_Acceptor +which created us has chosen a thread-per-connection</FONT> +<BR><FONT FACE="Arial,Helvetica"> strategy then +our open() method will activate us into a dedicate thread.</FONT> +<BR><FONT FACE="Arial,Helvetica"> The svc() method +will then execute in that thread performing some of the</FONT> +<BR><FONT FACE="Arial,Helvetica"> functions we +used to leave up to the reactor.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> int svc(void);</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> This has nothing +at all to do with ACE. I've added this here as a worker</FONT> +<BR><FONT FACE="Arial,Helvetica"> function which +I will call from handle_input(). That allows me to</FONT> +<BR><FONT FACE="Arial,Helvetica"> introduce concurrencly +in later tutorials with a no changes to the worker</FONT> +<BR><FONT FACE="Arial,Helvetica"> function. +You can think of process() as application-level code and</FONT> +<BR><FONT FACE="Arial,Helvetica"> everything elase +as application-framework code.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> int process (char *_rdbuf, int +_rdbuf_len);</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> We don't really +do anything in our destructor but we've declared it to be</FONT> +<BR><FONT FACE="Arial,Helvetica"> protected to +prevent casual deletion of this object. As I said above, I</FONT> +<BR><FONT FACE="Arial,Helvetica"> really would +prefer that everyone goes through the destroy() method to get</FONT> +<BR><FONT FACE="Arial,Helvetica"> rid of us.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> ~Client_Handler (void);</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> When we +get to the definition of Client_Handler we'll see that there are</FONT> +<BR><FONT FACE="Arial,Helvetica"> +several places where we go back to the Client_Acceptor for information.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +It is generally a good idea to do that through an accesor rather than</FONT> +<BR><FONT FACE="Arial,Helvetica"> +using the member variable directly.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> Client_Acceptor * client_acceptor( +void )</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{ return this->client_acceptor_; }</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> And since +you shouldn't access a member variable directly, neither should you</FONT> +<BR><FONT FACE="Arial,Helvetica"> +set (mutate) it. Although it might seem silly to do it this way, +you'll thank</FONT> +<BR><FONT FACE="Arial,Helvetica"> +yourself for it later.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> void client_acceptor( Client_Acceptor +* _client_acceptor )</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{ this->client_acceptor_ = _client_acceptor; }</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> The concurrency() +accessor tells us the current concurrency strategy. It actually</FONT> +<BR><FONT FACE="Arial,Helvetica"> +queries the Client_Acceptor for it but by having the accessor in place, +we could</FONT> +<BR><FONT FACE="Arial,Helvetica"> +change our implementation without affecting everything that needs to know.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> int concurrency(void);</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Likewise +for access to the Thread_Pool that we belong to.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> Thread_Pool * thread_pool(void);</FONT> +<BR><FONT FACE="Arial,Helvetica"></FONT> <FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> Client_Acceptor * client_acceptor_;</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> For some +reason I didn't create accessor/mutator methods for this. So much +for</FONT> +<BR><FONT FACE="Arial,Helvetica"> +consistency....</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +This variable is used to remember the thread in which we were created: +the "creator"</FONT> +<BR><FONT FACE="Arial,Helvetica"> +thread in other words. handle_input() needs to know if it is operating +in the</FONT> +<BR><FONT FACE="Arial,Helvetica"> +main reactor thread (which is the one that created us) or if it is operating +in</FONT> +<BR><FONT FACE="Arial,Helvetica"> +one of the thread pool threads. More on this when we get to handle_input().</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> ACE_thread_t +creator_;</FONT> +<BR><FONT FACE="Arial,Helvetica">};</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">#endif // CLIENT_HANDLER_H</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P> +<HR WIDTH="100%"> + +<P>Still, we're just not seeing a lot of changes due to intruduction of +the thread pool. That's a good thing! You don't want to go turning +your application upside down just because you changed thread models. + +<P> +<HR WIDTH="100%"> +<CENTER>[<A HREF="..">Tutorial Index</A>] [<A HREF="page06.html">Continue +This Tutorial</A>]</CENTER> + +</BODY> +</HTML> diff --git a/docs/tutorials/007/page06.html b/docs/tutorials/007/page06.html new file mode 100644 index 00000000000..605cc80a03f --- /dev/null +++ b/docs/tutorials/007/page06.html @@ -0,0 +1,332 @@ +<HTML> +<HEAD> + <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> + <META NAME="GENERATOR" CONTENT="Mozilla/4.04 [en] (X11; I; Linux 2.0.32 i486) [Netscape]"> + <META NAME="Author" CONTENT="James CE Johnson"> + <META NAME="Description" CONTENT="A first step towards using ACE productively"> + <TITLE>ACE Tutorial 007</TITLE> +</HEAD> +<BODY TEXT="#000000" BGCOLOR="#FFFFFF" LINK="#000FFF" VLINK="#FF0F0F"> + +<CENTER><B><FONT SIZE=+2>ACE Tutorial 007</FONT></B></CENTER> + +<CENTER><B><FONT SIZE=+2>Creating a thread-pool server</FONT></B></CENTER> + + +<P> +<HR WIDTH="100%"> + +<P><A HREF="client_handler.cpp">client_handler.cpp</A> shows some of the +changes due to the thread-pool. Just a few though. + +<P> +<HR WIDTH="100%"> +<BR><FONT FACE="Arial,Helvetica"></FONT> <FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">// $Id: client_handler.cpp,v 1.1 1998/08/30 +16:04:12 jcej Exp $</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Since this is the third time +we've seen most of this, I'm going to strip out almost</FONT> +<BR><FONT FACE="Arial,Helvetica"> all of the comments that +you've already seen. That way, you can concentrate on the</FONT> +<BR><FONT FACE="Arial,Helvetica"> new items.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">#include "client_acceptor.h"</FONT> +<BR><FONT FACE="Arial,Helvetica">#include "client_handler.h"</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> We're going to be registering +and unregistering a couple of times. To make sure that</FONT> +<BR><FONT FACE="Arial,Helvetica"> we use the same flags every +time, I've created these handy macros.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">#define REGISTER_MASK +ACE_Event_Handler::READ_MASK</FONT> +<BR><FONT FACE="Arial,Helvetica">#define REMOVE_MASK +(ACE_Event_Handler::READ_MASK | ACE_Event_Handler::DONT_CALL)</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Our constructor still doesn't +really do anything. We simply initialize the acceptor</FONT> +<BR><FONT FACE="Arial,Helvetica"> pointer to "null" and get +our current thread id. The static self() method of ACE_Thread</FONT> +<BR><FONT FACE="Arial,Helvetica"> will return you a thread +id native to your platform.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">Client_Handler::Client_Handler (void)</FONT> +<BR><FONT FACE="Arial,Helvetica"> : client_acceptor_(0)</FONT> +<BR><FONT FACE="Arial,Helvetica"> ,creator_(ACE_Thread::self())</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">Client_Handler::~Client_Handler (void)</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Query our acceptor for the +concurrency strategy. Notice that we don't bother</FONT> +<BR><FONT FACE="Arial,Helvetica"> to check that our acceptor +pointer is valid. That is proably a bad idea...</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">int Client_Handler::concurrency(void)</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +return this->client_acceptor()->concurrency();</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> And here we ask the acceptor +about the thread pool.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">Thread_Pool * Client_Handler::thread_pool(void)</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +return this->client_acceptor()->thread_pool();</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> The destroy() method hasn't +changed since we wrote it back in Tutorial 5.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">void Client_Handler::destroy (void)</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica"> this->peer ().close ();</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> this->reactor ()->remove_handler +(this, REMOVE_MASK );</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> delete this;</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Back to our open() method. +This is straight out of Tutorial 6. There's</FONT> +<BR><FONT FACE="Arial,Helvetica"> nothing additional here for +the thread-pool implementation.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">int Client_Handler::open (void *_acceptor)</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica"> client_acceptor( (Client_Acceptor +*) _acceptor );</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> if( concurrency() == Client_Acceptor::thread_per_connection_ +)</FONT> +<BR><FONT FACE="Arial,Helvetica"> {</FONT> +<BR><FONT FACE="Arial,Helvetica"> +return this->activate();</FONT> +<BR><FONT FACE="Arial,Helvetica"> }</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> this->reactor (client_acceptor()->reactor +());</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> ACE_INET_Addr addr;</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> if (this->peer ().get_remote_addr +(addr) == -1)</FONT> +<BR><FONT FACE="Arial,Helvetica"> {</FONT> +<BR><FONT FACE="Arial,Helvetica"> return +-1;</FONT> +<BR><FONT FACE="Arial,Helvetica"> }</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> if (this->reactor ()->register_handler +(this, REGISTER_MASK) == -1)</FONT> +<BR><FONT FACE="Arial,Helvetica"> {</FONT> +<BR><FONT FACE="Arial,Helvetica"> ACE_ERROR_RETURN +((LM_ERROR, "(%P|%t) can't register with reactor\n"), -1);</FONT> +<BR><FONT FACE="Arial,Helvetica"> }</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> ACE_DEBUG ((LM_DEBUG, "(%P|%t) connected +with %s\n", addr.get_host_name ()));</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> return 0;</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> In the open() method, we +registered with the reactor and requested to be</FONT> +<BR><FONT FACE="Arial,Helvetica"> notified when there is data +to be read. When the reactor sees that activity</FONT> +<BR><FONT FACE="Arial,Helvetica"> it will invoke this handle_input() +method on us. As I mentioned, the _handle</FONT> +<BR><FONT FACE="Arial,Helvetica"> parameter isn't useful to +us but it narrows the list of methods the reactor</FONT> +<BR><FONT FACE="Arial,Helvetica"> has to worry about and the +list of possible virtual functions we would have</FONT> +<BR><FONT FACE="Arial,Helvetica"> to override.</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> You've read that much before... +Now we have to do some extra stuff in case</FONT> +<BR><FONT FACE="Arial,Helvetica"> we're using the thread-pool +implementation. If we're called by our creator</FONT> +<BR><FONT FACE="Arial,Helvetica"> thread then we must be in +the reactor. In that case, we arrange to be put</FONT> +<BR><FONT FACE="Arial,Helvetica"> into the thread pool. +If we're not in the creator thread then we must be</FONT> +<BR><FONT FACE="Arial,Helvetica"> in the thread pool and we +can do some work.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">int Client_Handler::handle_input (ACE_HANDLE +_handle)</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica"> ACE_UNUSED_ARG (_handle);</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Check our strategy. +If we're using the thread pool and we're in the creation</FONT> +<BR><FONT FACE="Arial,Helvetica"> +thread then we know we were called by the reactor.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> if( concurrency() == Client_Acceptor::thread_pool_ +)</FONT> +<BR><FONT FACE="Arial,Helvetica"> {</FONT> +<BR><FONT FACE="Arial,Helvetica"> +if( ACE_Thread::self() == creator_ )</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Remove ourselves from the reactor and ask to be put into the thread pool's</FONT> +<BR><FONT FACE="Arial,Helvetica"> +queue of work. (You should be able to use suspend_handler() but I've +had</FONT> +<BR><FONT FACE="Arial,Helvetica"> +problems with that.)</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +this->reactor()->remove_handler( this, REMOVE_MASK );</FONT> +<BR><FONT FACE="Arial,Helvetica"> +return this->thread_pool()->enqueue(this);</FONT> +<BR><FONT FACE="Arial,Helvetica"> +}</FONT> +<BR><FONT FACE="Arial,Helvetica"> }</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Any strategy +other than thread-per-connection will eventually get here. If we're +in the</FONT> +<BR><FONT FACE="Arial,Helvetica"> +single-threaded implementation or the thread-pool, we still have to pass +this way.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> char buf[128];</FONT> +<BR><FONT FACE="Arial,Helvetica"> memset (buf, 0, sizeof (buf));</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Invoke the process() +method to do the work but save it's return value instead</FONT> +<BR><FONT FACE="Arial,Helvetica"> +of returning it immediately.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> int rval = this->process(buf,sizeof(buf));</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Now, we look +again to see if we're in the thread-pool implementation. If so then +we</FONT> +<BR><FONT FACE="Arial,Helvetica"> +need to re-register ourselves with the reactor so that we can get more +work when it</FONT> +<BR><FONT FACE="Arial,Helvetica"> +is available. (If suspend_handler() worked then we would use resume_handler() +here.)</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> if( concurrency() == Client_Acceptor::thread_pool_ +)</FONT> +<BR><FONT FACE="Arial,Helvetica"> {</FONT> +<BR><FONT FACE="Arial,Helvetica"> +if( rval != -1 )</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +this->reactor()->register_handler( this, REGISTER_MASK );</FONT> +<BR><FONT FACE="Arial,Helvetica"> +}</FONT> +<BR><FONT FACE="Arial,Helvetica"> }</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> /*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Return the result +of process()</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica"> return(rval);</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">int Client_Handler::handle_close (ACE_HANDLE +_handle, ACE_Reactor_Mask _mask)</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica"> ACE_UNUSED_ARG (_handle);</FONT> +<BR><FONT FACE="Arial,Helvetica"> ACE_UNUSED_ARG (_mask);</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> this->destroy ();</FONT> +<BR><FONT FACE="Arial,Helvetica"> return 0;</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">int Client_Handler::svc(void)</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica"> char buf[128];</FONT> +<BR><FONT FACE="Arial,Helvetica"> memset (buf, 0, sizeof (buf));</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> while( 1 )</FONT> +<BR><FONT FACE="Arial,Helvetica"> {</FONT> +<BR><FONT FACE="Arial,Helvetica"> if( this->process(buf,sizeof(buf)) +== -1 )</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +return(-1);</FONT> +<BR><FONT FACE="Arial,Helvetica"> }</FONT> +<BR><FONT FACE="Arial,Helvetica"> }</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> return(0);</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Once again, we see that the +application-level logic has not been at all affected</FONT> +<BR><FONT FACE="Arial,Helvetica"> by our choice of threading +models. Of course, I'm not sharing data between threads</FONT> +<BR><FONT FACE="Arial,Helvetica"> or anything. We'll +leave locking issues for a later tutorial.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">int Client_Handler::process (char *_rdbuf, +int _rdbuf_len)</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica"> switch (this->peer ().recv (_rdbuf, +_rdbuf_len))</FONT> +<BR><FONT FACE="Arial,Helvetica"> {</FONT> +<BR><FONT FACE="Arial,Helvetica"> case -1:</FONT> +<BR><FONT FACE="Arial,Helvetica"> ACE_ERROR_RETURN +((LM_ERROR, "(%P|%t) %p bad read\n", "client"), -1);</FONT> +<BR><FONT FACE="Arial,Helvetica"> case 0:</FONT> +<BR><FONT FACE="Arial,Helvetica"> ACE_ERROR_RETURN +((LM_ERROR, "(%P|%t) closing daemon (fd = %d)\n", this->get_handle ()), +-1);</FONT> +<BR><FONT FACE="Arial,Helvetica"> default:</FONT> +<BR><FONT FACE="Arial,Helvetica"> ACE_DEBUG +((LM_DEBUG, "(%P|%t) from client: %s", _rdbuf));</FONT> +<BR><FONT FACE="Arial,Helvetica"> }</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> return 0;</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P> +<HR WIDTH="100%"> + +<P>Ok, now we've gone and changed handle_input() so that it knows +when to do work and when to enqueue itself. Beyond that, we're still +about the same. + +<P> +<HR WIDTH="100%"> +<CENTER>[<A HREF="..">Tutorial Index</A>] [<A HREF="page07.html">Continue +This Tutorial</A>]</CENTER> + +</BODY> +</HTML> diff --git a/docs/tutorials/007/page07.html b/docs/tutorials/007/page07.html new file mode 100644 index 00000000000..4c6905ebf33 --- /dev/null +++ b/docs/tutorials/007/page07.html @@ -0,0 +1,197 @@ +<HTML> +<HEAD> + <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> + <META NAME="GENERATOR" CONTENT="Mozilla/4.04 [en] (X11; I; Linux 2.0.32 i486) [Netscape]"> + <META NAME="Author" CONTENT="James CE Johnson"> + <META NAME="Description" CONTENT="A first step towards using ACE productively"> + <TITLE>ACE Tutorial 007</TITLE> +</HEAD> +<BODY TEXT="#000000" BGCOLOR="#FFFFFF" LINK="#000FFF" VLINK="#FF0F0F"> + +<CENTER><B><FONT SIZE=+2>ACE Tutorial 007</FONT></B></CENTER> + +<CENTER><B><FONT SIZE=+2>Creating a thread-pool server</FONT></B></CENTER> + + +<P> +<HR WIDTH="100%"> + +<P>Two new files this time. The first is <A HREF="thread_pool.h">thread_pool.h</A> +where we declare our Thread_Pool object. This is responsible for +abstracting away the thread pool implementation details and allowing us +to make so few changes to the rest of the code. + +<P> +<HR WIDTH="100%"><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">// $Id: thread_pool.h,v 1.1 1998/08/30 +16:04:12 jcej Exp $</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">#ifndef THREAD_POOL_H</FONT> +<BR><FONT FACE="Arial,Helvetica">#define THREAD_POOL_H</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> In order to implement a thread +pool, we have to have an object that can create</FONT> +<BR><FONT FACE="Arial,Helvetica"> a thread. The ACE_Task<> +is the basis for doing just such a thing.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">#include "ace/Task.h"</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> We need a forward reference +for ACE_Event_Handler so that our enqueue() method</FONT> +<BR><FONT FACE="Arial,Helvetica"> can accept a pointer to one.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">class ACE_Event_Handler;</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Although we modified the +rest of our program to make use of the thread pool</FONT> +<BR><FONT FACE="Arial,Helvetica"> implementation, if you look +closely you'll see that the changes were rather</FONT> +<BR><FONT FACE="Arial,Helvetica"> minor. The "ACE way" +is generally to create a helper object that abstracts</FONT> +<BR><FONT FACE="Arial,Helvetica"> away the details not relevant +to your application. That's what I'm trying</FONT> +<BR><FONT FACE="Arial,Helvetica"> to do here by creating the +Thread_Pool object.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">class Thread_Pool : public ACE_Task<ACE_MT_SYNCH></FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica">public:</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Provide an enumeration for the default pool size. By doing this, +other objects</FONT> +<BR><FONT FACE="Arial,Helvetica"> +can use the value when they want a default.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +enum size_t</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +default_pool_size_ = 5</FONT> +<BR><FONT FACE="Arial,Helvetica"> +};</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +// Basic constructor</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Thread_Pool(void);</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Opening the thread pool causes one or more threads to be activated. +When activated,</FONT> +<BR><FONT FACE="Arial,Helvetica"> +they all execute the svc() method declared below.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +int open( int _pool_size = default_pool_size_ );</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +When you're done wit the thread pool, you have to have some way to shut +it down.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +This is what close() is for.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +int close(void);</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +To use the thread pool, you have to put some unit of work into it. +Since we're</FONT> +<BR><FONT FACE="Arial,Helvetica"> +dealing with event handlers (or at least their derivatives), I've chosen +to provide</FONT> +<BR><FONT FACE="Arial,Helvetica"> +an enqueue() method that takes a pointer to an ACE_Event_Handler. +The handler's</FONT> +<BR><FONT FACE="Arial,Helvetica"> +handle_input() method will be called, so your object has to know when it +is being</FONT> +<BR><FONT FACE="Arial,Helvetica"> +called by the thread pool.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +int enqueue( ACE_Event_Handler * _handler );</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">protected:</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Our svc() method will dequeue the enqueued event handler objects and invoke +the</FONT> +<BR><FONT FACE="Arial,Helvetica"> +handle_input() method on each. Since we're likely running in more +than one thread,</FONT> +<BR><FONT FACE="Arial,Helvetica"> +idle threads can take work from the queue while other threads are busy +executing</FONT> +<BR><FONT FACE="Arial,Helvetica"> +handle_input() on some object.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +int svc(void);</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Another handy ACE template is ACE_Atomic_Op<>. When parameterized, +this allows</FONT> +<BR><FONT FACE="Arial,Helvetica"> +is to have a thread-safe counting object. The typical arithmetic +operators are</FONT> +<BR><FONT FACE="Arial,Helvetica"> +all internally thread-safe so that you can share it across threads without +worrying</FONT> +<BR><FONT FACE="Arial,Helvetica"> +about any contention issues.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +typedef ACE_Atomic_Op<ACE_Mutex,int> counter_t;</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +We use the atomic op to keep a count of the number of threads in which +our svc()</FONT> +<BR><FONT FACE="Arial,Helvetica"> +method is running. This is particularly important when we want to +close() it down!</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +counter_t active_threads_;</FONT> +<BR><FONT FACE="Arial,Helvetica">};</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">#endif // THREAD_POOL_H</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P> +<HR WIDTH="100%"> + +<P>Well, that doesn't look too complex. What about the implementation? + +<P> +<HR WIDTH="100%"> +<CENTER>[<A HREF="..">Tutorial Index</A>] [<A HREF="page08.html">Continue +This Tutorial</A>]</CENTER> + +</BODY> +</HTML> diff --git a/docs/tutorials/007/page08.html b/docs/tutorials/007/page08.html new file mode 100644 index 00000000000..5c3ede6e8ab --- /dev/null +++ b/docs/tutorials/007/page08.html @@ -0,0 +1,509 @@ +<HTML> +<HEAD> + <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> + <META NAME="GENERATOR" CONTENT="Mozilla/4.04 [en] (X11; I; Linux 2.0.32 i486) [Netscape]"> + <META NAME="Author" CONTENT="James CE Johnson"> + <META NAME="Description" CONTENT="A first step towards using ACE productively"> + <TITLE>ACE Tutorial 007</TITLE> +</HEAD> +<BODY TEXT="#000000" BGCOLOR="#FFFFFF" LINK="#000FFF" VLINK="#FF0F0F"> + +<CENTER><B><FONT SIZE=+2>ACE Tutorial 007</FONT></B></CENTER> + +<CENTER><B><FONT SIZE=+2>Creating a thread-pool server</FONT></B></CENTER> + + +<P> +<HR WIDTH="100%"> + +<P>Finally, <A HREF="thread_pool.cpp">thread_pool.cpp</A> where we have +the Thread_Pool object implementation. + +<P> +<HR WIDTH="100%"><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">// $Id: thread_pool.cpp,v 1.1 1998/08/30 +16:04:12 jcej Exp $</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">#include "thread_pool.h"</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> We need this header so that +we can invoke handle_input() on the objects we dequeue.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">#include "ace/Event_Handler.h"</FONT> +<BR><FONT FACE="Arial,Helvetica"></FONT> <FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> All we do here is initialize +our active thread counter.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">Thread_Pool::Thread_Pool(void)</FONT> +<BR><FONT FACE="Arial,Helvetica"> : active_threads_(0)</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Our open() method is a thin +disguise around the ACE_Task<> activate() method. By</FONT> +<BR><FONT FACE="Arial,Helvetica"> hiding activate() in this +way, the users of Thread_Pool don't have to worry about</FONT> +<BR><FONT FACE="Arial,Helvetica"> the thread configuration +flags.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">int Thread_Pool::open( int _pool_size +)</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica"> return this->activate(THR_NEW_LWP|THR_DETACHED,_pool_size);</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Closing the thread pool can +be a tricky exercise. I've decided to take an easy approach</FONT> +<BR><FONT FACE="Arial,Helvetica"> and simply enqueue a secret +message for each thread we have active.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">int Thread_Pool::close(void)</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Find out how many threads are currently active</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +int counter = active_threads_.value();</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +For each one of the active threads, enqueue a "null" event handler. +Below, we'll</FONT> +<BR><FONT FACE="Arial,Helvetica"> +teach our svc() method that "null" means "shutdown".</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +while( counter-- )</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +this->enqueue( 0 );</FONT> +<BR><FONT FACE="Arial,Helvetica"> +}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +As each svc() method exits, it will decrement the active thread counter. +We just wait</FONT> +<BR><FONT FACE="Arial,Helvetica"> +here for it to reach zero. Since we don't know how long it will take, +we sleep for</FONT> +<BR><FONT FACE="Arial,Helvetica"> +a quarter-second or so between tries.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +while( active_threads_.value() )</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +ACE_OS::sleep( ACE_Time_Value(0.25) );</FONT> +<BR><FONT FACE="Arial,Helvetica"> +}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +return(0);</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> When an object wants to do +work in the pool, it should call the enqueue() method.</FONT> +<BR><FONT FACE="Arial,Helvetica"> We introduce the ACE_Message_Block +here but, unfortunately, we seriously missuse it.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">int Thread_Pool::enqueue( ACE_Event_Handler +* _handler )</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +An ACE_Message_Block is a chunk of data. You put them into an ACE_Message_Queue.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +ACE_Task<> has an ACE_Message_Queue built in. In fact, the parameter +to ACE_Task<></FONT> +<BR><FONT FACE="Arial,Helvetica"> +is passed directly to ACE_Message_Queue. If you look back at our +header file you'll</FONT> +<BR><FONT FACE="Arial,Helvetica"> +see that we used ACE_MT_SYNCH as the parameter indicating that we want +MultiThread</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Synch safety. This allows us to safely put ACE_Message_Block objects +into the</FONT> +<BR><FONT FACE="Arial,Helvetica"> +message queue in one thread and take them out in another.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +An ACE_Message_Block wants to have char* data. We don't have that. +We could</FONT> +<BR><FONT FACE="Arial,Helvetica"> +cast our ACE_Event_Handler* directly to a char* but I wanted to be more +explicit.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Since casting pointers around is a dangerous thing, I've gone out of my +way here</FONT> +<BR><FONT FACE="Arial,Helvetica"> +to be very clear about what we're doing.</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +First: Cast the handler pointer to a void pointer. You can't +do any useful work</FONT> +<BR><FONT FACE="Arial,Helvetica"> +on a void pointer, so this is a clear message that we're making the</FONT> +<BR><FONT FACE="Arial,Helvetica"> +pointer unusable.</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +Next: Cast the void pointer to a char pointer that the ACE_Message_Block +will accept.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +void * v_data = (void*)_handler;</FONT> +<BR><FONT FACE="Arial,Helvetica"> +char * c_data = (char*)v_data;</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Construct a new ACE_Message_Block. For efficiency, you might want +to preallocate a</FONT> +<BR><FONT FACE="Arial,Helvetica"> +stack of these and reuse them. For simplicity, I'll just create what +I need as I need it.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +ACE_Message_Block * mb = new ACE_Message_Block( c_data );</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Our putq() method is a wrapper around one of the enqueue methods of the +ACE_Message_Queue</FONT> +<BR><FONT FACE="Arial,Helvetica"> +that we own. Like all good methods, it returns -1 if it fails for +some reason.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +if( this->putq(mb) == -1 )</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Another trait of the ACE_Message_Block objects is that they are reference +counted.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Since they're designed to be passed around between various objects in several +threads</FONT> +<BR><FONT FACE="Arial,Helvetica"> +we can't just delete them whenever we feel like it. The release() +method is similar</FONT> +<BR><FONT FACE="Arial,Helvetica"> +to the destroy() method we've used elsewhere. It watches the reference +count and will</FONT> +<BR><FONT FACE="Arial,Helvetica"> +delete the object when possible.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +mb->release();</FONT> +<BR><FONT FACE="Arial,Helvetica"> +return(-1);</FONT> +<BR><FONT FACE="Arial,Helvetica"> +}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +return(0);</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> The "guard" concept is very +powerful and used throughout multi-threaded applications.</FONT> +<BR><FONT FACE="Arial,Helvetica"> A guard normally does some +operation on an object at construction and the "opposite"</FONT> +<BR><FONT FACE="Arial,Helvetica"> operation at destruction. +For instance, when you guard a mutex (lock) object, the guard</FONT> +<BR><FONT FACE="Arial,Helvetica"> will acquire the lock on +construction and release it on destruction. In this way, your</FONT> +<BR><FONT FACE="Arial,Helvetica"> method can simply let the +guard go out of scope and know that the lock is released.</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> Guards aren't only useful +for locks however. In this application I've created two guard</FONT> +<BR><FONT FACE="Arial,Helvetica"> objects for quite a different +purpose.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> The Counter_Guard is constructed +with a reference to the thread pool's active thread</FONT> +<BR><FONT FACE="Arial,Helvetica"> counter. The guard +increments the counter when it is created and decrements it at</FONT> +<BR><FONT FACE="Arial,Helvetica"> destruction. By creating +one of these in svc(), I know that the counter will be decremented</FONT> +<BR><FONT FACE="Arial,Helvetica"> no matter how or where svc() +returns.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">class Counter_Guard</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica">public:</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Counter_Guard( Thread_Pool::counter_t & _counter )</FONT> +<BR><FONT FACE="Arial,Helvetica"> +: counter_(_counter)</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +++counter_;</FONT> +<BR><FONT FACE="Arial,Helvetica"> +}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +~Counter_Guard(void)</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +--counter_;</FONT> +<BR><FONT FACE="Arial,Helvetica"> +}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">protected:</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Thread_Pool::counter_t & counter_;</FONT> +<BR><FONT FACE="Arial,Helvetica">};</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> My Message_Block_Guard is +also a little non-traditional. It doesn't do anything in the</FONT> +<BR><FONT FACE="Arial,Helvetica"> constructor but it's destructor +ensures that the message block's release() method is called.</FONT> +<BR><FONT FACE="Arial,Helvetica"> This is a cheap way to prevent +a memory leak if I need an additional exit point in svc().</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">class Message_Block_Guard</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica">public:</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Message_Block_Guard( ACE_Message_Block * & _mb )</FONT> +<BR><FONT FACE="Arial,Helvetica"> +: mb_(_mb)</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +~Message_Block_Guard( void )</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +mb_->release();</FONT> +<BR><FONT FACE="Arial,Helvetica"> +}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">protected:</FONT> +<BR><FONT FACE="Arial,Helvetica"> +ACE_Message_Block * & mb_;</FONT> +<BR><FONT FACE="Arial,Helvetica">};</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica">/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> Now we come to the svc() +method. As I said, this is being executed in each thread of the</FONT> +<BR><FONT FACE="Arial,Helvetica"> Thread_Pool. Here, +we pull messages off of our built-in ACE_Message_Queue and cause them</FONT> +<BR><FONT FACE="Arial,Helvetica"> to do work.</FONT> +<BR><FONT FACE="Arial,Helvetica"> */</FONT> +<BR><FONT FACE="Arial,Helvetica">int Thread_Pool::svc(void)</FONT> +<BR><FONT FACE="Arial,Helvetica">{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +The getq() method takes a reference to a pointer. So... we need a +pointer to give it</FONT> +<BR><FONT FACE="Arial,Helvetica"> +a reference to.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +ACE_Message_Block * mb;</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Create the guard for our active thread counter object. No matter +where we choose to</FONT> +<BR><FONT FACE="Arial,Helvetica"> +return() from svc(), we no know that the counter will be decremented.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Counter_Guard counter_guard(active_threads_);</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Get messages from the queue until we have a failure. There's no real +good reason</FONT> +<BR><FONT FACE="Arial,Helvetica"> +for failure so if it happens, we leave immediately.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +while( this->getq(mb) != -1 )</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +A successful getq() will cause "mb" to point to a valid refernce-counted</FONT> +<BR><FONT FACE="Arial,Helvetica"> +ACE_Message_Block. We use our guard object here so that we're sure +to call</FONT> +<BR><FONT FACE="Arial,Helvetica"> +the release() method of that message block and reduce it's reference count.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Once the count reaches zero, it will be deleted.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Message_Block_Guard message_block_guard(mb);</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +As noted before, the ACE_Message_Block stores it's data as a char*. +We pull that</FONT> +<BR><FONT FACE="Arial,Helvetica"> +out here and later turn it into an ACE_Event_Handler*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +char * c_data = mb->base();</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +We've chosen to use a "null" value as an indication to leave. If +the data we got</FONT> +<BR><FONT FACE="Arial,Helvetica"> +from the queue is not null then we have some work to do.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +if( c_data )</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Once again, we go to great lengths to emphasize the fact that we're casting +pointers</FONT> +<BR><FONT FACE="Arial,Helvetica"> +around in rather impolite ways. We could have cast the char* directly +to an</FONT> +<BR><FONT FACE="Arial,Helvetica"> +ACE_Event_Handler* but then folks might think that's an OK thing to do.</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +(Note: The correct way to use an ACE_Message_Block is to write data +into it.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +What I should have done was create a message block big enough to hold an</FONT> +<BR><FONT FACE="Arial,Helvetica"> +event handler pointer and then written the pointer value into the block. +When</FONT> +<BR><FONT FACE="Arial,Helvetica"> +we got here, I would have to read that data back into a pointer. +While politically</FONT> +<BR><FONT FACE="Arial,Helvetica"> +correct, it is also a lot of work. If you're careful you can get +away with casting</FONT> +<BR><FONT FACE="Arial,Helvetica"> +pointers around.)</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +void * v_data = (void*)c_data;</FONT> +<BR><FONT FACE="Arial,Helvetica"> </FONT> +<BR><FONT FACE="Arial,Helvetica"> +ACE_Event_Handler * handler = (ACE_Event_Handler*)v_data;</FONT> +<BR><FONT FACE="Arial,Helvetica"> </FONT> +<BR><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Now that we finally have an event handler pointer, invoke it's handle_input() +method.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +Since we don't know it's handle, we just give it a default. That's +OK because we</FONT> +<BR><FONT FACE="Arial,Helvetica"> +know that we're not using the handle in the method anyway.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +if( handler->handle_input(ACE_INVALID_HANDLE) == -1 )</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +return(-1); // Error, return now</FONT> +<BR><FONT FACE="Arial,Helvetica"> +}</FONT> +<BR><FONT FACE="Arial,Helvetica"> +}</FONT> +<BR><FONT FACE="Arial,Helvetica"> +else</FONT> +<BR><FONT FACE="Arial,Helvetica"> +{</FONT> +<BR><FONT FACE="Arial,Helvetica"> +/*</FONT> +<BR><FONT FACE="Arial,Helvetica"> +If we get here, we were given a message block with "null" data. That +is our</FONT> +<BR><FONT FACE="Arial,Helvetica"> +signal to leave, so we return(0) to leave gracefully.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +*/</FONT> +<BR><FONT FACE="Arial,Helvetica"> +return(0); +// Ok, shutdown request</FONT> +<BR><FONT FACE="Arial,Helvetica"> +}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +// message_block_guard goes out of scope here</FONT> +<BR><FONT FACE="Arial,Helvetica"> +// and releases the message_block instance.</FONT> +<BR><FONT FACE="Arial,Helvetica"> +}</FONT><FONT FACE="Arial,Helvetica"></FONT> + +<P><FONT FACE="Arial,Helvetica"> +return(0);</FONT> +<BR><FONT FACE="Arial,Helvetica">}</FONT> +<BR><FONT FACE="Arial,Helvetica"></FONT> <FONT FACE="Arial,Helvetica"></FONT> + +<P> +<HR WIDTH="100%"> +<CENTER>[<A HREF="..">Tutorial Index</A>] [<A HREF="page09.html">Continue +This Tutorial</A>]</CENTER> + +</BODY> +</HTML> diff --git a/docs/tutorials/007/page09.html b/docs/tutorials/007/page09.html new file mode 100644 index 00000000000..5023e761eec --- /dev/null +++ b/docs/tutorials/007/page09.html @@ -0,0 +1,64 @@ +<HTML> +<HEAD> + <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> + <META NAME="GENERATOR" CONTENT="Mozilla/4.04 [en] (X11; I; Linux 2.0.32 i486) [Netscape]"> + <META NAME="Author" CONTENT="James CE Johnson"> + <META NAME="Description" CONTENT="A first step towards using ACE productively"> + <TITLE>ACE Tutorial 006</TITLE> +</HEAD> +<BODY TEXT="#000000" BGCOLOR="#FFFFFF" LINK="#000FFF" VLINK="#FF0F0F"> + +<CENTER><B><FONT SIZE=+2>ACE Tutorial 007</FONT></B></CENTER> + +<CENTER><B><FONT SIZE=+2>Creating a thread-pool server</FONT></B></CENTER> + +<HR WIDTH="100%"> + +<P>That's it for Tutorial 7. As with Tutorial 6, we really didn't +have to change much to introduce a new threading strategy. Most of +the work was in creating the Thread_Pool object itself. Everything +else was just minor housekeeping. + +<P>There is a fourth common thread strategy: thread-per-request. +It's not one of my favorites, so I wasn't planning to go into it. +If you want to contribute a tutorial on that topic though, I'll be glad +to include it here. + +<P>For reference, here's the file list again: +<UL> +<LI> +<A HREF="Makefile">Makefile</A></LI> + +<LI> +<A HREF="client_acceptor.h">client_acceptor.h</A></LI> + +<LI> +<A HREF="client_acceptor.cpp">client_acceptor.cpp</A></LI> + +<LI> +<A HREF="client_handler.cpp">client_handler.cpp</A></LI> + +<LI> +<A HREF="client_handler.h">client_handler.h</A></LI> + +<LI> +<A HREF="server.cpp">server.cpp</A></LI> + +<LI> +<A HREF="thread_pool.h">thread_pool.h</A></LI> + +<LI> +<A HREF="thread_pool.cpp">thread_poo.cpp</A></LI> + +<LI> +<A HREF="fix.Makefile">fix.Makefile</A></LI> +</UL> + + +<P> +<HR WIDTH="100%"> +<CENTER>[<A HREF="..">Tutorial +Index</A>]</CENTER> + +</BODY> +</HTML> diff --git a/docs/tutorials/007/server.cpp b/docs/tutorials/007/server.cpp new file mode 100644 index 00000000000..14ef85c44d1 --- /dev/null +++ b/docs/tutorials/007/server.cpp @@ -0,0 +1,107 @@ +// $Id$ + +/* + We try to keep main() very simple. One of the ways we do that is to push + much of the complicated stuff into worker objects. In this case, we only + need to include the acceptor header in our main source file. We let it + worry about the "real work". + */ + +#include "client_acceptor.h" + +/* + As before, we create a simple signal handler that will set our finished + flag. There are, of course, more elegant ways to handle program shutdown + requests but that isn't really our focus right now, so we'll just do the + easiest thing. + */ + +static sig_atomic_t finished = 0; +extern "C" void handler (int) +{ + finished = 1; +} + +/* + A server has to listen for clients at a known TCP/IP port. The default ACE + port is 10002 (at least on my system) and that's good enough for what we + want to do here. Obviously, a more robust application would take a command + line parameter or read from a configuration file or do some other clever + thing. Just like the signal handler above, though, that's what we want to + focus on, so we're taking the easy way out. + */ + +static const u_short PORT = ACE_DEFAULT_SERVER_PORT; + +/* + Finally, we get to main. Some C++ compilers will complain loudly if your + function signature doesn't match the prototype. Even though we're not + going to use the parameters, we still have to specify them. + */ + +int main (int argc, char *argv[]) +{ +/* + In our earlier servers, we used a global pointer to get to the reactor. I've + never really liked that idea, so I've moved it into main() this time. When + we get to the Client_Handler object you'll see how we manage to get a + pointer back to this reactor. + */ + ACE_Reactor reactor; + + /* + The acceptor will take care of letting clients connect to us. It will + also arrange for a Client_Handler to be created for each new client. + Since we're only going to listen at one TCP/IP port, we only need one + acceptor. If we wanted, though, we could create several of these and + listen at several ports. (That's what we would do if we wanted to rewrite + inetd for instance.) + */ + Client_Acceptor peer_acceptor; + + /* + Create an ACE_INET_Addr that represents our endpoint of a connection. We + then open our acceptor object with that Addr. Doing so tells the acceptor + where to listen for connections. Servers generally listen at "well known" + addresses. If not, there must be some mechanism by which the client is + informed of the server's address. + + Note how ACE_ERROR_RETURN is used if we fail to open the acceptor. This + technique is used over and over again in our tutorials. + */ + if (peer_acceptor.open (ACE_INET_Addr (PORT), &reactor) == -1) + ACE_ERROR_RETURN ((LM_ERROR, "%p\n", "open"), -1); + + /* + Install our signal handler. You can actually register signal handlers + with the reactor. You might do that when the signal handler is + responsible for performing "real" work. Our simple flag-setter doesn't + justify deriving from ACE_Event_Handler and providing a callback function + though. + */ + ACE_Sig_Action sa ((ACE_SignalHandler) handler, SIGINT); + + /* + Like ACE_ERROR_RETURN, the ACE_DEBUG macro gets used quite a bit. It's a + handy way to generate uniform debug output from your program. + */ + ACE_DEBUG ((LM_DEBUG, "(%P|%t) starting up server daemon\n")); + + /* + This will loop "forever" invoking the handle_events() method of our + reactor. handle_events() watches for activity on any registered handlers + and invokes their appropriate callbacks when necessary. Callback-driven + programming is a big thing in ACE, you should get used to it. If the + signal handler catches something, the finished flag will be set and we'll + exit. Conveniently enough, handle_events() is also interrupted by signals + and will exit back to the while() loop. (If you want your event loop to + not be interrupted by signals, checkout the <i>restart</i> flag on the + open() method of ACE_Reactor if you're interested.) + */ + while (!finished) + reactor.handle_events (); + + ACE_DEBUG ((LM_DEBUG, "(%P|%t) shutting down server daemon\n")); + + return 0; +} diff --git a/docs/tutorials/007/thread_pool.cpp b/docs/tutorials/007/thread_pool.cpp new file mode 100644 index 00000000000..dc2ff7f2a39 --- /dev/null +++ b/docs/tutorials/007/thread_pool.cpp @@ -0,0 +1,262 @@ + +// $Id$ + +#include "thread_pool.h" + +/* + We need this header so that we can invoke handle_input() on the objects we dequeue. + */ +#include "ace/Event_Handler.h" + + +/* + All we do here is initialize our active thread counter. + */ +Thread_Pool::Thread_Pool(void) + : active_threads_(0) +{ +} + +/* + Our open() method is a thin disguise around the ACE_Task<> activate() method. By + hiding activate() in this way, the users of Thread_Pool don't have to worry about + the thread configuration flags. + */ +int Thread_Pool::open( int _pool_size ) +{ + return this->activate(THR_NEW_LWP|THR_DETACHED,_pool_size); +} + +/* + Closing the thread pool can be a tricky exercise. I've decided to take an easy approach + and simply enqueue a secret message for each thread we have active. + */ +int Thread_Pool::close(void) +{ + /* + Find out how many threads are currently active + */ + int counter = active_threads_.value(); + + /* + For each one of the active threads, enqueue a "null" event handler. Below, we'll + teach our svc() method that "null" means "shutdown". + */ + while( counter-- ) + { + this->enqueue( 0 ); + } + + /* + As each svc() method exits, it will decrement the active thread counter. We just wait + here for it to reach zero. Since we don't know how long it will take, we sleep for + a quarter-second or so between tries. + */ + while( active_threads_.value() ) + { + ACE_OS::sleep( ACE_Time_Value(0.25) ); + } + + return(0); +} + +/* + When an object wants to do work in the pool, it should call the enqueue() method. + We introduce the ACE_Message_Block here but, unfortunately, we seriously missuse it. + */ +int Thread_Pool::enqueue( ACE_Event_Handler * _handler ) +{ + /* + An ACE_Message_Block is a chunk of data. You put them into an ACE_Message_Queue. + ACE_Task<> has an ACE_Message_Queue built in. In fact, the parameter to ACE_Task<> + is passed directly to ACE_Message_Queue. If you look back at our header file you'll + see that we used ACE_MT_SYNCH as the parameter indicating that we want MultiThread + Synch safety. This allows us to safely put ACE_Message_Block objects into the + message queue in one thread and take them out in another. + */ + + /* + An ACE_Message_Block wants to have char* data. We don't have that. We could + cast our ACE_Event_Handler* directly to a char* but I wanted to be more explicit. + Since casting pointers around is a dangerous thing, I've gone out of my way here + to be very clear about what we're doing. + + First: Cast the handler pointer to a void pointer. You can't do any useful work + on a void pointer, so this is a clear message that we're making the + pointer unusable. + + Next: Cast the void pointer to a char pointer that the ACE_Message_Block will accept. + */ + void * v_data = (void*)_handler; + char * c_data = (char*)v_data; + + /* + Construct a new ACE_Message_Block. For efficiency, you might want to preallocate a + stack of these and reuse them. For simplicity, I'll just create what I need as I need it. + */ + ACE_Message_Block * mb = new ACE_Message_Block( c_data ); + + /* + Our putq() method is a wrapper around one of the enqueue methods of the ACE_Message_Queue + that we own. Like all good methods, it returns -1 if it fails for some reason. + */ + if( this->putq(mb) == -1 ) + { + /* + Another trait of the ACE_Message_Block objects is that they are reference counted. + Since they're designed to be passed around between various objects in several threads + we can't just delete them whenever we feel like it. The release() method is similar + to the destroy() method we've used elsewhere. It watches the reference count and will + delete the object when possible. + */ + mb->release(); + return(-1); + } + + return(0); +} + +/* + The "guard" concept is very powerful and used throughout multi-threaded applications. + A guard normally does some operation on an object at construction and the "opposite" + operation at destruction. For instance, when you guard a mutex (lock) object, the guard + will acquire the lock on construction and release it on destruction. In this way, your + method can simply let the guard go out of scope and know that the lock is released. + + Guards aren't only useful for locks however. In this application I've created two guard + objects for quite a different purpose. + */ + +/* + The Counter_Guard is constructed with a reference to the thread pool's active thread + counter. The guard increments the counter when it is created and decrements it at + destruction. By creating one of these in svc(), I know that the counter will be decremented + no matter how or where svc() returns. + */ +class Counter_Guard +{ +public: + Counter_Guard( Thread_Pool::counter_t & _counter ) + : counter_(_counter) + { + ++counter_; + } + + ~Counter_Guard(void) + { + --counter_; + } + +protected: + Thread_Pool::counter_t & counter_; +}; + +/* + My Message_Block_Guard is also a little non-traditional. It doesn't do anything in the + constructor but it's destructor ensures that the message block's release() method is called. + This is a cheap way to prevent a memory leak if I need an additional exit point in svc(). + */ +class Message_Block_Guard +{ +public: + Message_Block_Guard( ACE_Message_Block * & _mb ) + : mb_(_mb) + { + } + + ~Message_Block_Guard( void ) + { + mb_->release(); + } + +protected: + ACE_Message_Block * & mb_; +}; + +/* + Now we come to the svc() method. As I said, this is being executed in each thread of the + Thread_Pool. Here, we pull messages off of our built-in ACE_Message_Queue and cause them + to do work. + */ +int Thread_Pool::svc(void) +{ + /* + The getq() method takes a reference to a pointer. So... we need a pointer to give it + a reference to. + */ + ACE_Message_Block * mb; + + /* + Create the guard for our active thread counter object. No matter where we choose to + return() from svc(), we no know that the counter will be decremented. + */ + Counter_Guard counter_guard(active_threads_); + + /* + Get messages from the queue until we have a failure. There's no real good reason + for failure so if it happens, we leave immediately. + */ + while( this->getq(mb) != -1 ) + { + /* + A successful getq() will cause "mb" to point to a valid refernce-counted + ACE_Message_Block. We use our guard object here so that we're sure to call + the release() method of that message block and reduce it's reference count. + Once the count reaches zero, it will be deleted. + */ + Message_Block_Guard message_block_guard(mb); + + /* + As noted before, the ACE_Message_Block stores it's data as a char*. We pull that + out here and later turn it into an ACE_Event_Handler* + */ + char * c_data = mb->base(); + + /* + We've chosen to use a "null" value as an indication to leave. If the data we got + from the queue is not null then we have some work to do. + */ + if( c_data ) + { + /* + Once again, we go to great lengths to emphasize the fact that we're casting pointers + around in rather impolite ways. We could have cast the char* directly to an + ACE_Event_Handler* but then folks might think that's an OK thing to do. + + (Note: The correct way to use an ACE_Message_Block is to write data into it. + What I should have done was create a message block big enough to hold an + event handler pointer and then written the pointer value into the block. When + we got here, I would have to read that data back into a pointer. While politically + correct, it is also a lot of work. If you're careful you can get away with casting + pointers around.) + */ + void * v_data = (void*)c_data; + + ACE_Event_Handler * handler = (ACE_Event_Handler*)v_data; + + /* + Now that we finally have an event handler pointer, invoke it's handle_input() method. + Since we don't know it's handle, we just give it a default. That's OK because we + know that we're not using the handle in the method anyway. + */ + if( handler->handle_input(ACE_INVALID_HANDLE) == -1 ) + { + return(-1); // Error, return now + } + } + else + { + /* + If we get here, we were given a message block with "null" data. That is our + signal to leave, so we return(0) to leave gracefully. + */ + return(0); // Ok, shutdown request + } + + // message_block_guard goes out of scope here + // and releases the message_block instance. + } + + return(0); +} + diff --git a/docs/tutorials/007/thread_pool.h b/docs/tutorials/007/thread_pool.h new file mode 100644 index 00000000000..5fae81fae72 --- /dev/null +++ b/docs/tutorials/007/thread_pool.h @@ -0,0 +1,88 @@ + +// $Id$ + +#ifndef THREAD_POOL_H +#define THREAD_POOL_H + +/* + In order to implement a thread pool, we have to have an object that can create + a thread. The ACE_Task<> is the basis for doing just such a thing. + */ +#include "ace/Task.h" + +/* + We need a forward reference for ACE_Event_Handler so that our enqueue() method + can accept a pointer to one. + */ +class ACE_Event_Handler; + +/* + Although we modified the rest of our program to make use of the thread pool + implementation, if you look closely you'll see that the changes were rather + minor. The "ACE way" is generally to create a helper object that abstracts + away the details not relevant to your application. That's what I'm trying + to do here by creating the Thread_Pool object. + */ +class Thread_Pool : public ACE_Task<ACE_MT_SYNCH> +{ +public: + + /* + Provide an enumeration for the default pool size. By doing this, other objects + can use the value when they want a default. + */ + enum size_t + { + default_pool_size_ = 5 + }; + + // Basic constructor + Thread_Pool(void); + + /* + Opening the thread pool causes one or more threads to be activated. When activated, + they all execute the svc() method declared below. + */ + int open( int _pool_size = default_pool_size_ ); + + /* + When you're done wit the thread pool, you have to have some way to shut it down. + This is what close() is for. + */ + int close(void); + + /* + To use the thread pool, you have to put some unit of work into it. Since we're + dealing with event handlers (or at least their derivatives), I've chosen to provide + an enqueue() method that takes a pointer to an ACE_Event_Handler. The handler's + handle_input() method will be called, so your object has to know when it is being + called by the thread pool. + */ + int enqueue( ACE_Event_Handler * _handler ); + +protected: + + /* + Our svc() method will dequeue the enqueued event handler objects and invoke the + handle_input() method on each. Since we're likely running in more than one thread, + idle threads can take work from the queue while other threads are busy executing + handle_input() on some object. + */ + int svc(void); + + /* + Another handy ACE template is ACE_Atomic_Op<>. When parameterized, this allows + is to have a thread-safe counting object. The typical arithmetic operators are + all internally thread-safe so that you can share it across threads without worrying + about any contention issues. + */ + typedef ACE_Atomic_Op<ACE_Mutex,int> counter_t; + + /* + We use the atomic op to keep a count of the number of threads in which our svc() + method is running. This is particularly important when we want to close() it down! + */ + counter_t active_threads_; +}; + +#endif // THREAD_POOL_H |