tcpservice.cpp

00001 //
00002 // tcpservice.cpp
00003 //
00004 //  Copyright 2000 - Gianni Mariani <gianni@mariani.ws>
00005 //
00006 //  An example of a simple chatty server using CommonC++.
00007 //
00008 //  This simple application basically operates as a
00009 //  very simple chat system. From a telnet session
00010 //  on localhost:3999 , any messages typed from a telnet
00011 //  client are written to all participating sessions.
00012 //
00013 //  This is free software licensed under the terms of the GNU
00014 //  Public License
00015 //
00016 //  This example:
00017 //
00018 //  This demostrates a simple threaded server, actually,
00019 //  the sessions are not all threaded though they could be
00020 //  if that's what you wanted.  Basically it demonstrates the
00021 //  use of SocketService, SocketPorts and Threads.
00022 //
00023 //  For those familiar with Unix network programming, SocketService
00024 //  basically encapsulates all the work to communicate with
00025 //  the select() or poll() system calls.  SocketPorts are
00026 //  basically encapsulations of sessions or open file descriptors.
00027 //
00028 //  Anyhow, this example is a very simple echo server but
00029 //  it echos to all connected clients.  So it's a poor man's
00030 //  IRC !  You connect via telnet to localhost port 3999 and
00031 //  it will echo to all other connected clients what you type in !
00032 //
00033 
00034 #include <cc++/socketport.h>
00035 
00036 #include <iostream>
00037 
00038 // For starters, we need a thread safe list, we'll make one
00039 // out of the STL list<> template -
00040 //  http://www.sgi.com/Technology/STL/index.html
00041 //
00042 // Thread safe list class
00043 //
00044 #include <list>
00045 
00046 #ifdef  CCXX_NAMESPACES
00047 using namespace std;
00048 using namespace ost;
00049 #endif
00050 
00051 class ts_list_item;
00052 typedef list<ts_list_item *> ts_list;
00053 
00054 // a list head - containing a list and a Mutex.
00055 // It would be really nice to teach stl to do this.
00056 
00057 class ts_list_head {
00058 public:
00059 
00060     // No point inheriting, I'd have to implement
00061     // alot of code. We'll hold off on that exercise.
00062 
00063     // Using the CommonC++ Mutex class.
00064     Mutex                   linkmutex;
00065     // And the STL template.
00066     ts_list                 list_o_items;
00067 
00068     // Not nessasary, but nice to be explicit.
00069     ts_list_head()
00070         : linkmutex(), list_o_items()
00071     {
00072     }
00073 
00074     // This thing knows how to remove and insert items.
00075     void RemoveListItem( ts_list_item * li );
00076     void InsertListItem( ts_list_item * li );
00077 
00078     // And it knows how to notify that it became empty
00079     // or an element was deleted and it was the last one.
00080     virtual void ListDepleted()
00081     {
00082     }
00083 
00084     virtual ~ts_list_head()
00085     {
00086     }
00087 };
00088 
00089 
00090 // This item knows how to remove itself from the
00091 // list it belongs to.
00092 class ts_list_item {
00093 public:
00094     ts_list::iterator      linkpoint;
00095     ts_list_head          * listhead;
00096 
00097     virtual ~ts_list_item()
00098     {
00099         listhead->RemoveListItem( this );
00100     }
00101 
00102     ts_list_item( ts_list_head * head )
00103     {
00104         listhead = head;
00105         head->InsertListItem( this );
00106     }
00107 };
00108 
00109 void ts_list_head::RemoveListItem( ts_list_item * li )
00110 {
00111     bool    is_empty;
00112     linkmutex.enterMutex();
00113     list_o_items.erase( li->linkpoint );
00114     is_empty = list_o_items.empty();
00115     linkmutex.leaveMutex();
00116 
00117     // There is a slim possibility that at this time
00118     // we recieve a connection.
00119     if ( is_empty ) {
00120         ListDepleted();
00121     }
00122 }
00123 
00124 void ts_list_head::InsertListItem( ts_list_item * li )
00125 {
00126     linkmutex.enterMutex();
00127     list_o_items.push_front( li );
00128     li->linkpoint = list_o_items.begin();
00129     linkmutex.leaveMutex();
00130 }
00131 
00132 // ChatterSession operates on the individual connections
00133 // from clients as are managed by the SocketService
00134 // contained in CCExec.  ChatterThread simply waits in
00135 // a loop to create these, listening forever.
00136 //
00137 // Even though the SocketService contains a list of
00138 // clients it serves, it may actually serve more than
00139 // one type of session so we create our own list by
00140 // inheriting the ts_list_item.
00141 //
00142 
00143 class ChatterSession :
00144     public virtual SocketPort,
00145     public virtual ts_list_item
00146 {
00147 public:
00148 
00149         enum { size_o_buf = 2048 };
00150 
00151     // Nothing special to do here, it's all handled
00152     // by SocketPort and ts_list_item
00153 
00154     virtual ~ChatterSession()
00155     {
00156         cerr << "ChatterSession deleted !\n";
00157     }
00158 
00159     // When you create a ChatterSession it waits to accept a
00160     // connection.  This is done by it's own
00161     ChatterSession(
00162         TCPSocket      & server,
00163         SocketService   * svc,
00164         ts_list_head    * head
00165     ) :
00166         SocketPort( NULL, server ),
00167         ts_list_item( head )
00168     {
00169         cerr << "ChatterSession Created\n";
00170 
00171         tpport_t port;
00172         InetHostAddress ia = getPeer( & port );
00173 
00174         cerr << "connecting from " << ia.getHostname() <<
00175         ":" << port << endl;
00176 
00177         // Set up non-blocking reads
00178         setCompletion( false );
00179 
00180         // Set yerself to time out in 10 seconds
00181         setTimer( 100000 );
00182         attach(svc);
00183     }
00184 
00185     //
00186     // This is called by the SocketService thread when it the
00187     // object has expired.
00188     //
00189 
00190     virtual void expired()
00191     {
00192         // Get outa here - this guy is a LOOSER - type or terminate
00193         cerr << "ChatterSession Expired\n";
00194         delete this;
00195     }
00196 
00197     //
00198     // This is called by the SocketService thread when it detects
00199     // that there is somthing to read on this connection.
00200     //
00201 
00202     virtual void pending()
00203     {
00204         // Implement the echo
00205 
00206         cerr << "Pending called\n";
00207 
00208         // reset the timer
00209         setTimer( 100000 );
00210         try {
00211             int    len;
00212             unsigned int total = 0;
00213             char    buf[ size_o_buf ];
00214 
00215             while ( (len = receive(buf, sizeof(buf) )) > 0 ) {
00216                 total += len;
00217                 cerr << "Read '";
00218                 cerr.write( buf, len );
00219                 cerr << "'\n";
00220 
00221                 // Send it to all the sessions.
00222                 // We probably don't really want to lock the
00223                 // entire list for the entire time.
00224                 // The best way to do this would be to place the
00225                 // message somewhere and use the service function.
00226                 // But what are examples for ?
00227 
00228                 bool sent = false;
00229                 listhead->linkmutex.enterMutex();
00230                 for (
00231                    ts_list::iterator iter = listhead->list_o_items.begin();
00232                    iter != listhead->list_o_items.end();
00233                    iter ++
00234                 ) {
00235                    ChatterSession * sess =
00236                     dynamic_cast< ChatterSession * >( * iter );
00237                    if ( sess != this ) {
00238                     sess->send( buf, len );
00239                     sent = true;
00240                    }
00241                 }
00242                 listhead->linkmutex.leaveMutex();
00243 
00244                 if ( ! sent ) {
00245                    send(
00246                     ( void * ) "No one else listening\n",
00247                     sizeof( "No one else listening\n" ) - 1
00248                    );
00249 
00250                    send( buf, len );
00251                 }
00252             }
00253             if (total == 0)
00254             {
00255                 cerr << "Broken connection!\n" << endl;
00256                 delete this;
00257             }
00258         }
00259         catch ( ... )
00260         {
00261             // somthing wrong happened here !
00262             cerr << "Socket port write sent an exception !\n";
00263         }
00264 
00265     }
00266 
00267     virtual void disconnect()
00268     {
00269         // Called by the SocketService thread when the client
00270         // hangs up.
00271         cerr << "ChatterSession disconnected!\n";
00272 
00273         delete this;
00274     }
00275 
00276 };
00277 
00278 class ChatterThread;
00279 
00280 //
00281 // This is the main application object containing all the
00282 // state for the application.  It uses a SocketService object
00283 // (and thread) to do all the work, however, that object could
00284 // theoretically be use by more than one main application.
00285 //
00286 // It creates a ChatterThread to sit and wait for connections
00287 // from clients.
00288 
00289 class CCExec : public virtual ts_list_head {
00290 public:
00291 
00292     SocketService           * service;
00293     ChatterThread              * my_Chatter;
00294     Semaphore                 mainsem[1];
00295 
00296     CCExec():my_Chatter(NULL)
00297     {
00298         service = new SocketService( 0 );
00299     }
00300 
00301     virtual void ListDepleted();
00302 
00303     // These methods defined later.
00304     virtual ~CCExec();
00305     int RunApp( char * hn = "localhost" );
00306 
00307 };
00308 
00309 //
00310 // ChatterThread simply creates ChatterSession all the time until
00311 // it has an error.  I suspect you could create as many of these
00312 // as the OS could take.
00313 //
00314 
00315 class ChatterThread : public virtual TCPSocket, public virtual Thread {
00316 public:
00317 
00318     CCExec              * exec;
00319 
00320     void run ()
00321     {
00322         while ( 1 ) {
00323             try {
00324                 // new does all the work to accept a new connection
00325                 // attach itself to the SocketService AND include
00326                 // itself in the CCExec list of sessions.
00327                 new ChatterSession(
00328                    * ( TCPSocket * ) this,
00329                    exec->service,
00330                    exec
00331                 );
00332             }
00333             catch ( ... )
00334             {
00335                 // Bummer - there was an error.
00336                 cerr << "ChatterSession create failed\n";
00337                 exit();
00338             }
00339         }
00340     }
00341 
00342     ChatterThread(
00343         InetHostAddress & machine,
00344         int           port,
00345         CCExec         * inexec
00346 
00347     ) : TCPSocket( machine, port ),
00348         Thread(),
00349         exec( inexec )
00350     {
00351             start();
00352     }
00353 
00354 
00355 };
00356 
00357 //
00358 // Bug here, this should go ahead and shut down all sessions
00359 // for application.  An exercise left to the reader.
00360 
00361 CCExec::~CCExec()
00362 {
00363     // MUST delete my_Chatter first or it may end up using
00364     // a deleted service.
00365     if ( my_Chatter ) delete my_Chatter;
00366 
00367     // Delete whatever is left.
00368     delete service;
00369 }
00370 
00371 //
00372 // Run App would normally read some config file or take some
00373 // parameters about which port to connect to and then
00374 // do that !
00375 int CCExec::RunApp( char * hn )
00376 {
00377     // which port ?
00378 
00379     InetHostAddress machine( hn );
00380 
00381     if ( machine.isInetAddress() == false ) {
00382         cerr << "machine is not address" << endl;
00383     }
00384 
00385     cerr << "machine is " << machine.getHostname() << endl;
00386 
00387     // Start accepting connections - this will bind to the
00388     // port as well.
00389     try {
00390         my_Chatter = new ChatterThread(
00391             machine,
00392             3999,
00393             this
00394         );
00395     }
00396     catch ( ... )
00397     {
00398         cerr << "Failed to bind\n";
00399         return false;
00400     }
00401     
00402     return true;
00403 }
00404 
00405 // When there is no one else connected - terminate !
00406 void CCExec::ListDepleted()
00407 {
00408     mainsem->post();
00409 }
00410 
00411 
00412 int main( int argc, char ** argv )
00413 {
00414     CCExec      * server;
00415 
00416     server = new CCExec();
00417 
00418     // take the first command line option as a hostname
00419     // to listen to.
00420     if ( argc > 1 ) {
00421         server->RunApp( argv[ 1 ] );
00422     } else {
00423         server->RunApp();
00424     }
00425 
00426     server->mainsem->wait();
00427 
00428     delete server;
00429 
00430     return 0;
00431 }

Generated on Tue Jan 9 16:38:58 2007 for GNU CommonC++ by  doxygen 1.5.1