blob: 8e16a0442ccf681eee2b63527e993c0da0c531c6 [file] [log] [blame]
//
// 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 = (char *)"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;
}