<!-- page09.html,v 1.16 2003/08/19 15:08:26 schmidt Exp --> <HTML> <HEAD> <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> <META NAME="Author" CONTENT="James CE Johnson"> <TITLE>ACE Tutorial 015</TITLE> </HEAD> <BODY TEXT="#000000" BGCOLOR="#FFFFFF" LINK="#000FFF" VLINK="#FF0F0F"> <CENTER><B><FONT SIZE=+2>ACE Tutorial 015</FONT></B></CENTER> <CENTER><B><FONT SIZE=+2>Building a protocol stream</FONT></B></CENTER> <P> <HR WIDTH="100%"> Like any other event handler, the handle_input() method will be responsible for getting data from the peer() and doing something with it. In this case, we have a Protocol_Stream to deal with. We'll use the stream for the actual I/O but we are ultimately responsible for processing the data from the peer. To do that, we've created a Handler_Task that fits within the Protocol_Stream framework to process data that has been received. Handler::handle_input() will tell the stream that it's time to read data and that data will eventually show up at Handler_Task::recv() where we'll process it as required by our application logic. <HR> <PRE> <font color=red>// page09.html,v 1.16 2003/08/19 15:08:26 schmidt Exp</font> <font color=blue>#include</font> "<font color=green>Handler.h</font>" <font color=blue>#include</font> "<font color=green>Protocol_Task.h</font>" <font color=red>/* The Protocol_Stream gives us the option to insert a Protocol_Task to process data received by the stream. We'll get into the details more when we talk about the stream in detail. For now it's enough to know that <font color=#008888>Handler_Task::recv</font>() will be invoked by the stream after data from the client has been received and processed (eg -- decrypted, uncompressed, and whatever else the protocol requires.) */</font> class Handler_Task : public Protocol_Task { public: <font color=red>// Typical...</font> typedef Protocol_Task inherited; <font color=red>// Simple...</font> Handler_Task(void); ~Handler_Task(void); protected: <font color=red>// recv() is invoked after received data has been fully</font> <font color=red>// processed by the protocol rules. Data processing typically</font> <font color=red>// done in handle_input() can then be done here.</font> int recv(ACE_Message_Block * message, ACE_Time_Value *timeout = 0); }; <font color=#008888>Handler::Handler</font>(void) { ; } <font color=#008888>Handler::~Handler</font>(void) { ; } <font color=red>/* The Acceptor will open() us once the peer() connection is established. There are a couple of things we have to do here before we're ready to receive data from the client. */</font> int <font color=#008888>Handler::open</font> (void *) { ACE_INET_Addr addr; <font color=red>// Make sure that we can get the peer's address. If we can't</font> <font color=red>// then there may be a network error or something else that</font> <font color=red>// will prevent communicating with the client. This is</font> <font color=red>// something you'll want to do in every event handler you create.</font> if (this->peer ().get_remote_addr (addr) == -1) ACE_ERROR_RETURN ((LM_ERROR, "<font color=green>(%P|%t) Cannot get remote addr\n</font>"), -1); <font color=red>// Announce the client</font> ACE_DEBUG ((LM_DEBUG, "<font color=green>(%P|%t) connected with %s\n</font>", addr.get_host_name() )); <font color=red>// Here's the first new twist to the old event handler.</font> <font color=red>// Before we can use the Protocol_Stream to communicate with</font> <font color=red>// the peer, we must open() it. We provide the stream with</font> <font color=red>// the peer() so that it will have a valid socket on which to</font> <font color=red>// read client requests and send our responses. We also</font> <font color=red>// provide a Handler_Task instance that will ultimately be</font> <font color=red>// responsible for processing any client data we receive.</font> int rval = stream().open( this->peer(), new Handler_Task() ); <font color=red>// Of course, we have to account for the chance that the</font> <font color=red>// stream's open() may fail.</font> if( rval == -1 ) { ACE_ERROR_RETURN ((LM_ERROR, "<font color=green>(%P|%t) Cannot open the protocol stream.\n</font>"), -1); } <font color=red>// Now that we know the client is valid and that the stream is</font> <font color=red>// ready for business we can register with the gloabl reactor</font> <font color=red>// instance. Here again is an opportunity for improvement if</font> <font color=red>// we expect to have mulitple Server object instances.</font> if (<font color=#008888>ACE_Reactor::instance</font>()->register_handler (this, ACE_Event_Handler::READ_MASK) == -1) ACE_ERROR_RETURN ((LM_ERROR, "<font color=green>(%P|%t) Cannot register with reactor\n</font>"), -1); return rval; } <font color=red>/* This is a fairly typical destroy() method that can be shared by both close() and handle_close(). */</font> void <font color=#008888>Handler::destroy</font> (void) { <font color=#008888>ACE_Reactor::instance</font>()->remove_handler(this,ACE_Event_Handler::READ_MASK|ACE_Event_Handler::DONT_CALL); this->peer ().close (); delete this; } <font color=red>/* In this simple application we just forward the close() and handle_close() requests right on to the destroy() method. */</font> int <font color=#008888>Handler::close</font> (u_long) { this->destroy (); return 0; } int <font color=#008888>Handler::handle_close</font>(ACE_HANDLE, ACE_Reactor_Mask) { this->destroy(); return 0; } <font color=red>/* Unlike a "<font color=green>traditional</font>" handle_input() ours is very simple. Because of the use of the protocol stream, we delegate the read function to the stream's get() and rely on our Handler_Task to do the real work. */</font> int <font color=#008888>Handler::handle_input</font> (ACE_HANDLE) { ACE_DEBUG ((LM_DEBUG, "<font color=green>(%P|%t) Activity from client\n</font>" )); <font color=red>// This will cause a blocking read from the peer(). The data</font> <font color=red>// will then be pushed through the protocol stream.</font> if( stream().get( ) == -1 ) { ACE_ERROR_RETURN ((LM_ERROR, "<font color=green>(%P|%t) Cannot get data from protocol stream\n</font>"), -1); } return 0; } <font color=red>/* A Protocol_Task is derived from ACE_Task and has the option of running in one or more threads. I've chosen here to construct the baseclass with no threads but it should work just fine with one or more if you need. Unless you're sharing the Handler_Task with several peers, however, you're probably just wasting a thread to activate it. On the other hand, if your reactor is running in a single thread (as in this example) then you can easily implement thread-per-connection concurrency by giving the baseclass one thread. */</font> <font color=#008888>Handler_Task::Handler_Task</font>(void) : inherited() { ; } <font color=#008888>Handler_Task::~Handler_Task</font>(void) { ; } <font color=red>/* When installed into the protocol stream, the Handler_Task's recv() method will be called when data is ready for processing. */</font> int <font color=#008888>Handler_Task::recv</font>(ACE_Message_Block * message, ACE_Time_Value *timeout ) { <font color=red>// Announce the request we got from the client</font> ACE_DEBUG ((LM_INFO, "<font color=green>(%P|%t) <font color=#008888>Handler_Task::recv</font>() got (%s)\n</font>", message->rd_ptr() )); <font color=red>// Create a response message to send to the client</font> ACE_Message_Block * response = new ACE_Message_Block( 128 ); <font color=red>// Nothing very original about this I'm afraid...</font> <font color=#008888>ACE_OS::sprintf</font>( response->wr_ptr(), "<font color=green>You Said: (%s)</font>", message->rd_ptr() ); response->wr_ptr( strlen(response->wr_ptr())+1 ); <font color=red>// Release the original message block now that we're through</font> <font color=red>// "<font color=green>processing</font>" it.</font> message->release(); <font color=red>// Turn the message around and send it back down the Stream.</font> <font color=red>// In other words, we invoke the put() method on the</font> <font color=red>// Protocol_Stream without having to have a direct reference</font> <font color=red>// to the stream object.</font> return this->reply( response, timeout ); } </PRE> <HR> <P> That's it for the server-specific code. I think I've been fairly successful in keeping it simple and to the point. There are a couple of places where the as-yet-undescribed Protocol_Stream pops up and may cause confusion. We're going to discuss that mystery now but before we do here's the list of server files if you want to review: <UL> <LI><A HREF="Makefile.server">Server Makefile</A> <LI><A HREF="server.cpp">server.cpp</A> <LI><A HREF="Server_i.h">Server_i.h</A> <LI><A HREF="Server_i.cpp">Server_i.cpp</A> <LI><A HREF="Handler.h">Handler.h</A> <LI><A HREF="Handler.cpp">Handler.cpp</A> </UL> <P> <P><HR WIDTH="100%"> <CENTER>[<A HREF="../online-tutorials.html">Tutorial Index</A>] [<A HREF="page10.html">Continue This Tutorial</A>]</CENTER>