Introduction Manual Class Reference Header Reference

Communication

Overview

In this chapter we will extend our client and server from both previous chapters. The goal is to let the client send a password along with the connection request. The server will accept the connection only when the password is correct. After the connection has been established, the client sends a number. The server is expected to send the string "Hello from Server" the requested amount of times back to the client. The client will receive these strings and print them to the console.

Step 1 (Client): Attaching Data To The Connection Request

Arbitrary data can be attached to a connection request. This way it is possible to perform quick communication between two ZCom_Controls without establishing a full connection. Because no matter if the the server accepts or denies the request, it can always send back a reply. This can be used for several things:

Attaching the data is a simple matter of passing a bitstream object to the connect function.

// assuming the initialized client from the previous chapter here

// prepare bitstream
ZCom_BitStream *password = new ZCom_BitStream();
password->addString("let_me_in");
// prepare address
ZCom_Address server_addr;
server_addr.setAddress( eZCom_AddressUDP, 0, "localhost:10000");
// connect
ZCom_ConnID connection_id = client->ZCom_Connect(server_addr, password);
password = NULL;

Attention:
As written in Memory Management, bitstreams may not be deleted manually when given to a Zoidcom function. That is why the password variable is set to NULL, to explicitly show that the bitstream is of no interest to our code anymore.

Step 2 (Server): Receiving The Attached Data And Sending A Reply

Connection requests will be received by the server through the callback ZCom_cbConnectionRequest. This callback has several parameters which are set by Zoidcom and read by the application code.

The first parameter is _id, which holds the id of the incoming connection. When the connection is accepted by the server, this _id can be used to act on the connection as long as it is alive.

The second parameter is _request. It is a reference to a bitstream, and this bitstream contains whatever the client has attached to the connection request. The goal is to accept the connection only when the correct password has been attached.

The following code is meant to replace the implementation presented in Step 3.1 Handling Connection Requests from The Server.

class Server : public ZCom_Control {
  bool ZCom_cbConnectionRequest(ZCom_ConnID  _id, ZCom_BitStream &_request, ZCom_BitStream &_reply ) {
    printf("A client requested connection - the new id is [%d].\n", _id);
    // get the password
    const char *password = _request.getStringStatic();
    // compare it
    if (password && strlen(password) > 0 && strcmp("let_me_in", password) == 0) {
      printf("Connection Accepted.\n");
      _reply.addString("Password Correct.");
      // accept connection
      return true;
    } else {
      _reply.addString("Password Wrong.");
      printf("Connection Denied.\n");
      // deny connection
      return false;
    }
  }
  // all other callback methods omitted here
};

As you can see, the _reply bitstream in this callback can be filled with data. This data will be received by the client in the ZCom_cbConnectResult() callback.

Step 3 (Client): Receiving The Reply And Sending Data

When the server wrote data to the _reply bitstream in ZCom_cbConnectionRequest(), this data can be read in the client's ZCom_cbConnectResult() callback. The following code is meant to replace the code from 3.1 Getting Notified When The Connect Is Done in The Client.

class Client : public ZCom_Control {
  virtual void ZCom_cbConnectResult( ZCom_ConnID _id, eZCom_ConnectResult _result, 
                                     ZCom_BitStream &_reply )
  {
    if (_result == eZCom_ConnAccepted)
      printf("Connection established. The reply was: %s\n", _reply.getStringStatic());
    else {
      printf("Connection failed. The reply was: %s\n", _reply.getStringStatic());
      return;
    }
    
    // if we get here, the connection was accepted
    
    // create a bitstream for the request
    ZCom_BitStream *therequest = new ZCom_BitStream();
    // add the number 9 as a 4 bit value
    therequest->addInt(9, 4);
    // send the data to the server
    ZCom_sendData(_id, therequest);
    // we are not allowed to delete the stream after it has been sent
    therequest = NULL;
  }

  // all other callback methods omitted here

This adds two things to the previous implementation. First, the content of the _reply bitstream is read and printed to the console. The bitstream has to contain a string, otherwise getStringStatic() will return NULL. Second, and most importantly, we send the request for the "Hello from Server" string. This is done by creating a new bitstream object, filling it with the number 9 (meaning we want the string 9 times) and then sending the bitstream to the server.

The number is sent as a 4 bit value. That means, the maximum sendable number would be 15. The server will expect a 4 bit value, so it is not possible to simply increase the amount of bits only on the client, as the server will try to read exactly 4 bits. If more bits are needed, the change needs to be done on server and client alike.

ZCom_sendData() sends the data to the connection with the id stored in _id. This happens to be the connection for which we just received the connectresult notification, the connection initially requested with the password "let_me_in". The value stored in _id is also equal to the value stored in connection_id previously declared here: Step 1 (Client): Attaching Data To The Connection Request.

Step 4 (Server): Receiving The Data And Sending Strings Back

When data is sent from the client to the server (or vice versa) through ZCom_Control::ZCom_sendData(), the other side will receive the data through the callback ZCom_cbDataReceived(). The callback provides information about the id of the sending connection and the actual data.

The server implementation for this callback will read an integer as a 4 bit value from the incoming data (because that is what the client should be sending) and then it will send the string "Hello from Server" the read amount of times back to the client.

This code replaces the previously empty callback ZCom_cbDataReceived() in the server class (The Server).

class Server : public ZCom_Control {
  void ZCom_cbDataReceived(ZCom_ConnID  _id, ZCom_BitStream &_data) {
    // read 4 bit integer
    int number = _data.getInt(4);
    printf("The client requested our message %d number of times.\n", number);
    
    // create a bitstream for the message
    ZCom_BitStream *message = new ZCom_BitStream();
    message->addString("Hello from Server");
    
    // send it as often as requested
    while (number--)
      ZCom_sendData(_id, message->Duplicate());
      
    // we made a copy for every send, so the original bitstream is unused
    // and can be deleted
    delete message;
  }
  // all other callback methods omitted here
};

Attention:
As seen in Memory Management, bitstream objects that are sent, don't belong to the application anymore. Zoidcom will work with them and delete them eventually, maybe even immediately. That makes it necessary to create copies of a bitstream via ZCom_BitStream::Duplicate() whenever the same data shall be sent more than once *and* it is necessary to create the copy *before* sending the data.

Step 5 (Client): Receiving The Strings

The server has sent it's replies with the same function that the client used to send the initial request: ZCom_sendData(). That means that the client will receive the data through the same callback as the server did: ZCom_cbDataReceived().

The server sends the message 9 times (because we sent a 9 in Step 3 (Client): Receiving The Reply And Sending Data), and so the callback will get called 9 times, too.

class Client : public ZCom_Control {
  void ZCom_cbDataReceived(ZCom_ConnID  _id, ZCom_BitStream &_data) {
    printf("Received a string: %s\n", _data.getStringStatic());
  }
  // all other callback methods omitted here
};

Conclusion

The communication methods shown here represent only Zoidcom's basic networking functionality. You can stay at this level if you don't want to use object replication at all. When you are using object replication these methods still work, but you will soon find out that it is more convenient to send most data through the objects themselves.

The Code Complete

The Server

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

class Server : public ZCom_Control 
{
  // someone tries to connect
  bool ZCom_cbConnectionRequest(ZCom_ConnID  _id, ZCom_BitStream &_request, ZCom_BitStream &_reply ) {
    printf("A client requested connection - the new id is [%d].\n", _id);
    // get the password
    const char *password = _request.getStringStatic();
    // compare it
    if (password && strlen(password) > 0 && strcmp("let_me_in", password) == 0) {
      printf("Connection Accepted.\n");
      _reply.addString("Password Correct.");
      // accept connection
      return true;
    } else {
      _reply.addString("Password Wrong.");
      printf("Connection Denied.\n");
      // deny connection
      return false;
    }
  }

  // someone has connected
  void ZCom_cbConnectionSpawned( ZCom_ConnID _id ) {
    printf("New connection with id [%d]\n", _id);
  }

  // someone has disconnected
  void ZCom_cbConnectionClosed( ZCom_ConnID _id, eZCom_CloseReason _reason, ZCom_BitStream &_reasondata ) {
    printf("Connection with id [%d] closed\n", _id);
  }
  
  // someone has sent data
  void ZCom_cbDataReceived(ZCom_ConnID  _id, ZCom_BitStream &_data) {
    // read 4 bit integer
    int number = _data.getInt(4);
    printf("The client requested our message %d number of times.\n", number);
    
    // create a bitstream for the message
    ZCom_BitStream *message = new ZCom_BitStream();
    message->addString("Hello from Server");
    
    // send it as often as requested
    int i = 1;
    while (number--) {
      printf("Sending #%d\n", i++);
      ZCom_sendData(_id, message->Duplicate());
    }
      
    // we made a copy for every send, so the original bitstream is unused
    // and can be deleted
    delete message;
  }

  // currently irrelevant callbacks have empty bodies
  void ZCom_cbConnectResult( ZCom_ConnID _id, eZCom_ConnectResult _result, ZCom_BitStream &_reply ) {}
  bool ZCom_cbZoidRequest( ZCom_ConnID _id, zU8 _requested_level, ZCom_BitStream &_reason ) {return false;}
  void ZCom_cbZoidResult( ZCom_ConnID _id, eZCom_ZoidResult _result, zU8 _new_level, ZCom_BitStream &_reason ) {}
  void ZCom_cbNodeRequest_Dynamic( ZCom_ConnID _id, ZCom_ClassID _requested_class, ZCom_BitStream *_announcedata,
                                   eZCom_NodeRole _role, ZCom_NodeID _net_id ) {}
  void ZCom_cbNodeRequest_Tag( ZCom_ConnID _id, ZCom_ClassID _requested_class, ZCom_BitStream *_announcedata,
                               eZCom_NodeRole _role, zU32 _tag ) {}
  bool ZCom_cbDiscoverRequest( const ZCom_Address &_addr, ZCom_BitStream &_request, 
                               ZCom_BitStream &_reply ) {return false;}
  void ZCom_cbDiscovered( const ZCom_Address & _addr, ZCom_BitStream &_reply )  {}
};

int main()
{
  // initialize Zoidcom
  ZoidCom* zcom = new ZoidCom("zoidcom.log");
  if (!zcom || !zcom->Init())
    exit(255);

  // make instance of our Server class
  Server *server = new Server();
  server->ZCom_setDebugName("Server");

  // this creates and initializes the network sockets
  // true = use udp socket, 10000 = the UDP port to use, 0 = no internal socket
  bool result = server->ZCom_initSockets(true, 10000, 0);
  // if result is false, Zoidcom had problems while initializing
  if (!result)
    exit(255);

  while ( true )
  {
    // tell the server to process incoming data
    printf("Processing input...\n");
    server->ZCom_processInput();
    // tell the server to process outgoing data
    printf("Processing output...\n");
    server->ZCom_processOutput();
    // let the program sleep for 0 msecs
    zcom->Sleep(1);
  }

  delete server;
  delete zcom;

  return 0;
};

The Client

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

bool exit_now = false;

class Client : public ZCom_Control {
  virtual void ZCom_cbConnectResult( ZCom_ConnID _id, eZCom_ConnectResult _result, 
                                     ZCom_BitStream &_reply )
  {
    if (_result == eZCom_ConnAccepted)
      printf("Connection established. The reply was: %s\n", _reply.getStringStatic());
    else {
      printf("Connection failed. The reply was: %s\n", _reply.getStringStatic());
      return;
    }
    
    // if we get here, the connection was accepted
    
    printf("Requesting 9 strings...\n");
    // create a bitstream for the request
    ZCom_BitStream *therequest = new ZCom_BitStream();
    // add the number 9 as a 4 bit value
    therequest->addInt(9, 4);
    // send the data to the server
    ZCom_sendData(_id, therequest);
    // therequest has been given to Zoidcom and may not be deleted
    therequest = NULL;
  }

  void ZCom_cbConnectionClosed( ZCom_ConnID _id, eZCom_CloseReason _reason, ZCom_BitStream &_reasondata )  {
    printf("Connection to server closed. Exiting...\n");
    exit_now = true;
  }
  
  // data was received
  void ZCom_cbDataReceived(ZCom_ConnID  _id, ZCom_BitStream &_data) {
    printf("Received a string: %s\n", _data.getStringStatic());
  }  

  bool ZCom_cbConnectionRequest( ZCom_ConnID  _id, ZCom_BitStream &_request, ZCom_BitStream &_reply ){return false;}
  void ZCom_cbConnectionSpawned( ZCom_ConnID _id ) {}
  bool ZCom_cbZoidRequest( ZCom_ConnID _id, zU8 _requested_level, ZCom_BitStream &_reason ) {return false;}
  void ZCom_cbZoidResult( ZCom_ConnID _id, eZCom_ZoidResult _result, zU8 _new_level, ZCom_BitStream &_reason ) {}
  void ZCom_cbNodeRequest_Dynamic( ZCom_ConnID _id, ZCom_ClassID _requested_class, ZCom_BitStream *_announcedata,
                                   eZCom_NodeRole _role, ZCom_NodeID _net_id ) {}
  void ZCom_cbNodeRequest_Tag( ZCom_ConnID _id, ZCom_ClassID _requested_class, ZCom_BitStream *_announcedata,
                               eZCom_NodeRole _role, zU32 _tag ) {}
  bool ZCom_cbDiscoverRequest( const ZCom_Address &_addr, 
                               ZCom_BitStream &_request, ZCom_BitStream &_reply ) {return false;}
  void ZCom_cbDiscovered( const ZCom_Address & _addr, ZCom_BitStream &_reply )  {}
};

int main()
{
  // initialize Zoidcom
  ZoidCom* zcom = new ZoidCom("zoidcom.log");
  if (!zcom || !zcom->Init())
    exit(255);

  // make instance of our Client class
  Client *client = new Client();
  client->ZCom_setDebugName("Client");

  // this creates and initializes the network sockets
  // true = use udp socket, 0 = let OS choose UDP port, 0 = no internal socket
  bool result = client->ZCom_initSockets(true, 0, 0);
  // if result is false, Zoidcom had problems while initializing
  if (!result)
    exit(255);

  // put this into a codeblock so that server_addr gets out of scope before 
  // 'delete zcom;' get called (everything needs to be gone before the 
  // ZoidCom object gets deleted)
  {
    printf("Connecting...\n");
    // prepare bitstream
    ZCom_BitStream *password = new ZCom_BitStream();
    password->addString("let_me_in");
    // prepare address
    ZCom_Address server_addr;
    server_addr.setAddress( eZCom_AddressUDP, 0, "localhost:10000");
    // connect
    ZCom_ConnID connection_id = client->ZCom_Connect(server_addr, password);
    password = NULL;
  }

  while ( !exit_now )
  {
    // tell the client to process incoming data
    printf("Processing input...\n");    
    client->ZCom_processInput();
    // tell the client to process outgoing data
    printf("Processing output...\n");    
    client->ZCom_processOutput();
    // give up remaining cpu time
    zcom->Sleep(1);
  }
  
  delete client;
  delete zcom;

  return 0;
};

This file is part of the documentation for Zoidcom. Documentation copyright © 2004-2008 by Jörg Rüppel. Generated on Sat Aug 16 15:26:51 2008 for Zoidcom by doxygen 1.4.6-NO