18.Server & Client Communication

18.1How to use ProudNet

To use ProudNet, Lib file should be linked to the project. Lib file is included in Lib folder at the ProudNet's installation path. The version that matches the Visual Studio being used and the compile version (debug, release and etc.) should be linked. Please be aware that if the version does not match operations may fail. After linking, ‘ProudNet.h’ should be included in ‘./include’ folder of the installed location of ProudNet in order to use ProudNet module. All sources of ProudNet are bundled with a namespace Proud, and can easily be accessed by using namespace Proud. For easier understanding of following examples, please refer to "Simple Samples" and Help included in ProudNet’s Sample folder.

18.2Server Implementation

First, let’s start by creating the server.

Required Object and Headerr

// Include ProudNet.
#include “include\ProudNet.h”
  
// All objects of ProudNet
// are bundled
// in the namespace Proud.
using namespace Proud;
  
// port definition
int g_ServerPort = 33334;

Δ Preparation

CNetServer* srv = 
       ProudNet::CreateServer();
srv->SetEventSink(
       &g_eventSink);
  
CStartServerParameter p1;
 
// Client’s connection port.
p1.m_tcpPort = 33334;  
  
srv->Start(p1);

Δ Server Start

Note that‘SetEventSink’function is called after the server object is created. The function is to register callbacks for server events. As the input argument, use an inherited object of‘INetServerEvent’. After registering the server will perform callbacks for the events created internally through this object.

Class CServerEventSink 
           : public INetServerEvent 
{
       // When completion of Client connection, 
       // callback will be done. 
       Virtual void OnClientJoin(
           CNetClientInfo *info) 
           OVERRIDE
       {
            // Process after
            // receiving client information
       }
       // When client is disconnected,
       // callback will be done.
       Virtual void OnClientLeave(
           CNetClientInfo *info) 
           OVERRIDE
       {
       // Process after
       // receiving client information.
       }
       // The rest is omitted.
}

Δ The Object to receive events from CNetServer

As shown above, CNetClientInfo object is used as a medium for callback events. CNetClientInfoObject includes connected client information, and the member variable m_HostID of CNetClientInfo is a unique identifier for each host.

Disconnection

Proud::HostID

HostID is the unique identifier for provided by each host. So, if a client connects to two different servers, the host IDs received from two servers is different.

HostID_None = 0
HostID_Server = 1
HostID_Last = 2
  
Host ID is assigned from 3
in the order of conection/creation
(when HostID Reuse Option is NOT enabled)

18.3Client Implementation

This section discusses how to create a client that connects to the server.Required Objects and Headers

// Include ProudNet.
#include “include\ProudNet.h”
  
// All objects of ProudNet
// are bundled
// in namespace.
Using Namespace Proud;
  
// port definition
int g_ServerPort = 33334;

Δ Preparation

A simple source coding enables server connection for communication

CNetClient *client 
       = ProudNet::CreateClient();
  
// Register the object
// for event callbacks.
client ->SetEventSink(&g_eventSink);
// proxy setting for server connect
client->AttachProxy(&g_c2sProxy); 
// stub setting for data transmission
client->AttachStub(&g_c2sStub);   
// proxy setting for client connection
client->AttachProxy(&g_c2cProxy); 
// stub setting for data transmission
client->AttachStub(&g_c2cStub);   
  
CNetConnectionParam cp;
// Server IP address.
// Input the required 
// IP address.
// Entering localhost indicates
// that there is server and client
// in the same device. 
cp.m_serverIP = L"localhost";   
// Server port
cp.m_serverPort = g_ServerPort; 
  
client->Connect(cp);
While(1)
{
// Client should call FrameMove
// for each frame. 
Client->FrameMove();
Sleep(100);
}
   
delete client;

Δ Server Connection Start

After connecting the 4 proxy and stub objects using CNetClient AttachProxy and CNetClient AttachStub as shown in the code example, the communication is established by calling functions Connect and FrameMove. Please note that the FrameMove function is called for each frame, unlike the server. This is due to the different of threading model applied. The below explains the differences in two threading models.

표 18-1Threading Model

Client Object (CNetClient)

Server Object (CNetServer)

Single Thread Modell

Multi Thread Model

Polling Model

▶ CNetClient::FrameMove()

(Event Callback on FrameMove)

Thread Pool Event Callback

(Separate thread for event callbacks)

In general, a game client has a fast recycling loop. Polling is used over multi-threading to reduce unnecessary complexion by avoiding relatively more difficult threaded programming. By using this model, event callbacks simply only take place in the thread which calls FrameMove.

class CClientEventSink
           : public INetClientEvent
{
       // When server connection is completed,
       // Callback is done. 
       virtual void OnJoinServerComplete(
           ErrorInfo *info, 
           const ByteArray & replyFromServer) 
       {
           if(info->m_errorType != 
                     ErrorType_Ok)
           {
            // In this case
            // connection fails.
            // Write a log that indicates
            // why it failed. 
           }
       }
  
       // When disconnection from server,
       // Callback is done. 
       Virtual void OnLeaveServer(
           ErrorInfo *errorInfo) { }
       // The remaining events are omitted. 
}
CClientEventSink g_eventSink;

Δ The Object to receive event from CNetClient

Disconnection

18.4Event

The following events are used in both Client and Server. Detailed information of the problem can be checked by errorInfo -> ToString();.

The following callbacks are used in the server. These can be used for performance related tests.

18.5Communication between Server & Client

ProudNet uses Remote Method Invocation (RMI) for server & client communication.

RMI

What is RMI?

RMI refers to calling of functions in other network or process. When RMI is used, process of defining transmission routines and message structure is automated. Without it, developers need to repeatedly define transmission routines and message structures.

Note that Proxy is where a call is generated and called from, and Stub is the side in which the call is received and invoked.

What is PIDL?

PIDL is a compiler used by ProudNet to write RIM classes. Once protocols are defined, PIDL will create a file that contains auto-generated objects. (Protocol definition steps are described in the next section). For convenience, it is advised to create a common project to contain these objects since these are used by both client and server objects.

PIDL File Creation & Setting

PIDL file creation requires a few simple steps. Using Visual Studio, create a txt file and change the extension to PIDL. Once this step is completed, proceed with the settings specific to the compiler being used. PIDL file needs a Custom Build settings configured before building. (Open Visual Studio Solution Viewer ▶ Right click on PIDL file ▶ Go to Properties ▶ General ▶ Item Type: Custom Build Tool.)Please refer to Help or the Sample for more detail and an example. In Help, go to ProudNet Operation Guide ▶ Remote Method Invocation Operation ▶ Compiling PIDL file.

PIDL Grammar

A PIDL is structured as below.

global (namespace) 
           (The Initial Value of Message’s ID) 
{ 
       Function declaration ([in] function parameter, …)
}

When compiled, new namespace is created with Stub and Proxy classes included. All RMI functions in the namespace will have unique IDs. These IDs will start from the inputted initial value of message's ID and increment by one per function. These IDs can be any number except 0~1300 and 6300~ (after 6300) as these numbers are reserved for internal usage for ProudNet.

Example)
global S2C 1000 
{
    // Define Protocol
    Chat([in] Proud::String txt);
}

Using Proxy & Stub Files

Running the PIDL, six files are created.

PIDLfilename_Common .Cpp & .h

PIDLfilename_proxy .Cpp & .h

PIDLfilename_stub .Cpp & .h

It is convenient to include h file in header and cpp file in cpp file by using #include, except for Common files. This is because these files change frequently due to Customer Build use. These two parameters are automatically added to the created RMI functions.

Proud::HostID – Host’s ID Value

Proud::RmiContext - Options for Transmit or receive data

Proud::RmiContext

RmiContext is an option class for communication and it is usually enough to use declared static internal variables.

ReliableSend : Reliable communication

FastEncryptedReliableSend : Reliable connection with fast (low security level) encryption

SecureReliableSend : Encrypted Reliable communication

UnreliableSend : Unreliable communication

FastEncryptedUnreliableSend : Unreliable connection with fast (low security level) encryption

SecureUnreliableSend : Encrypted Unreliable communication

Option used very frequently is Static and it has been already made. For user’s convenience, direct option setting is possible.

m_reliability : Option to use either a Reliable & Unreliable connection

m_encryptMode : Option to set level of Encryption (Three options are available).

m_compressMode : Option to set Compression level

For more options, refer to Help.

Reliable & Unreliable

The following table describes the differences between a reliable and unreliable connection types.

Reliable

Unreliable

Packet reception order is secured.

Packet reception order is not secured.

No loss of Packet is secured.

Possible loss of Packet.

TCP & UDP

(When Hole-Punching is successful, Reliable UDP is used internally.)

UDP is used if available. Otherwise TCP is used.

The Reliable UDP is a method to use the UDP with guaranteed data delivery and order securing. However, more traffic will occur compared to normal unreliable UDP.

Registering and using Proxy & Stub

This section will demonstrate how Proxy & Stub objects are used with a server and clients. It is assumed that created PIDL file is managed by a Common project as described in. Define and declare protocol for server to client communication in PIDL.

Global S2C 3000
{
   Chat(Proud::StringA txt);
}

Δ Server ▶ Client

Compiling the PIDL will create Proxy and Stub objects. Include the header to the file that will use Proxy object.

// Server: In Server ▶ Client, 
// the server is to include
// Proxy for transmitting. 
// Declare in the header file. 
// Since it is assumed that
// common object is used,
// include file from common project folder. 
#include "../Common/S2C_proxy.h"
  
// Declare in cpp file
#include "../Common/S2C_proxy.cpp"

Δ Proxy

Create Proxy and register it in Server Object.

// Create object.
S2C::Proxy g_S2CProxy;
  
void main()
{
    // The created server object as
    // shown in server description.
    CNetServer* srv = 
        ProudNet::CreateServer();
    Svr->AttachProxy(&g_S2CProxy);
  
    // The rest is omitted. 
}

In the above code, registering is done by passing a pointer of the Proxy object to the AttachProxy method of the server. Server object manages proxy objects in an array internally and is capable of storing different types of PIDLs. After the registration, communication needs to be enabled through the chat function of the Proxy object.

// HostID and RmiContex 
// are automatically added.
// Client HostID that
// needs to be sent to hostID.
g_S2CProxy.Chat(
        hostID, 
        RmiContext::ReliableSend, 
        “Send Message”);

Include the Header in the file like Proxy.

Client:
 In Server ▶ Client, the client 
 is to include stub object for receiving.
// Declare in header file.
// Since it is assumed that 
// common project is used,
// include file from common project folder. 
#include "../Common/S2C_stub.h"
  
// Declare in cpp file.
#include "../Common/S2C_stub.cpp"

Δ Stub

Stub object itself is a definition class and therefore, in order to receive data, a new class needs to be created and inherit Stub class. This new class can be registered by AttachStub function and once registered, callback would happen when a specific call has been received. Stub class has a following definition, and using this definition, there is no need to modify cpp and header files.

#define DECRMI_C2S_Chat bool Chat(
        Proud::HostID remote,
        Proud::RmiContext &rmiContext,
        const Proud::StringA txt)

Inherited class needs to select from Stub object's definitions and declare chosen 'DEFRMI_namespace_function_name' to the header and 'DECRMI_nameSpace_function_name(Class_Name)' in cpp file.

class CS2CStub
          : public S2C::Stub
{
public:
  
// Protocol is defined in the Stub to eliminate need
// for code modification in the inherited class
// in case of protocol change. 
// If it is set to be  ‘DEFRMI_NameSpace_functionname’,
// it is declared in the Header.
    DECRMI_S2C_Chat;
};
CS2CStub g_S2CStub;
  
// Declare and implement 
// (inherited class name)
// if DEFRMI_Protocol...used 
DEFRMI_S2C_Chat(CS2CStub)
{
    printf( 
         "[Client] HostID:%d, text: %s”, 
         remote, 
         txt);
  
        // “True” should be returned.
    return true;
}

The above example returns "True" to indicate this function has been processed. If "False" is returned, it is considered that the protocol is not processed and OnNoRmiProcessed Events gets called back. 'remote' indicates a host ID of a RMI caller of DEFRMI_S2C_Chat function. Using this value in a Proxy, one can send a message to that caller. Finally, register created Stub object to the client.

CNetClient *client 
         = ProudNet:CreateClient();
client->AttachStub(&g_S2CStub);
// The rest is omitted.

AttachStub, likewise AttachProxy, is handled as an array and registered by passing a pointer.

Always check traffic.

During and after development, amount of traffic must be monitored. Check the traffic in each of the clients, super peers if used, and servers for excessive traffic. If possible, reduce as much traffic as possible by removing any unnecessary calls. There are number of ways to monitor traffic. Here are some suggestions. Use tools like‘NetLimiter’. Use Window's taskmanager. After minimizing number of processes that uses networking and observe networking tab for sent & received bytes per interval. Use the internal function provided - namely, CNetServer::GetStats(CNetServerStats&outVal). This function could control the amount of traffic to send & received per second, how much traffic is sent & received, and etc. in real time. In general, if the traffic amount is more than 20~30 KB per second, it is considered potentially problematic in international services.

***WARNING***

Network monitoring tools such as NetLimiter must be deleted after use. These tools usually involve kernel hooking and cause overhead as much as 20 times to the core that manages networking device.