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         // This thing knows how to remove and insert items.
00074         void RemoveListItem( ts_list_item * li );
00075         void InsertListItem( ts_list_item * li );
00076 
00077         // And it knows how to notify that it became empty
00078         // or an element was deleted and it was the last one.
00079         virtual void ListDepleted() {
00080         }
00081 
00082         virtual ~ts_list_head() {
00083         }
00084 };
00085 
00086 
00087 // This item knows how to remove itself from the
00088 // list it belongs to.
00089 class ts_list_item {
00090 public:
00091         ts_list::iterator          linkpoint;
00092         ts_list_head      * listhead;
00093 
00094         virtual ~ts_list_item() {
00095         listhead->RemoveListItem( this );
00096         }
00097 
00098         ts_list_item( ts_list_head * head ) {
00099         listhead = head;
00100         head->InsertListItem( this );
00101         }
00102 };
00103 
00104 void ts_list_head::RemoveListItem( ts_list_item * li )
00105 {
00106         bool    is_empty;
00107         linkmutex.enterMutex();
00108         list_o_items.erase( li->linkpoint );
00109         is_empty = list_o_items.empty();
00110         linkmutex.leaveMutex();
00111 
00112         // There is a slim possibility that at this time
00113         // we recieve a connection.
00114         if ( is_empty ) {
00115         ListDepleted();
00116         }
00117 }
00118 
00119 void ts_list_head::InsertListItem( ts_list_item * li )
00120 {
00121         linkmutex.enterMutex();
00122         list_o_items.push_front( li );
00123         li->linkpoint = list_o_items.begin();
00124         linkmutex.leaveMutex();
00125 }
00126 
00127 // ChatterSession operates on the individual connections
00128 // from clients as are managed by the SocketService
00129 // contained in CCExec.  ChatterThread simply waits in
00130 // a loop to create these, listening forever.
00131 //
00132 // Even though the SocketService contains a list of
00133 // clients it serves, it may actually serve more than
00134 // one type of session so we create our own list by
00135 // inheriting the ts_list_item.
00136 //
00137 
00138 class ChatterSession :
00139         public virtual SocketPort,
00140         public virtual ts_list_item {
00141 public:
00142 
00143         enum { size_o_buf = 2048 };
00144 
00145         // Nothing special to do here, it's all handled
00146         // by SocketPort and ts_list_item
00147 
00148         virtual ~ChatterSession() {
00149         cerr << "ChatterSession deleted !\n";
00150         }
00151 
00152         // When you create a ChatterSession it waits to accept a
00153         // connection.  This is done by it's own
00154         ChatterSession(
00155         TCPSocket      & server,
00156         SocketService   * svc,
00157         ts_list_head    * head
00158         ) :
00159         SocketPort( NULL, server ),
00160         ts_list_item( head ) {
00161         cerr << "ChatterSession Created\n";
00162 
00163         tpport_t port;
00164         InetHostAddress ia = getPeer( & port );
00165 
00166         cerr << "connecting from " << ia.getHostname() <<
00167         ":" << port << endl;
00168 
00169         // Set up non-blocking reads
00170         setCompletion( false );
00171 
00172         // Set yerself to time out in 10 seconds
00173         setTimer( 100000 );
00174         attach(svc);
00175         }
00176 
00177         //
00178         // This is called by the SocketService thread when it the
00179         // object has expired.
00180         //
00181 
00182         virtual void expired() {
00183         // Get outa here - this guy is a LOOSER - type or terminate
00184         cerr << "ChatterSession Expired\n";
00185         delete this;
00186         }
00187 
00188         //
00189         // This is called by the SocketService thread when it detects
00190         // that there is somthing to read on this connection.
00191         //
00192 
00193         virtual void pending() {
00194         // Implement the echo
00195 
00196         cerr << "Pending called\n";
00197 
00198         // reset the timer
00199         setTimer( 100000 );
00200         try {
00201             int    len;
00202             unsigned int total = 0;
00203             char    buf[ size_o_buf ];
00204 
00205             while ( (len = receive(buf, sizeof(buf) )) > 0 ) {
00206                 total += len;
00207                 cerr << "Read '";
00208                 cerr.write( buf, len );
00209                 cerr << "'\n";
00210 
00211                 // Send it to all the sessions.
00212                 // We probably don't really want to lock the
00213                 // entire list for the entire time.
00214                 // The best way to do this would be to place the
00215                 // message somewhere and use the service function.
00216                 // But what are examples for ?
00217 
00218                 bool sent = false;
00219                 listhead->linkmutex.enterMutex();
00220                 for (
00221                    ts_list::iterator iter = listhead->list_o_items.begin();
00222                    iter != listhead->list_o_items.end();
00223                    iter ++
00224                 ) {
00225                    ChatterSession * sess =
00226                     dynamic_cast< ChatterSession * >( * iter );
00227                    if ( sess != this ) {
00228                     sess->send( buf, len );
00229                     sent = true;
00230                    }
00231                 }
00232                 listhead->linkmutex.leaveMutex();
00233 
00234                 if ( ! sent ) {
00235                    send(
00236                     ( void * ) "No one else listening\n",
00237                     sizeof( "No one else listening\n" ) - 1
00238                    );
00239 
00240                    send( buf, len );
00241                 }
00242             }
00243             if (total == 0) {
00244                 cerr << "Broken connection!\n" << endl;
00245                 delete this;
00246             }
00247         }
00248         catch ( ... ) {
00249             // somthing wrong happened here !
00250             cerr << "Socket port write sent an exception !\n";
00251         }
00252 
00253         }
00254 
00255         virtual void disconnect() {
00256         // Called by the SocketService thread when the client
00257         // hangs up.
00258         cerr << "ChatterSession disconnected!\n";
00259 
00260         delete this;
00261         }
00262 
00263 };
00264 
00265 class ChatterThread;
00266 
00267 //
00268 // This is the main application object containing all the
00269 // state for the application.  It uses a SocketService object
00270 // (and thread) to do all the work, however, that object could
00271 // theoretically be use by more than one main application.
00272 //
00273 // It creates a ChatterThread to sit and wait for connections
00274 // from clients.
00275 
00276 class CCExec : public virtual ts_list_head {
00277 public:
00278 
00279         SocketService       * service;
00280         ChatterThread          * my_Chatter;
00281         Semaphore                     mainsem[1];
00282 
00283         CCExec():my_Chatter(NULL) {
00284         service = new SocketService( 0 );
00285         }
00286 
00287         virtual void ListDepleted();
00288 
00289         // These methods defined later.
00290         virtual ~CCExec();
00291         int RunApp( char * hn = "localhost" );
00292 
00293 };
00294 
00295 //
00296 // ChatterThread simply creates ChatterSession all the time until
00297 // it has an error.  I suspect you could create as many of these
00298 // as the OS could take.
00299 //
00300 
00301 class ChatterThread : public virtual TCPSocket, public virtual Thread {
00302 public:
00303 
00304         CCExec          * exec;
00305 
00306         void run () {
00307         while ( 1 ) {
00308             try {
00309                 // new does all the work to accept a new connection
00310                 // attach itself to the SocketService AND include
00311                 // itself in the CCExec list of sessions.
00312                 new ChatterSession(
00313                    * ( TCPSocket * ) this,
00314                    exec->service,
00315                    exec
00316                 );
00317             }
00318             catch ( ... ) {
00319                 // Bummer - there was an error.
00320                 cerr << "ChatterSession create failed\n";
00321                 exit();
00322             }
00323         }
00324         }
00325 
00326         ChatterThread(
00327         InetHostAddress & machine,
00328         int           port,
00329         CCExec         * inexec
00330 
00331         ) : TCPSocket( machine, port ),
00332         Thread(),
00333         exec( inexec ) {
00334             start();
00335         }
00336 
00337 
00338 };
00339 
00340 //
00341 // Bug here, this should go ahead and shut down all sessions
00342 // for application.  An exercise left to the reader.
00343 
00344 CCExec::~CCExec()
00345 {
00346         // MUST delete my_Chatter first or it may end up using
00347         // a deleted service.
00348         if ( my_Chatter ) delete my_Chatter;
00349 
00350         // Delete whatever is left.
00351         delete service;
00352 }
00353 
00354 //
00355 // Run App would normally read some config file or take some
00356 // parameters about which port to connect to and then
00357 // do that !
00358 int CCExec::RunApp( char * hn )
00359 {
00360         // which port ?
00361 
00362         InetHostAddress machine( hn );
00363 
00364         if ( machine.isInetAddress() == false ) {
00365         cerr << "machine is not address" << endl;
00366         }
00367 
00368         cerr << "machine is " << machine.getHostname() << endl;
00369 
00370         // Start accepting connections - this will bind to the
00371         // port as well.
00372         try {
00373         my_Chatter = new ChatterThread(
00374             machine,
00375             3999,
00376             this
00377         );
00378         }
00379         catch ( ... ) {
00380         cerr << "Failed to bind\n";
00381         return false;
00382         }
00383 
00384         return true;
00385 }
00386 
00387 // When there is no one else connected - terminate !
00388 void CCExec::ListDepleted()
00389 {
00390         mainsem->post();
00391 }
00392 
00393 
00394 int main( int argc, char ** argv )
00395 {
00396         CCExec  * server;
00397 
00398         server = new CCExec();
00399 
00400         // take the first command line option as a hostname
00401         // to listen to.
00402         if ( argc > 1 ) {
00403         server->RunApp( argv[ 1 ] );
00404         } else {
00405         server->RunApp();
00406         }
00407 
00408         server->mainsem->wait();
00409 
00410         delete server;
00411 
00412         return 0;
00413 }

Generated on Sat May 12 17:38:39 2007 for GNU CommonC++ by  doxygen 1.4.7