<!-- page06.html,v 1.12 2000/03/19 20:09:24 jcej Exp --> <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> <HR> <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%"> <PRE> <font color=red>// page06.html,v 1.12 2000/03/19 20:09:24 jcej Exp</font> <font color=red>/* 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. */</font> <font color=blue>#include</font> "<font color=green>client_acceptor.h</font>" <font color=blue>#include</font> "<font color=green>client_handler.h</font>" <font color=red>/* 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. */</font> <font color=blue>#define</font> <font color=purple>REGISTER_MASK</font> <font color=#008888>ACE_Event_Handler::READ_MASK</font> <font color=blue>#define</font> <font color=purple>REMOVE_MASK</font> (<font color=#008888>ACE_Event_Handler::READ_MASK</font> | ACE_Event_Handler::DONT_CALL) <font color=red>/* Our constructor still doesn't really do anything. We simply initialize the acceptor pointer to "<font color=green>null</font>" and get our current thread id. The static self() method of ACE_Thread will return you a thread id native to your platform. */</font> <font color=#008888>Client_Handler::Client_Handler</font> (void) : client_acceptor_(0), creator_ (<font color=#008888>ACE_Thread::self</font> ()) { } <font color=#008888>Client_Handler::~Client_Handler</font> (void) { this->peer().close(); } <font color=red>/* 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... */</font> int <font color=#008888>Client_Handler::concurrency</font>(void) { return this->client_acceptor ()->concurrency (); } <font color=red>/* And here we ask the acceptor about the thread pool. */</font> Thread_Pool * <font color=#008888>Client_Handler::thread_pool</font> (void) { return this->client_acceptor ()->thread_pool (); } <font color=red>/* Back to our open() method. This is straight out of Tutorial 6. There's nothing additional here for the thread-pool implementation. */</font> int <font color=#008888>Client_Handler::open</font> (void *acceptor) { client_acceptor ((Client_Acceptor *) acceptor); if (concurrency () == <font color=#008888>Client_Acceptor::thread_per_connection_</font>) return this->activate (THR_DETACHED); 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, "<font color=green>(%P|%t) can't register with reactor\n</font>"), -1); ACE_DEBUG ((LM_DEBUG, "<font color=green>(%P|%t) connected with %s\n</font>", addr.get_host_name ())); return 0; } <font color=red>/* The destroy() method will remove us from the reactor (with the DONT_CALL flag set!) and then free our memory. This allows us to be closed from outside of the reactor context without any danger. */</font> void <font color=#008888>Client_Handler::destroy</font> (void) { this->reactor ()->remove_handler (this, REMOVE_MASK); delete this; } <font color=red>/* As mentioned in the header, the typical way to close an object in a threaded context is to invoke it's close() method. */</font> int <font color=#008888>Client_Handler::close</font> (u_long flags) { ACE_UNUSED_ARG(flags); <font color=red>/* We use the destroy() method to clean up after ourselves. That will take care of removing us from the reactor and then freeing our memory. */</font> this->destroy (); <font color=red>/* Don't forward the close() to the baseclass! handle_close() above has already taken care of delete'ing. Forwarding close() would cause that to happen again and things would get really ugly at that point! */</font> return 0; } <font color=red>/* We will be called when handle_input() returns -1. That's our queue to delete ourselves to prevent memory leaks. */</font> int <font color=#008888>Client_Handler::handle_close</font> (ACE_HANDLE handle, ACE_Reactor_Mask mask) { ACE_UNUSED_ARG (handle); ACE_UNUSED_ARG (mask); delete this; return 0; } <font color=red>/* 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. */</font> int <font color=#008888>Client_Handler::handle_input</font> (ACE_HANDLE handle) { ACE_UNUSED_ARG (handle); <font color=red>/* 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. */</font> if (concurrency () == <font color=#008888>Client_Acceptor::thread_pool_</font>) { if (<font color=#008888>ACE_OS::thr_equal</font> (ACE_Thread::self(), creator_)) { <font color=red>/* 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.) By removing ourselves from the reactor, we're guaranteed that we won't be called back until the thread pool picks us up out of the queue. If we didn't remove ourselves, then the reactor would continue to invoke handle_input() and we don't want that to happen. */</font> this->reactor ()->remove_handler (this, REMOVE_MASK); return this->thread_pool ()->enqueue (this); } } <font color=red>/* 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. */</font> char buf[BUFSIZ]; <font color=red>/* Invoke the process() method to do the work but save it's return value instead of returning it immediately. */</font> int rval = this->process (buf, sizeof (buf)); <font color=red>/* 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.) */</font> if (concurrency () == <font color=#008888>Client_Acceptor::thread_pool_</font>) { if (rval != -1) <font color=red>/* If we don't remember to re-register ourselves, then we won't be able to respond to any future client requests. */</font> this->reactor ()->register_handler (this, REGISTER_MASK); } <font color=red>/* Return the result of process() */</font> return rval; } <font color=red>/* Remember that when we leave our svc() method, the framework will take care of calling our close() method so that we can cleanup after ourselves. */</font> int <font color=#008888>Client_Handler::svc</font> (void) { char buf[BUFSIZ]; while (1) if (this->process (buf, sizeof (buf)) == -1) return -1; return 0; } <font color=red>/* 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. */</font> int <font color=#008888>Client_Handler::process</font> (char *rdbuf, int rdbuf_len) { ssize_t bytes_read; switch ( (bytes_read = this->peer ().recv (rdbuf, rdbuf_len)) ) { case -1: ACE_ERROR_RETURN ((LM_ERROR, "<font color=green>(%P|%t) %p bad read\n</font>", "<font color=green>client</font>"), -1); case 0: ACE_ERROR_RETURN ((LM_ERROR, "<font color=green>(%P|%t) closing daemon (fd = %d)\n</font>", this->get_handle ()), -1); default: rdbuf[bytes_read] = 0; ACE_DEBUG ((LM_DEBUG, "<font color=green>(%P|%t) from client: %s</font>", rdbuf)); } return 0; } </PRE> <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="../online-tutorials.html">Tutorial Index</A>] [<A HREF="page07.html">Continue This Tutorial</A>]</CENTER>