Introduction Manual Class Reference Header Reference

The Client

Overview

This chapter shows how to implement a client class and how to use it. It will be able to connect to a server.

Server and client are both implemented by deriving from ZCom_Control, so the client code will look very familiar.

Step 1: Derive from ZCom_Control

Let's do this without further explanation:

class Client : public ZCom_Control {
};

Step 2: Copy The Callback Block

Again, the easiest way to get a compilable class is to copy the callback block from the bottom of zoidcom_control.h into the derived class:

class Client : public ZCom_Control {
  void ZCom_cbConnectResult( ZCom_ConnID _id, eZCom_ConnectResult _result, ZCom_BitStream &_reply ) {}
  bool ZCom_cbConnectionRequest( ZCom_ConnID  _id, ZCom_BitStream &_request, ZCom_BitStream &_reply ){return false;}
  void ZCom_cbConnectionSpawned( ZCom_ConnID _id ) {}
  void ZCom_cbConnectionClosed( ZCom_ConnID _id,  eZCom_CloseReason _reason, ZCom_BitStream &_reasondata ) {}
  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 ) {}
  void ZCom_cbDataReceived( ZCom_ConnID _id, ZCom_BitStream &_data ) {}
  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 )  {}
};

This is exactly the same as we did for the server in step 2. Now the relevant callbacks get implemented.

Step 3: Implement The Relevant Callbacks

This is what the client should be able to do at the end of this chapter:

That leaves us with this set of callbacks:

Connecting to another ZCom_Control is asynchronous. That means, when you call ZCom_Connect() the connection process gets started but the method returns before the connection is established. The connection is established when ZCom_cbConnectResult() gets called with a positive result code. The net effect is that you can establish a connection to a remote host while the application can do other things in the meantime, like updating the GUI. Otherwise everything would freeze until the connection has been established.

3.1 Getting Notified When The Connect Is Done

This notification is acquired through ZCom_cbConnectResult(). As soon as Zoidcom has established the connection or has given up because the remote host didn't answer, this callback gets called. We implement it here so the application can react accordingly.

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.\n");
    else
      printf("Connection failed\n");
  }

  // all other callback methods omitted here
};

For a list of possible result codes see eZCom_ConnectResult. The _reply parameter contains anything the server has put there while executing ZCom_cbConnectionRequest().

3.2 Handling Closing Connections

Exactly like the server, the client gets notified when the connection goes down through ZCom_cbConnectionClosed():

class Client : public ZCom_Control {
  virtual void ZCom_cbConnectionClosed( ZCom_ConnID _id, eZCom_CloseReason _reason, ZCom_BitStream &_reasondata )
  {
    printf("Connection to server closed");
  }
  // all other callback methods omitted here
};

Step 4: Instantiate The Client

// <- insert complete client class here

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();

  // more soon
  
  return 0;
};

Step 5: Initialize Sockets

Unlike the server, the client doesn't need a well-known UDP port. The client is the one initiating the connection, so it is totally irrelevant which port it uses. For that reason, we leave it up to the OS to assign a free port to us.

  // 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);
};

Step 6: Initiate A Connection

One special thing the client has to do is initiating the connection to the server. This is done by calling ZCom_Connect() with the desired target address. In this example, the server is expected to run on localhost (i.e. 127.0.0.1) on port 10000.

Insert this after the socket initialization code:

ZCom_Address server_addr;
server_addr.setAddress( eZCom_AddressUDP, 0, "localhost:10000");
ZCom_ConnID connection_id = client->ZCom_Connect(server_addr, NULL);

// unable to connect
// this happens if the connection process can't even be started
// for some reason
if (connection_id == ZCom_Invalid_ID)
  exit(255);

I suggest you store the connection ID somewhere for later use. You need it everytime you want to send data over that connection and for other things, too.

Note that it is possible to connect to as many other ZCom_Controls as you like in parallel. Each call to ZCom_Connect() returns a new connection ID, which can then be used to identify the connection in other callbacks.

After ZCom_Connect() has been called, it is necessary to wait for ZCom_cbConnectResult() to get called by Zoidcom.

Step 7: Run The Client

Again, just like it was done for the server:

  while ( true )
  {
    // tell the client to process incoming data
    client->ZCom_processInput();
    // tell the client to process outgoing data
    client->ZCom_processOutput();
    // give up remaining cpu time
    zcom->Sleep(1);
  }

Done: The Client Complete

This is a very simple client. It will keep running forever, unless the connection times out or the server disconnects on purpose. When the connection closes, it sets the boolean exit_now to true so that the mainloop will stop running.

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

bool exit_now = false;

class Client : public ZCom_Control {
  void ZCom_cbConnectResult( ZCom_ConnID _id, eZCom_ConnectResult _result, 
                                     ZCom_BitStream &_reply )  {
    if (_result == eZCom_ConnAccepted)
      printf("Connection established. \n");
    else
      printf("Connection failed\n");
  }

  void ZCom_cbConnectionClosed( ZCom_ConnID _id, eZCom_CloseReason _reason, ZCom_BitStream &_reasondata )  {
    printf("Connection to server closed. Exiting...\n");
    exit_now = true;
  }

  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_cbDataReceived( ZCom_ConnID _id, ZCom_BitStream &_data) {}
  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)
  {
    // prepare the destination adress
    ZCom_Address server_addr;
    server_addr.setAddress( eZCom_AddressUDP, 0, "localhost:10000");
    // and connect
    ZCom_ConnID connection_id = client->ZCom_Connect(server_addr, NULL);
  
    // unable to connect
    // this happens if the connection process can't even be started
    // for some reason
    if (connection_id == ZCom_Invalid_ID)
      exit(255);
  }

  while ( !exit_now )
  {
    // tell the client to process incoming data
    client->ZCom_processInput();
    // tell the client to process outgoing data
    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