![]() |
||||||||
|
|
||||||||
Servers (and clients) are implemented by deriving from ZCom_Control. ZCom_Control provides connection related information through virtual methods which are used as callbacks. Callbacks, in a more general sense, are functions defined by the application that get called by some other part of the application when a specific events kicks in. That is why they are also sometimes referred to as 'event handlers'. The C-function defined in Custom Logging is such a callback for instance.
ZCom_Control, upon detection of connection related events such as timeouts, will call the respective callback method on itself. Since the callback methods are defined as 'virtual', deriving classes that define the same methods will override the previous method in ZCom_Control. So it will call the method in the derived class instead. What we have to do in order to get notified about ZCom_Control's events is to override all provided callback methods in ZCom_Control with our own versions.
There are two flavours of callbacks: Those that expect a return value and those that do not. The callbacks without return value are informational only and not necessarily needed to run a server or client. They tell you when a connection goes down or when data arrived and other things. It is possible to make use of this information or totally ignore it. The callbacks expecting a return value are of higher importance to Zoidcom, as they control how Zoidcom will react to the event in question. For example, when someone tries to connect, the ZCom_cbConnectionRequest() callback gets called. When the callback returns true, the connection will be accepted by Zoidcom, when it returns false, the connection will be denied instead.
class Server : public ZCom_Control { };
For people without prior OOP experience, this code tells the compiler that we are declaring a new class, which is a subclass of ZCom_Control. Subclassing means inheriting, so class Server now has the same functions available as the class ZCom_Control.
class Server : 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 ) {} };
These empty implementations will be used as starting point for the server implementation.
In this chapter the server will only be able to accept connection and notice when a connection breaks down. When you take a look at the callbacks in ZCom_Control, these fit to the above items:
The selected callbacks will get their special implementations, all others stay as they are.
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); return true; } // all other callback methods omitted here };
When clients connect they can attach data to the connection request, which is made available through the _request parameter here. It could for example contain a username and a password.
Inside the callback, data can be written into the _reply bitstream. Whatever has been stored in there will get sent back to the client, for example a 'invalid password' message.
class Server : public ZCom_Control { void ZCom_cbConnectionSpawned( ZCom_ConnID _id ) { printf("Connection [%d] has been established.\n", _id); } // all other callback methods omitted here };
class Server : public ZCom_Control { void ZCom_cbConnectionClosed( ZCom_ConnID _id, eZCom_CloseReason _reason, ZCom_BitStream &_reasondata ) { printf("Connection with id [%d] closed", _id); } // all other callback methods omitted here };
When the connected client disconnected through a call to ZCom_Disconnect(), the disconnection reason can be read from the _reasondata bitstream. In all other cases this bitstream will be empty.
// <- insert complete server class here int main() { // initialize Zoidcom - write all output to zoidcom.log ZoidCom* zcom = new ZoidCom("zoidcom.log"); if (!zcom || !zcom->Init()) exit(255); // create instance of our Server class Server *server = new Server(); // more soon return 0; };
We want the real thing, so we initialize UDP sockets only:
if (!server->ZCom_initSockets(true /* use udp */, 10000 /* udp port */, 0 /* no internal socket */)) // if false returned, there was a problem exit(255);
The server opens an UDP socket on port 10000 and waits for data to arrive.
while ( true ) { // tell the server to process incoming data server->ZCom_processInput(); // tell the server to process outgoing data server->ZCom_processOutput(); // give up remaining cpu time zcom->Sleep(1); }
ZCom_processInput() is the method where most callbacks are generated from. If someone connects to the server, the connection packet is waiting in an internal queue until ZCom_processInput() fetches and processes it. The type of packet is detected and ZCom_cbConnectionRequest() called. After the callback returned, ZCom_processInput() will prepare a new packet for the requester and put it into the outqueue. As soon as ZCom_processOutput() is called next, the reply packet will be sent. It should be obvious that it is essential to keep calling processInput/Output as often as possible to achieve low latency.
zcom->Sleep(1) gives up remaining CPU time to the OS. This can be removed if you don't need it.
#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) { return true; } // someone has connected void ZCom_cbConnectionSpawned( ZCom_ConnID _id ) { printf("New connection with id [%d]", _id); } // someone has disconnected void ZCom_cbConnectionClosed( ZCom_ConnID _id, eZCom_CloseReason _reason, ZCom_BitStream &_reasondata ) { printf("Connection with id [%d] closed", _id); } // currently irrelevant callbacks have empty bodies void ZCom_cbConnectResult( ZCom_ConnID _id, eZCom_ConnectResult _result, ZCom_BitStream &_reply ) {} void ZCom_cbDataReceived( ZCom_ConnID _id, ZCom_BitStream &_data ) {} 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 server->ZCom_processInput(); // tell the server to process outgoing data server->ZCom_processOutput(); // let the program sleep for 0 msecs zcom->Sleep(1); } delete server; delete zcom; return 0; };
1.4.6-NO