Pegasus Network Library

Introduction

Pegasus is a C++ library for making networked games. Pegasus is based on Medusa and is part of PLIB. Pegasus, like Medusa, runs as a single process, multiplexing I/O with its various client and server connections within a single process/thread. Pegasus is supported on any platform that includes a socket implementation with the select() function. This includes Win32, Linux, and a majority of Unix implementations.

This document assumes a certain degree of knowledge of TCP/IP and socket programming.

To use the NET library, you'll need to '#include ' and link to libplibnet.a

Symbol Conventions.

Pegasus follows the same conventions for symbols and tokens that are used by OpenGL and GLUT. All Pegasus symbols for classes and functions start with net and all #define tokens start with NET. Words within a class or function name are Capitalised and NOT separated with underscores. Words within #define tokens may be separated with underscores to make them readable.

Initialisation.

The first NET call in any program must always be netInit().

Classes

The following class hierarchy makes up the core package - which can be extended to add functionality or to change some underlying mechanisms.

   class netAddress

   class netBuffer
    |__ class netMessage

   class netGuid
    
   class netSocket
    |__ class netChannel
         |__ class netBufferChannel
         |    |__ class netChat
         |    |__ class netMessageChannel
         |
         |__ class netMonitorServer

class netBuffer

A simple buffer class.
class netBuffer
{
public:
  netBuffer ( int max_length ) ;
  ~netBuffer () ;

  int getLength() const ;
  int getMaxLength() const ;
  char* getData() ;

  void remove () ;
  void remove (int pos, int n) ;
  bool append (const char* s, int n) ;
  bool append (int n) ;
} ;

class netMessage

A message buffer used to transfer binary data and handle byte swapping.
class netMessage : public netBuffer
{
public:

  netMessage ( const char* s, int n ) ;
  netMessage ( int type, int to_id, int from_id ) ;

  int getType () const ;
  int getToID () const ;
  int getFromID () const ;

  void geta ( void* a, int n ) const ;  // a=array; array should already be serialized
  void puta ( const void* a, int n ) ;

  int getch () const ;
  void putch ( int c ) ;

  bool getb () const ;
  void putb ( bool b ) ;

  int getw () const ;
  void putw ( int i ) ;

  int geti () const ;
  void puti ( int i ) ;

  void gets ( char* s, int n ) const ;
  void puts ( const char* s ) ;

  void print ( FILE *fd = stderr ) const ;
};

class netAddress

This is the representation of an Internet-style machine address.
class netAddress
{
public:
  netAddress () ;
  netAddress ( const char* host, int port ) ;

  void set ( const char* host, int port ) ;

  const char* getHost () const ;
  int         getPort () const ;

  static const char* getLocalHost () ;

  bool getBroadcast () const ;
} ;

When reading from the network using one of the higher level classes, you can use a netAddress with an empty string as the 'host' to mean "accept data from any host". When writing to the network, the reserved host name '<broadcast>' (the < and > are literally there) will cause the message to be broadcast to all machines on your subnet.

Otherwise, use either the machines' network name - or it's IP address (as an ASCII string).

class netSocket

netSocket is the low-level socket class.
class netSocket
{
public:
  netSocket () ;
  virtual ~netSocket () ;

  int getHandle () const ;
  void setHandle (int handle) ;

  bool  open ( bool stream=true ) ;
  int   bind ( cchar* host, int port ) ;
  int   listen ( int backlog ) ;
  int   accept ( netAddress* addr ) ;
  int   connect ( cchar* host, int port ) ;
  int   send ( const void * buffer, int size, int flags = 0 ) ;
  int   sendto ( const void * buffer, int size, int flags, const netAddress* to ) ;
  int   recv ( void * buffer, int size, int flags = 0 ) ;
  int   recvfrom ( void * buffer, int size, int flags, netAddress* from ) ;
  void  close ( void ) ;
  void  setBlocking ( bool blocking ) ;
} ;

Example:

This example is stripped of error checking for clarity:

Sender:

  netInit () ;

  netSocket *sock = new netSocket () ;
  sock -> open        ( false ) ;
  sock -> setBlocking ( false ) ;
  sock -> connect     ( host, port ) ;

  while ( !done )
    sock -> send ( msg, len, 0 );

  sock -> close () ;

Reciever:

  netInit () ;

  netSocket *sock = new netSocket () ;
  sock -> open        ( false ) ;
  sock -> setBlocking ( false ) ;
  sock -> bind        ( host, port ) ;

  while ( !done )
    if ( (len = sock -> recv(msg, maxlen, 0)) >= 0 )
      ...use the data...

  sock -> close () ;
This code produces a 'Datagram' connection - which is fast but unreliable (using UDP protocol). Passing a 'true' to sock->open() would produce a 'Stream' connection (using TCP).

class netChannel

netChannel adds event-handling to the low-level netSocket class. Otherwise, it can be treated as a normal non-blocking socket object. The direct interface between the poll loop and the channel object are the handleReadEvent and handleWriteEvent methods. These are called whenever a channel object 'fires' that event. The firing of these low-level events can tell us whether certain higher-level events have taken place, depending on the timing and state of the connection.
class netChannel : public netSocket
{
public:

  netChannel () ;
  virtual ~netChannel () ;

  void setHandle (int s, bool is_connected = true);
  bool isConnected () const ;
  bool isClosed () const ;
  void shouldDelete () ;

  // --------------------------------------------------
  // socket methods
  // --------------------------------------------------
  
  bool  open        ( bool stream=true ) ;
  int   listen	    ( int backlog ) ;
  int   connect     ( cchar* host, int port ) ;
  int   send        ( const void * buf, int size, int flags = 0 ) ;
  int   recv        ( void * buf, int size, int flags = 0 ) ;
  void  close       ( void ) ;

  // poll() eligibility predicates
  virtual bool readable (void) ;
  virtual bool writable (void) ;
  
  // --------------------------------------------------
  // event handlers
  // --------------------------------------------------
  
  void handleReadEvent (void);
  void handleWriteEvent (void);
  
  // These are meant to be overridden.
  virtual void handleConnect (void) ;
  virtual void handleRead (void) ;
  virtual void handleWrite (void) ;
  virtual void handleClose (void) ;
  virtual void handleAccept (void) ;
  virtual void handleError (int error) ;

  static bool poll (u32 timeout = 0 ) ;
  static void loop (u32 timeout = 0 ) ;
};

class netBufferChannel

netBufferChannel is a netChannel with I/O buffering. Clients and servers built on top of netBufferChannel automatically support pipelining where you can send multiple commands without waiting for the response to each command before you send the next.
class netBufferChannel : public netChannel
{
public:
  netBufferChannel (int in_buffer_size = 512, int out_buffer_size = 4096) ;
  void closeWhenDone (void) ;

  virtual bool bufferSend (const char* msg, int msg_len) ;
  virtual void handleBufferRead (netBuffer& buffer) ;
};

class netChat

netChat adds support for 'chat' style protocols - where one side sends a 'command', and the other sends a response (examples would be the common internet protocols - smtp, nntp, ftp, etc..).

The handle_buffer_read() method looks at the input stream for the current 'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n' for multi-line

class netChat : public netBufferChannel
{
public:
  netChat () ;

  void setTerminator (const char* t);
  const char* getTerminator (void);

  bool push (const char* s) ;

  virtual void collectIncomingData (const char* s, int n) ;
  virtual void foundTerminator (void) ;
};

class netMessageChannel

A channel for binary messages. Takes care of packing and unpacking them in/out of the buffers.
class netMessageChannel : public netBufferChannel
{
public:
  netMessageChannel () ;

  bool sendMessage ( const netMessage& msg ) ;
  virtual void handleMessage ( const netMessage& msg ) ;
};

class netMonitorServer

The monitor server gives you remote, 'back-door' access to your server while it is running. netMonitor is a telnet command port with password authorization. It can be paired with and used to remotely admin another server. Once connected via any telnet client to the monitor, you can issue commands. The monitor can be hooked up to a python interpreter for added power. Since an ordinary telnet session is not secure, the password could be intercepted, but that level of security is usually not needed for games.
class netMonitorServer : public netChannel
{
public:
  netMonitorServer( cchar* _name, int port ) ;
  ~netMonitorServer() ;

  cchar* getPassword () const ;
  void setPassword ( cchar* string ) ;
  void setPrompt ( cchar* string ) ;
  void setCommandFunc ( void (*func)(cchar*, netMonitorChannel*) ) ;
} ;

Valid HTML 4.0!
Dave McClurg <dpm@efn.org>