Client-Server Communication

When a client connects to a server, communication is first established over TCP.

In the meantime, if a successful UDP holepunch to the server occurs in the background, UDP communication is possible, but until then, both reliable, unreliable messaging is replaced by TCP. However, after a successful UDP holepunch, reliable messaging is replaced by UDP.

Clients each have a TCP port and a UDP port, and the server has one TCP listening port and one or more UDP ports. Clients maintain a TCP connection to the server and select one of the server-side UDP ports, meaning that the server shares multiple UDP ports evenly among all connected clients.

For example, if 40,000 clients communicate with a server that has 20,000 open UDP ports, there will be two clients communicating for each UDP port on the server side.

Registering and Using Proxy & Stub Communication Objects

To add communication to the server and client, we will create a Common project that will be used by both the server and client for convenience, and prepare a PIDL file. In the prepared PIDL file, define the protocol for sending communication from the server to the client.

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

Compiling the above PIDL file will create a Proxy and Stub object.

How to Use Proxy Objects

First, include the Header wherever you want to use it.

// Server: Server ▶ As a Client
// To make the call,
// the server uses the Proxy object to make the call.
// Declare in header file
// Since we assume we are creating a Common project, 
// we will include the generated files 
// from the folder in the Common project.
#include "../Common/S2C_proxy.h"
  
// Declare in a cpp file
#include "../Common/S2C_proxy.cpp"

Create a proxy and register it with the server object.

// Creates an object.
S2C::Proxy g_S2CProxy;
  
void main()
{
    // The Server object you created 
    // in the Server description.
    CNetServer* srv = 
         ProudNet::CreateServer();
    Svr->AttachProxy(&g_S2CProxy);
  
    // Omitted below
}

We registered it by passing a pointer to the created Proxy object using a function called AttachProxy. AttachProxy can register multiple types of PIDLs by managing them internally as an array. Once registered, you can use the functions of the Proxy object to communicate.

// The HostID and RmiContext
// are added automatically.
// Insert the HostID value of the Client 
// you want to send as hostID.
g_S2CProxy.Chat(
         hostID, 
         RmiContext::ReliableSend, 
         “Send Message”);

Like proxy, stub includes a header wherever you want to use it.

Client:
 Server ▶ Since it is a client, the client
 includes a stub object to receive calls.
// Declare in header file 
// Since we assumed
// we were creating a Common project, 
// we would include the file created in the Common folder.
#include "../Common/S2C_stub.h"
  
// Declare in a CPP file
#include "../Common/S2C_stub.cpp"

In the case of the Stub object, you must create and use an inherited object because it is a defining function of the protocol to be received. If you register it using the AttachStub function, it will be called when that call comes in. Inside the generated Stub object, a Define is created, which allows you to avoid having to modify your cpp and h files separately when you change protocols.

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

Among the define statements specified in the Stub Class, declare DEFRMI_NameSpace_function name in the header file of the inherited object and DECRMI_NameSpace_function name (Class_Name) in the cpp.

class CS2CStub : public S2C::Stub
{
public:
  
// The protocol is handled
// as a define statement in the stub 
// so that the user does not have to modify the class if it changes.
// If it is 'DEFRMI_NameSpace_FunctionName',
// declare it in the header.
    DECRMI_S2C_Chat;
};
CS2CStub g_S2CStub;
  
// ‘DEFRMI_Protocol Taxonomy name_protocol name(
//               Inherited class name)’
// then declare it in the CPP.
DEFRMI_S2C_Chat(CS2CStub)
{
    printf( 
         "[Client] HostID:%d, text: %s”, 
         remote, 
         txt);
  
         // It must return true
    return true;
}

Returning 'True' means that it has been processed. If it returns 'False', the OnNoRmiProcessed Event is called because the user has not processed the protocol. Of the function arguments called with DEFRMI_S2C_Chat, remote is the value of the HostID of the other party that called the RMI. By invoking the Proxy with this ID value, you can send communication to the desired party.

Now we will register the Stub object we created with the Client object.

CNetClient *client 
         = ProudNet:CreateClient();
client->AttachStub(&g_S2CStub);
// Omitted below

The AttachStub function is also managed internally as an array, and is registered by passing a pointer to it.

Be sure to check your traffic after development. Traffic requires checking the amount of communication on each client, server (and Super Peer if you use Super Peer), etc. and removing unnecessary traffic. To check your traffic, you can use the methods below.

  • Use a tool like NetLimiter.

  • There is a way to run exactly as many processes as you want to use on one computer, and then check the number of bytes sent and received per interval in the Task Manager.

  • You can use ProudNet's internal function CNetServer::GetStats(CNetServerStats &outVal); to get real-time traffic sent and received per second, the number sent or received, and more. If the total traffic from a single client is roughly 20-30 KB or more, you may experience issues with your international service.

Tools like NetLimiter are recommended to be deleted after use. Kernel Hooking puts 20x the original speed burden on the core holding the communication daubers.

Event

- Common to client and server

Using errorInfo -> ToString(); in the parameter errorInfo, you can easily get information about the problem.

- Server

This can be used for performance testing and more.

Last updated