RakNet: Client Server Basics

RakNet is the best open-source multiplayer networking library I’ve come across. In my experience, it doesn’t make any sense to reinvent the wheel by coding your own networking (except maybe as an exercise).

This article presents a barebones RakNet skeleton to connect multiple clients to a server.

Startup

RakPeerInterface is RakNet’s central class which allows you to dis/connect and send packets. Use RakNetworkFactor::GetRakPeerInterface() rather than directly instantiating a RakPeerInterface.

// create a new peer interface
rakPeerInterface = RakNetworkFactory::GetRakPeerInterface();

For clients, we call Startup() with an empty descriptor. Once our port is opened, we try to connect the server at the given port/ip.

void initClient(const char* ip, unsigned short port)
{
	// use an empty descriptor to let raknet choose local port/ip
	SocketDescriptor socketDescriptor;
	rakPeerInterface->Startup(1, 100, &socketDescriptor, 1);

	rakPeerInterface->Connect(ip, port, 0, 0, 0);
	printf("Connecting...\n");
}

For the server, we open a specific port and tell RakNet to allow a maximum of 32 connections.

void initServer(unsigned short port)
{
	// have the server use the given port
	SocketDescriptor socketDescriptor;
	socketDescriptor.port = port;

	// startup allow max 32 connections
	rakPeerInterface->Startup(32, 100, &socketDescriptor, 1);
	rakPeerInterface->SetMaximumIncomingConnections(32);
}

The second parameter of Startup() is how many milliseconds RakNet sleeps between update cycles. We use 100ms for this example, but 30ms or less would be more appropriate in realtime networking environments.

Packet Processing

Once we initialize the RakPeerInterface, we can starting polling for incoming packets. The first byte of every packet is designated as the type identifier, which can be RakNet-specific or user-defined. In this case, we output the name and sender of some of the core packet types.

// process all available incoming packets
Packet* packet = rakPeerInterface->Receive();
while (packet != NULL)
{
	// the first packet byte is the type identifier
	switch (packet->data[0])
	{
		case ID_CONNECTION_REQUEST_ACCEPTED:
			printf(”ID_CONNECTION_REQUEST_ACCEPTED from %s\n”,
				packet->systemAddress.ToString());
			break;

		case ID_CONNECTION_ATTEMPT_FAILED:
			quit = true;
			printf(”ID_CONNECTION_ATTEMPT_FAILED from %s\n”,
				packet->systemAddress.ToString());
			break;

		case ID_NEW_INCOMING_CONNECTION:
			printf(”ID_NEW_INCOMING_CONNECTION from %s\n”,
				packet->systemAddress.ToString());
			break;

		case ID_NO_FREE_INCOMING_CONNECTIONS:
			quit = true;
			printf(”ID_NO_FREE_INCOMING_CONNECTIONS from %s\n”,
				packet->systemAddress.ToString());
			break;

		case ID_DISCONNECTION_NOTIFICATION:
			printf(”ID_DISCONNECTION_NOTIFICATION from %s\n”,
				packet->systemAddress.ToString());
			break;

		case ID_CONNECTION_LOST:
			printf(”ID_CONNECTION_LOST from %s\n”,
				packet->systemAddress.ToString());
			break;
	}

	// get the next packet for processing
	rakPeerInterface->DeallocatePacket(packet);
	packet = rakPeerInterface->Receive();
}

Here, we process all received packets every update cycle. However, if we are constantly receiving packets in a high-traffic system, we will never break from the packet processing loop - causing a stall. A more robust solution would impose a maximum number of packets processed per update cycle.

Shutdown

Upon exit, we shutdown and destroy the RakPeerInterface.

// wait 100ms to receive remain messages then shutdown
rakPeerInterface->Shutdown(100, 0);
RakNetworkFactory::DestroyRakPeerInterface(rakPeerInterface);

Download: bin, src

Tags: , , , , , , , , , ,

Comments are closed.