Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members | Related Pages | Examples

tcpservice.cpp

//
// tcpservice.cpp
//
//  Copyright 2000 - Gianni Mariani <gianni@mariani.ws>
//
//  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 <cc++/socketport.h>

#include <iostream>

// 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 <list>

#ifdef  CCXX_NAMESPACES
using namespace std;
using namespace ost;
#endif

class ts_list_item;
typedef list<ts_list_item *> 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;
}

Generated on Fri Dec 9 22:36:21 2005 for GNU CommonC++ by  doxygen 1.4.4