// // tcpservice.cpp // // Copyright 2000 - Gianni Mariani // // An example of a simple chatty server using CommonC++. // // This simple application basically operates as a // very simple chat system. From a telnet session // on localhost:3999 , any messages typed from a telnet // client are written to all participating sessions. // // This is free software licensed under the terms of the GNU // Public License // // This example: // // This demostrates a simple threaded server, actually, // the sessions are not all threaded though they could be // if that's what you wanted. Basically it demonstrates the // use of SocketService, SocketPorts and Threads. // // For those familiar with Unix network programming, SocketService // basically encapsulates all the work to communicate with // the select() or poll() system calls. SocketPorts are // basically encapsulations of sessions or open file descriptors. // // Anyhow, this example is a very simple echo server but // it echos to all connected clients. So it's a poor man's // IRC ! You connect via telnet to localhost port 3999 and // it will echo to all other connected clients what you type in ! // #include #include // For starters, we need a thread safe list, we'll make one // out of the STL list<> template - // http://www.sgi.com/Technology/STL/index.html // // Thread safe list class // #include #ifdef CCXX_NAMESPACES using namespace std; using namespace ost; #endif class ts_list_item; typedef list ts_list; // a list head - containing a list and a Mutex. // It would be really nice to teach stl to do this. class ts_list_head { public: // No point inheriting, I'd have to implement // alot of code. We'll hold off on that exercise. // Using the CommonC++ Mutex class. Mutex linkmutex; // And the STL template. ts_list list_o_items; // Not nessasary, but nice to be explicit. ts_list_head() : linkmutex(), list_o_items() { } // This thing knows how to remove and insert items. void RemoveListItem( ts_list_item * li ); void InsertListItem( ts_list_item * li ); // And it knows how to notify that it became empty // or an element was deleted and it was the last one. virtual void ListDepleted() { } virtual ~ts_list_head() { } }; // This item knows how to remove itself from the // list it belongs to. class ts_list_item { public: ts_list::iterator linkpoint; ts_list_head * listhead; virtual ~ts_list_item() { listhead->RemoveListItem( this ); } ts_list_item( ts_list_head * head ) { listhead = head; head->InsertListItem( this ); } }; void ts_list_head::RemoveListItem( ts_list_item * li ) { bool is_empty; linkmutex.enterMutex(); list_o_items.erase( li->linkpoint ); is_empty = list_o_items.empty(); linkmutex.leaveMutex(); // There is a slim possibility that at this time // we recieve a connection. if ( is_empty ) { ListDepleted(); } } void ts_list_head::InsertListItem( ts_list_item * li ) { linkmutex.enterMutex(); list_o_items.push_front( li ); li->linkpoint = list_o_items.begin(); linkmutex.leaveMutex(); } // ChatterSession operates on the individual connections // from clients as are managed by the SocketService // contained in CCExec. ChatterThread simply waits in // a loop to create these, listening forever. // // Even though the SocketService contains a list of // clients it serves, it may actually serve more than // one type of session so we create our own list by // inheriting the ts_list_item. // class ChatterSession : public virtual SocketPort, public virtual ts_list_item { public: enum { size_o_buf = 2048 }; // Nothing special to do here, it's all handled // by SocketPort and ts_list_item virtual ~ChatterSession() { cerr << "ChatterSession deleted !\n"; } // When you create a ChatterSession it waits to accept a // connection. This is done by it's own ChatterSession( TCPSocket & server, SocketService * svc, ts_list_head * head ) : SocketPort( NULL, server ), ts_list_item( head ) { cerr << "ChatterSession Created\n"; tpport_t port; InetHostAddress ia = getPeer( & port ); cerr << "connecting from " << ia.getHostname() << ":" << port << endl; // Set up non-blocking reads setCompletion( false ); // Set yerself to time out in 10 seconds setTimer( 100000 ); attach(svc); } // // This is called by the SocketService thread when it the // object has expired. // virtual void expired() { // Get outa here - this guy is a LOOSER - type or terminate cerr << "ChatterSession Expired\n"; delete this; } // // This is called by the SocketService thread when it detects // that there is somthing to read on this connection. // virtual void pending() { // Implement the echo cerr << "Pending called\n"; // reset the timer setTimer( 100000 ); try { int len; unsigned int total = 0; char buf[ size_o_buf ]; while ( (len = receive(buf, sizeof(buf) )) > 0 ) { total += len; cerr << "Read '"; cerr.write( buf, len ); cerr << "'\n"; // Send it to all the sessions. // We probably don't really want to lock the // entire list for the entire time. // The best way to do this would be to place the // message somewhere and use the service function. // But what are examples for ? bool sent = false; listhead->linkmutex.enterMutex(); for ( ts_list::iterator iter = listhead->list_o_items.begin(); iter != listhead->list_o_items.end(); iter ++ ) { ChatterSession * sess = dynamic_cast< ChatterSession * >( * iter ); if ( sess != this ) { sess->send( buf, len ); sent = true; } } listhead->linkmutex.leaveMutex(); if ( ! sent ) { send( ( void * ) "No one else listening\n", sizeof( "No one else listening\n" ) - 1 ); send( buf, len ); } } if (total == 0) { cerr << "Broken connection!\n" << endl; delete this; } } catch ( ... ) { // somthing wrong happened here ! cerr << "Socket port write sent an exception !\n"; } } virtual void disconnect() { // Called by the SocketService thread when the client // hangs up. cerr << "ChatterSession disconnected!\n"; delete this; } }; class ChatterThread; // // This is the main application object containing all the // state for the application. It uses a SocketService object // (and thread) to do all the work, however, that object could // theoretically be use by more than one main application. // // It creates a ChatterThread to sit and wait for connections // from clients. class CCExec : public virtual ts_list_head { public: SocketService * service; ChatterThread * my_Chatter; Semaphore mainsem[1]; CCExec():my_Chatter(NULL) { service = new SocketService( 0 ); } virtual void ListDepleted(); // These methods defined later. virtual ~CCExec(); int RunApp( char * hn = "localhost" ); }; // // ChatterThread simply creates ChatterSession all the time until // it has an error. I suspect you could create as many of these // as the OS could take. // class ChatterThread : public virtual TCPSocket, public virtual Thread { public: CCExec * exec; void run () { while ( 1 ) { try { // new does all the work to accept a new connection // attach itself to the SocketService AND include // itself in the CCExec list of sessions. new ChatterSession( * ( TCPSocket * ) this, exec->service, exec ); } catch ( ... ) { // Bummer - there was an error. cerr << "ChatterSession create failed\n"; exit(); } } } ChatterThread( InetHostAddress & machine, int port, CCExec * inexec ) : TCPSocket( machine, port ), Thread(), exec( inexec ) { start(); } }; // // Bug here, this should go ahead and shut down all sessions // for application. An exercise left to the reader. CCExec::~CCExec() { // MUST delete my_Chatter first or it may end up using // a deleted service. if ( my_Chatter ) delete my_Chatter; // Delete whatever is left. delete service; } // // Run App would normally read some config file or take some // parameters about which port to connect to and then // do that ! int CCExec::RunApp( char * hn ) { // which port ? InetHostAddress machine( hn ); if ( machine.isInetAddress() == false ) { cerr << "machine is not address" << endl; } cerr << "machine is " << machine.getHostname() << endl; // Start accepting connections - this will bind to the // port as well. try { my_Chatter = new ChatterThread( machine, 3999, this ); } catch ( ... ) { cerr << "Failed to bind\n"; return false; } return true; } // When there is no one else connected - terminate ! void CCExec::ListDepleted() { mainsem->post(); } int main( int argc, char ** argv ) { CCExec * server; server = new CCExec(); // take the first command line option as a hostname // to listen to. if ( argc > 1 ) { server->RunApp( argv[ 1 ] ); } else { server->RunApp(); } server->mainsem->wait(); delete server; return 0; }