The message management layer and it's founding class such as CCallbackClient and CCallbackServer will be used extensively by the higher layers, chiefly layer 5 which handles and manages the NeL network services. However you may use these classes to facilitate TCP connectivity between your services and a client or tool. In many of the available examples the "player client" uses a CCallbackClient to connect to the frontend service's CCallbackServer, which then receives and filters player messages and submits them to the rest of the game services as appropriate.
In addition to this use of the message management functionality it is, as said earlier, the basis of functionality for the higher layers so it is very important to understand how to create and receive messages and receive them through callbacks and callback arrays.
The work done to facilitate serialization and streaming in the NLMISC module has not gone to waste, it is also hard at work here in the NLNET module and is the core concept behind the CMessage class. In its simplest form the CMessage class has a name and is a stream of variables loaded in a specific order. A quick example in code may clarify:
// Define some data to be sent.
std::string playerStr = "playername: bob";
uint32 playerHitPoints = 32;
// Create a message.
CMessage messageToSend("PLRINFO");
messageToSend.serial(playerStr);
messageToSend.serial(playerHitPoints);
You can see it is as simple as the serialization system is explained in earlier chapters: you call the object's serial method with an argument of the variable you would like to serialize. Since this class is based on the CMemStream class from NLMISC you may use any of the documented serialization methods, including those to serialize containers. Just like in ordinary serialization, order is critically important. When reading a message you must read the variables in the order that they were added:
// Define the data to be received.
std::string playerStr;
uint32 playerHitPoints;
// Read a message.
messageRecvd.serial(playerStr);
messageRecvd.serial(playerHitPoints);
Messages also have a number of other useful methods for figuring out more information about them. For example depending on upon whether a message is being sent or received reflects what the serial methods do: if a message is being sent the variables are added to the stream and if the message is being received the data is read from the message stream and assigned in to the variable. You can verify this through the isReading() method of the message.
Messages allow you to change their stream formats - the way in which the data is encoded when the message is streamed to the destination. The default option is UseDefault, but you can specifically choose Binary or String. The default string mode is configurable through CMessage::setDefaultStringMode(bool). The default upon compilation is false - meaning that messages will prefer to use Binary mode unless specifically asked to use a different format using the setType() method. It is important to note that when CMessage is building its header it will change the mode to Binary and then change the mode back to the stream format you have chosen - meaning that the header should always be formatted in Binary.
If you set the type (message type, not stream format) via setType and the message is in reading mode it will only change the message name and stream format flags but will not rebuild the header. When building a new message a setType call is implicit. Typically the default stream format is sufficient and the streaming system will elect to change stream formats dynamically as befits the data streaming.
NeL also has four message types: OneWay, Request, Response and Except. The default message type is OneWay. The concept of mesasge types is specific to layer 6 of the NeL network architecture.
Callbacks live at the core of the message management layer. Callbacks are comprised of three parts: the callback function, the callback array and the server or client utilizing the callbacks.
Callback functions are the easiest concept to explain. There are two function templates used for callbacks: unified network callbacks and unified message callbacks. Unified network callbacks are callbacks used to receive notice about service up/down events. Unified message callbacks are what you would call when a certain message type (signified by message name, not literal message type) are received. These functions receive the message that was sent, read it in and produce some sort of logic.
There are a number of callback types available in NeL's networking framework:
Callback Name | Use | Description |
---|---|---|
TNetCallback | Both | This callback definition is used for connection and disconnection calls. You can define a function using this format and register it at a low level (to CBufNetBase) or all the way up to the upper layers of the NeL network framework (e.g. CCallbackServer.) |
TMsgCallback | Both | This is commonly used for messages received by a CCallbackServer or CCallbackClient to handle messages which are received and have an unknown/undefinde callback handler. It's used in non-unified callback arrays (see also TCallbackItem below) and for pre-dispatch message processing. |
TRemoteClientCallback | Internal (AS) | This is used by the admin layer of NeLNS. |
TNewClientCallback | External (FS) | This is a callback for the CLoginServer class used by frontends. It allows the WS to signal the frontend that a new player has identified and is now connected. |
TNewCookieCallback | External (FS) | This is a callbackf or the CLoginServer class used by frontends. It allows frontend developers to be aware of when a new cookie has become acceptable. |
TDisconnectClientCallback | External (FS) | This is a callback for the CLoginServer class used by frontends. It allows the WS to inform a frontend that it should disconnect a player (double login.) |
TBroadcastCallback | External | This is for unified servers - it allows service developers to be notified when a new service is registered or unregistered with the naming service. |
TNetManagerCallback | Internal (L4) | This is part of the CNetManager (aka Layer 4) portion of the NeL networking framework. It has been deprecated and should not be actively used. |
TUnifiedNetCallback | External | This is for unified services. It allows service developers to be notified when services come online or go offline in a manner that is more flexible than using the TBroadcastCallback directly with the CNamingClient. |
TUnifiedMsgCallback | External | This defines the functions that are mapped to message names through the TUnifiedCallbackItem callback array. This is the function template you write your unified message handling through. |
Below are some of the function template definitions:
// A base message callback.
typedef void (*TMsgCallback) (CMessage &msgin, TSockId from, CCallbackNetBase &netbase);
// A base network callback.
typedef void (*TNetCallback) ( TSockId from, void *arg );
// A unified message callback
typedef void (*TUnifiedMsgCallback) (CMessage &msgin, const std::string &serviceName, TServiceId sid);
// A unified network callback
typedef void (*TUnifiedNetCallback) (const std::string &serviceName, TServiceId sid, void *arg);
A message callback is the function called whenever a message name is mapped to the function through a callback array. A service callback is called whenever the function is provided to the unified network system as a method to call when services by a specific short name come up and down. This will be explained in more detail in the section about NeL services and the unified network system. Below is a brief example of the two callback templates in implementation:
// A couple base message callback functions.
void cbTestRecv(CMessage &msgin, TSockId from, CCallbackNetBase &netbase)
{
CMessage msgout("TS_ACK");
Client->send(msgout);
nlinfo( "Received TEST from server, sending TS_ACK back");
}
void cbTestAck(CMessage &msgin, TSockId from, CCallbackNetBase &netbase)
{
nlinfo( "Received TS_ACK from a client.");
}
// A unified message callback function
void cbPing(CMessage &msgin, const std::string &serviceName, TServiceId sid)
{
CMessage msgout("PONG");
CUnifiedNetwork::getInstance()->send("PS", msgout);
nlinfo( "Received PING from %s, send PONG to Ping Service", serviceName.c_str());
}
// A unified service callback function
void cbUpTS(const std::string &serviceName, TServiceId sid, void *arg)
{
nlinfo("Test Service connecting");
sendPing();
}
The callback arrays are simply a way of mapping a message name to a callback function. There are two primary callback arrays used when writing NeL network clients or services: TUnifiedCallbackItem and TCallbackItem. The latter is used with CCallbackServer and CCallbackClient and the former is used by the unified network system.
Callback arrays are only used for unified message function mapping and never for unified network functions - the latter are always assigned directly to a short name through the unified network system. Here is an example callback array for the above examples:
/*
* Callback array for messages received from a client
*/
TUnifiedCallbackItem CallbackArray[] =
{
{ "PING", cbPing },
};
You can see we took the message name we expected to receive from the other service and mapped it to our function that implemented the function specification for unified message callback functions. Both callback array types use this same message-to-function mapping.
To bring this all together and demonstrate the concepts before we move on to the layer 5 unified network services we'll demonstrate a simple client/server using the callback client and server.