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

Tutorial 3: TCP Client/Server Demonstration

Overview

Tutorial 3 demonstrates the creation of a simple multi-threaded client/server application using MynahSA's TCP transport mechanism. The process of setting up a multi-threaded server is straight forward, and builds on the basics established in Tutorial 1: Simple Object Persistence.

In this example, the client will transmit a "Ping" message to a server and the server will respond back to the client with a ping response. The behavior is analogous to the unix ping command and measurements of the round trip time are reported. The protocol diagram is:

ping_proto.png

Ping Protocol

Network Object Implementation

The server in this application has no state, which simplifies the implementation.

To start with, the objects transmitted across the network are defined. The RequestPing object contains a time stamp, and its definition (from examples/tutorial3/requestping.hpp) is as follows:

class RequestPing : public MynahSA::IoBase { 
public:
  void serialize(MynahSA::Archive& ar) { 
    ar & _sec;   // serialize seconds
    ar & _usec;  // serialize useconds
  }
  
  RequestPing();

  RequestPing(const struct timeval& tv);

  virtual ~RequestPing();

  virtual std::string ioName() const;
  
  inline unsigned int getSeconds() const { return _sec; }
  
  inline unsigned int getUSeconds() const { return _usec; }
  
private:
  unsigned int _sec;
  unsigned int _usec;
};

The ResponePing object that the server will transmit back to the client is similar, however, a boolean status flag named _ok is added to indicate the server's status. The definition for ResposnePing is placed in examples/tutorial3/responseping.hpp.

class ResponsePing : public MynahSA::IoBase { 
public:

  void serialize(MynahSA::Archive& ar) { 
    ar & _ok;
    ar & _sec;
    ar & _usec;
  }

  ResponsePing(bool ok=false, unsigned int sec=0, unsigned int usec=0);
  
  virtual ~ResponsePing();

  virtual std::string ioName() const;

  bool isOk() const { return _ok; }

  inline unsigned int getSeconds() const { return _sec; }
  
  inline unsigned int getUSeconds() const { return _usec; }

private:
  bool _ok;
  
  int _sec;
  
  int _usec;
};
Both of these classes are simple container classes and serve only as a mechanism for MynahSA's archiving system.

Server Implementation

The next step is to define the "Server" behavior of the system. The server is nothing more then a mechanism that converts one descendant class of MynahSA::IoBase into another - in this case, reading a RequestPing object and returning a ResponsePing. The server behavior is defined by creating a class that derives from MynahSA::RequestResponseHandler. The following code segment is taken from MynahSA::RequestResponseHandler, and shows the operator() method that must be overridden to handle the RequestPing object.
class RequestResponseHandler { 
  public:
    ...
    virtual SHARED_PTR<IoBase> 
      operator()(const SHARED_PTR<IoBase>&) = 0;  
};

MynahSA's network server classes invoke RequestResponseHandler::operator() every time an object is received. To handle ping objects, an object named PingRequestResponseHandler is derived from MynahSA::RequestResponseHandler, and its operator() is:

SHARED_PTR<IoBase> 
PingRequestResponseHandler::operator()(const SHARED_PTR<IoBase>& req) { 
  SHARED_PTR<IoBase> resp;

  if (req->ioName() == RequestPing().ioName()) { 
    SHARED_PTR<RequestPing> prq = static_pointer_cast<RequestPing>(req);
    // create the response and copy the seconds and microseconds parameters 
    //  from the request object
    resp = SHARED_PTR<IoBase>(new ResponsePing(true,
                                               prq->getSeconds(),
                                               prq->getUSeconds()));
  } else {
    // this is a logic error; unknown class inserted! 
    cerr << "PingRequestResponseHandler::operator()"
            " : Unknown class received." << endl;
  }
    
  return resp;
}
There are a few things of note here:

Constructor Objects

Every time a new connection is created with MynahSA's TCP server a new instance of TCPArchiveStream is instantiated to provide archiving and serialization of the objects transmitted over the TCP connection. As explained in Tutorial 1: Simple Object Persistence objects transmitted by shared pointer must provide a constructor object and the constructor object must be registered with all input archivers. MynahSA's network transports operate by transmitting objects by shared pointer, therefore, constructor functions must be provided for all objects that will be transmitted. Constructor objects are created by deriving from base class MynahSA::StreamConstructor:

class PingStreamConstructor : public MynahSA::StreamConstructor { 
public:
  PingStreamConstructor();

  virtual ~PingStreamConstructor();
  
  virtual void operator()(MynahSA::ArchiveStreamBase&;) const;
};

The implementation of operator() from examples/tutorial3/pingstreamconstructor.cpp, shown below, demonstrates the registration of RequestPing and ResponsePing:

using namespace MynahSA;

// build the template constructor classes for RequestPing and ResponsePing
MYNAHSA_BUILD_CONSTRUCTOR(RequestPing);
MYNAHSA_BUILD_CONSTRUCTOR(ResponsePing);

void PingStreamConstructor::operator()(ArchiveStreamBase& asb) const { 
  // Register RequestPing
  MYNAHSA_REGISTER_CONSTRUCTOR(asb, RequestPing);
  
  // Register ResponsePing
  MYNAHSA_REGISTER_CONSTRUCTOR(asb, ResponsePing);
}
The above implementation of operator() takes a bidirectional stream instance (class SSLArchiveStream or TCPArchiveStream), instantiates constructor objects and registers them with the stream. A call to MYNAHSA_REGISTER_CONSTRUCTOR is required for each class transmitted by shared pointer across the network.

All of the components that are shared between client and server have now been demonstrated. The next step is to pull the components together and build the server and client implementations.

Server Implementation

The server is implemented in examples/tutorial3/tcpserver.cpp. The steps in creating the server are: We show the relevant code bits here from tcpserver.cpp - omitting exception handling and the calculation of the serverPort variable.

    // initialize WinSock if on Win32 and SSL
    saInit();

...

    // step 1: build the ping request response handler
    PingRequestResponseHandler prh;
    
    // step 2: create the stream constructor object.
    PingStreamConstructor myConstructor;
  
    // step 4: instantiate a connection manager on the handler and the 
    //         stream constructor
    TCPConnectionManager cm(prh,
                            myConstructor);
                                              
    // step 5: build an ssl server on top of the connection manager instance
    TCPRPCServer server(cm, serverPort); 
    
    // step 6: enter loop where we serve requests forever
    while (1) { 
      try { 
        server.checkClients(5);
      } catch (const TCPServerConnectionError& sce) { 
        cerr << "Connection error detected: " << sce.what() << endl;
      }
    }

Client Implmenetation

The client implementation is simple, and the steps are similar to the server. The steps are: For brevity, the computation of the server's IP address and port are omitted. Refer to examples/tutorial3/tcpclient.cpp for details.
The creation of a PingStreamConstructor is identical to the server. The setup code looks like this:

  PingStreamConstructor myConstructor;
  TCPRPCClient myClient(myConstructor, ina, port);
The final step is to loop forever transmitting ping objects and receiving and handling responses. The code for calculating the round trip time has been omitted:
while(1) {
  ...
  SHARED_PTR<RequestPing> rpreq(new RequestPing(t));
  // perform the remote procedure call
  SHARED_PTR<IoBase> resp(myClient.rpc(rpreq));
  if (resp->ioName() == ResponsePing().ioName()) {
    SHARED_PTR<ResponsePing> rsp = static_pointer_cast<ResponsePing>(resp);
    // Print round trip time
    ...
  }
}

Summary

This tutorial has demonstrated the creation of a simple client/server using MynahSA's TCP transport mechanism. The process of using TCP for creating a client/server system is reasonably simple, however, there are a few things to remember: