Main Page   Class Hierarchy   Alphabetical List   Compound List   File List   Compound Members   Examples  

chat.cpp

This example is a chat room which can be considered to be one of the simplest tasks solved by multiclient TCP daemons which can't fork a child for every client session because of inter-client dataflows.

/*
  This program is a very simple TCP 'chat room' server. 
  It listens for TCP connections at the port 6666. You 
  can connect the server using, e.g., something like

            telnet 0 6666
  
  then enter your name and enjoy the chat. Unlimited 
  number of clients is allowed (in fact, the number is 
  limited by open descriptors limits).

  If you remain silent for 60 seconds, you get disconnected  
  (that is, "timed out").
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "sue_sel.hpp"
#include "sue_tcps.hpp"

const int the_server_port = 6666;
const int the_server_timeout = 60;



class ChatServerSession : public SUETcpServerSession {
    char *name;
    class ChatServer *the_server;
public:
    ChatServerSession(int a_fd, int a_timeout, 
                      SUEEventSelector *a_selector,
                      ChatServer *a_server);

    ~ChatServerSession();

      // this function must be overriden in every application, 
      // it is the place to implement the main functionality of the server
    virtual void HandleNewInput();

      // when the session is closed for whatever reason, we'd like to 
      // notify all the remaining clients that one of them is gone
    virtual void TcpServerSessionShutdownHook(); 
      
      // we want to notify the user (and all the clients as well) that 
      // she is timed out; so we override this function. If we had left
      // it untouched, it would simply shut the session down saying nothing.
    virtual void HandleSessionTimeout();

      // this function is called by the server when it is necessary to send
      // some text to the client. It is just a piece of internals of
      // the particular application (chat room), not the SUE library.
    void Send(const char *message);

};



class ChatServer : public SUETcpServer {
    struct Item {
        ChatServerSession *sess;
        Item *next;
    }; 
    Item *first;

    int timeout;
public:
    ChatServer(int a_port, int a_timeout);
    ~ChatServer();

      // this is the only one we really MUST override in a child of
      // the SUETcpServer class. It must create another ChatServerSession
      // object.
    virtual SUETcpServerSession* SpawnSession(int newsessionfd);

      // all the rest appear to be the chat room implementation
    void SendMessage(const char *name, const char *message);
    void SendEvent(const char *name, const char *event);
    void ExcludeSession(ChatServerSession *sess);
private:
    void Send(const char *msg);
};











ChatServerSession::ChatServerSession(int a_fd, int a_timeout, 
              SUEEventSelector *a_selector,
              ChatServer *a_server)
: SUETcpServerSession(a_fd, a_timeout, a_selector, a_server, 
                    "Please enter your name: ")
{ 
    name = 0; 
    the_server = a_server; 

      // we want to time out the users who don't type anything in
    inputresetstimeout = true;
      // when a user gets a message (as opposit to _sending_ a message),
      // it doesn't affect her idle time counter
    outputresetstimeout = false;
}

ChatServerSession::~ChatServerSession() 
{ 
    if(name) 
        delete[] name; 
}

void ChatServerSession::HandleNewInput() 
{   
    SUEBuffer ln;
    while(inputbuffer.ReadLine(ln)) {
        if(!name) {
            if(ln.Length()<3) { // 3 is for one char and <CR><LF>
                outputbuffer.AddString("Name too short.\n"
                                       "Please enter your name: ");
            } else {
                name = new char[ln.Length()+1];
                strncpy(name, ln.GetBuffer(), ln.Length());
                name[ln.Length()] = 0;
                the_server->SendEvent(name, "entered the chat room");
            }
        } else {
            the_server->SendMessage(name, ln.GetBuffer());
        }
    }
}

void ChatServerSession::HandleSessionTimeout() 
{
    // NOTE the text "timed out" will be seen by all the clients
    // _including_ the one timed out, while the text
    // "left the chat room" will be seen by all the clients
    // _except_ the one timed out. This is because the first
    // one is sent before GracefulShutdown() is called, and the
    // former one is sent from the shutdown hook, that is, 
    // _after_ the session is closed.
    the_server->SendEvent(name, "timed out");
    GracefulShutdown();
}

void ChatServerSession::TcpServerSessionShutdownHook() 
{
    the_server->ExcludeSession(this);
    if(name) 
        the_server->SendEvent(name, "left the chat room");
}

void ChatServerSession::Send(const char *message)
{
    outputbuffer.AddString(message);
}




ChatServer::ChatServer(int a_port, int a_timeout)
    : SUETcpServer("0.0.0.0", a_port)
{
    first = 0;
    timeout = a_timeout;
}

ChatServer::~ChatServer()
{
    while(first) {
        Item* tmp = first;
        first = first->next;
        delete tmp;
    }
}

SUETcpServerSession* ChatServer::SpawnSession(int newsessionfd)
{
    Item *tmp = new Item;
    tmp->next = first;
    tmp->sess = new ChatServerSession(newsessionfd, timeout, selector, this);
    first = tmp;
    return first->sess;
}



void ChatServer::SendMessage(const char *name, const char *message)
{
    char buf[1024];
    snprintf(buf, sizeof(buf), "<%s> %s\n", name, message);
    Send(buf);
}

void ChatServer::SendEvent(const char *name, const char *event)
{
    char buf[1024];
    snprintf(buf, sizeof(buf), "* %s %s\n", name, event);
    Send(buf);
}

void ChatServer::ExcludeSession(ChatServerSession *sess)
{
    Item **tmp = &first;
    while(*tmp && (*tmp)->sess != sess) tmp = &((*tmp)->next);
    if(!*tmp) return;
    Item *to_del = *tmp;
    *tmp = (*tmp)->next;
    delete to_del;
}

void ChatServer::Send(const char *msg)
{
    printf("[chat] %s", msg);
    for(Item *tmp = first; tmp; tmp=tmp->next)
        tmp->sess->Send(msg);
}

int main(int argc, char **argv)
{
    SUEEventSelector selector;
    ChatServer serv(the_server_port, the_server_timeout);
    if(serv.Up(&selector)) { 
        printf("[chat] Listening port %d\n", the_server_port);
    } else {
        printf("Failed to bring the server up, exiting...\n");
        exit(1);
    }
    selector.Go();
    return 0;
}

Generated on Fri Feb 27 13:17:25 2004 for SUE Library by doxygen1.2.18