DinoMage on Twitter RSS Feed

In my recently released game, Button Battle, I opted to use RakNet to implement simple networked multiplayer.  I figured I should talk a bit about how that all went, since I would have enjoyed reading such an article myself… and frankly, I’m going to forget this all soon and I will have to read it later.

I chose RakNet because it has been around for a while (mature), it works on Windows, Linux, and more (cross-platform), and it is free for my purposes (affordable).  I have my own networking library based on SDL_net that is certainly cross-platform and affordable, but I would be making a bad decision (personally, as a game dev, and as a business) if I put more time into the library instead of my games.  SDL_net does what it does well enough, but there is a lot of work to do in order to get something out of it that can be used in games.

Getting RakNet

Using RakNet requires a license.  To get this license, I had to fill out a form on the RakNet site.  It’s pretty simple to do that, so then I got an email directing me to the download.  The download is actually the source code for RakNet.  It does not come as pre-built binaries, at least not as far as I found.

Building

This is my experience with RakNet v4.037. It is actively developed, so you will not run into all of the same things. If you use MS Visual Studio, you’ll probably have a quick time of building the library files.  There is an included VC++ solution/project file that controls the build process for the main library and the examples.

For me, though, there is a little more work to do.  I use MinGW (via Code::Blocks) on Windows and g++ (usually also via Code::Blocks) on GNU/Linux.  RakNet does have CMake build files, which are cross-platform, but there is a little work to be done on each platform to make the build go on.

On Windows…

I ran CMake-GUI and pointed the source code and build paths to the RakNet base directory (e.g. C:/Users/Jonny/Builds/RakNet_PC-4.037). After pressing the Configure button, I get a popup where I choose the Code::Blocks project file generator and use the native compilers. Pressing Finish brings me back to the main window, where nothing fun is going on. Sadly, the Code::Blocks project generator just creates a Makefile that the project file uses. I would prefer a real C::B project. So, I have to specify where my CMAKE_MAKE_PROGRAM is (it says CMAKE_MAKE_PROGRAM-NOTFOUND). For me, it’s at C:/Program Files (x86)/CodeBlocks/MinGW/bin/mingw32-make.exe. I also check the DISABLEDEPENDENCIES checkbox so I don’t have to get all of the odd dependencies together.  If you use other features or need database access (for a dedicated patch server for example), you will want to work that out.

After trying to Configure again, it fails, but I can now specify the CMAKE_CODEBLOCKS_EXECUTABLE. I do that (C:/Program Files (x86)/CodeBlocks/codeblocks.exe) and press Configure and it works now (with CMake warnings that can be ignored). I can press Generate now, and a new Code::Blocks project appears in the RakNet base directory. However, that project file does not successfully build anything. Instead, I open up the project Lib/DLL/RakNetDynamicLink.cbp. When building this project, I get a lot of errors. One of the errors is very telling:

MINGW3~2.EXE: /D: No such file or directory

This means that one of the CMakeLists.txt files is written wrong. “/D” is how you would specify a command-line define for cl, MS Visual Studio’s compiler. So, pop open Lib/DLL/CMakeLists.txt and change line 8:

- SET( CMAKE_CXX_FLAGS "/D WIN32 /D _RAKNET_DLL /D _CRT_NONSTDC_NO_DEPRECATE /D _CRT_SECURE_NO_DEPRECATE ")
+ SET( CMAKE_CXX_FLAGS "-D WIN32 -D _RAKNET_DLL -D _CRT_NONSTDC_NO_DEPRECATE -D _CRT_SECURE_NO_DEPRECATE ")

Now the build runs until we hit another error. RakNetTypes.cpp line 712 has a function that is not appropriate for this compiler. I use the code in the #else section:

g=strtoull(source, (char **)NULL, 10);

…and we can finally finish the build. Whew!

On Linux…

In the base directory after extraction, I ran:

cmake -G "Unix Makefiles" -DDISABLEDEPENDENCIES=TRUE

The dependencies are a little annoying to gather, so I just disabled them.  If you use other features or need database access (for a dedicated patch server for example), you will want to work that out.  There are some CMake warnings, but everything comes out okay.

Next, of course, is to run ‘make’. I get this error:

/home/jonny/Builds/RakNet_PC-4.037/Source/TeamManager.cpp: In member function ‘RakNet::TeamMemberLimit RakNet::TM_Team::GetMemberLimit() const’:
/home/jonny/Builds/RakNet_PC-4.037/Source/TeamManager.cpp:845:32: error: ‘FALSE’ was not declared in this scope

So clearly there’s a declaration missing. I just went in and changed that line:

-       if (world->GetBalanceTeams()==FALSE)
+       if (world->GetBalanceTeams()==false)

My next error is:

/home/jonny/Builds/RakNet_PC-4.037/Samples/NATCompleteServer/main.cpp:18:31: fatal error: CloudServerHelper.h: No such file or directory

This is just for a sample project, so it’s not really vital. By this point, the library files have already been built and put into Lib/DLL and Lib/LibStatic. You can fix this error by setting the right include paths in one of the CMakeLists.txt files, but I’d rather not go any further with that if we already have the libs.

Installing

I considered installing RakNet in my compiler’s include/ and lib/ directories as I usually do, but decided not to.  Instead, I made a special directory for it that I add to my project’s compiler and linker search paths.  This makes the installation much more modular, especially since the “special directory” is in my Subversion repository.

Connection Screen

Once I got the installation sorted out, it was time to write some code.  The first thing to do was to get two computers connected to each other.

There are just two computers. One is the host/server and the other is the client.

For basic RakNet functionality, writing my networking code revolved around using RakNet::RakPeerInterface.  Creating one of these provides the interface to all the standard networking calls like initializing, connecting/disconnecting, sending/receiving, pinging, and much more.  You will need to #include “RakPeerInterface.h”.

To keep things simple, I used a global pointer to my RakPeerInterface:

RakNet::RakPeerInterface* peer = NULL;

The object is instantiated when I decide if this computer will be a client or a server based on the user’s choice.  I’ll start with the server.

Servers need a port to open for incoming connections, so that’s the function argument.

void initServer(const std::string& portString)
{

I allow the code to call this function multiple times, but I want only one port open at a time.  So I close any existing connections.

closeNetworking();

Create the RakPeerInterface.

peer = RakNet::RakPeerInterface::GetInstance();

Interpret the port number from the string we have.  We should actually only use ports 49152-65535, but I let the user choose a registered port if they feel so inclined.  My DEFAULT_SERVER_PORT is 60000.

int port = toInt(portString);
if(port < 1024 || port > 65535)
    port = DEFAULT_SERVER_PORT;

Now we create a socket to start the server on.  MAX_CLIENTS in Button Battle is 1 since we can only ever have one opponent.

RakNet::SocketDescriptor sd(port,0);
peer->Startup(MAX_CLIENTS, &sd, 1);

I maintain my own (global, bleh) variables to make it easy to tell if there is a connection and if the local computer is the server or the client.

isServer = true;

Then I make sure that there will only be one other connection coming in on this port.

printf("Starting the server.n");
// We need to let the server accept incoming connections from the clients
peer->SetMaximumIncomingConnections(MAX_CLIENTS);
}

The client initialization is similar, but I lumped together initialization and connection since the client isn’t one (a client) until he connects.  That means a little more error checking, too.

The client needs an IP address to connect to and a port to try.  I added a return value for my status messages to the user.

std::string initClient(const std::string& ipString, const std::string& portString)
{

This part should look familiar.

    closeNetworking();
    peer = RakNet::RakPeerInterface::GetInstance();
    RakNet::SocketDescriptor sd;
    peer->Startup(1,&sd, 1);
    isServer = false;
    int port = toInt(portString);
    if(port < 1024 || port > 65535)
        port = DEFAULT_SERVER_PORT;

I did minimal IP string validation.  If it doesn’t work, it doesn’t work.  I assume IPv4, but RakNet does support IPv6.  I just haven’t looked very far into it yet.

    std::string ip = ipString;
    if (ip.size() == 0)
    {
        ip = "127.0.0.1";
    }

Now we’re ready to attempt a connection.  There are lots of optional arguments to RakPeerInterface::Connect(), but I wanted to reduce the time the user has to wait for a failure message.  To do this, I specified a lower-than-default number of connection tries.

    printf("Starting the client.n");
    int tries = 5;
    int msPerTry = 500;
    RakNet::ConnectionAttemptResult result = peer->Connect(ip.c_str(), port, NULL, 0, NULL, 0, tries, msPerTry, 0);

RakPeerInterface::Connect() returns a RakNet::ConnectionAttemptResult which is usually a RakNet::CONNECTION_ATTEMPT_STARTED.  The errors we can check for are immediate, but not terribly applicable to Button Battle.

    switch(result)
    {
        case RakNet::CONNECTION_ATTEMPT_STARTED:
            return string("Attempting connection to "") + ip + string(""...");
        break;
        case RakNet::INVALID_PARAMETER:
            return "Connection attempt failed to start: Invalid parameter.";
        break;
        case RakNet::CANNOT_RESOLVE_DOMAIN_NAME:
            return "Connection attempt failed to start: Cannot resolve domain name.";
        break;
        case RakNet::ALREADY_CONNECTED_TO_ENDPOINT:
            return "Connection attempt failed to start: Already connected.";
        break;
        case RakNet::CONNECTION_ATTEMPT_ALREADY_IN_PROGRESS:
            return "Connection attempt failed to start: Already attemping...";
        break;
        case RakNet::SECURITY_INITIALIZATION_FAILED:
            return "Connection attempt failed to start: Security could not be initialized.";
        break;
    }
    return "";
}

Disconnecting is important, so I have a function that cleans up the networking stuff.

void closeNetworking()
{
    if(peer == NULL)
        return;

The RakPeerInterface is shut down and destroyed after it waits for 500ms to send all remaining outgoing packets.

    peer->Shutdown(500, 0, LOW_PRIORITY);
    RakNet::RakPeerInterface::DestroyInstance(peer);
    peer = NULL;
}

Now that we can connect and disconnect, we need to send, receive, and react to data packets.

First, sending…  Put in #include “MessageIdentifiers.h” so we can create our own packet IDs, #include “BitStream.h” for the packet data structure, and #include “RakNetTypes.h” for the RakNet::MessageID cast we’ll be using.

enum GameMessages
{
    ID_DEBUG = ID_USER_PACKET_ENUM+1,
    ID_GO_TO_NET_MENU,
    ID_START_GAME,
    ID_TEXT,
    ID_GAME_ATTACK,
    ID_GAME_BLOCK,
    ID_GAME_RESET,
    ID_GAME_QUIT,
    ID_GAME_OVER
};

There’s some design work that you can do here, but it boils down to considering how the two players can interact with each other.  Button Battle is a simple game, so the list of interactions is short.  For now, on the Connection Screen, the only thing I need the server and client to do is to agree that they are connected and that they should go to the next screen (ID_GO_TO_NET_MENU).  You can get fancy with the RakNet::ReadyEvent plugin, but I just made a simple packet for this game.  The server is in charge of telling the client machine what to do.

This function sends the ID_GO_TO_NET_MENU packet (from a connected server).

void net_GoToNetMenu()
{
    if(peer == NULL || !isConnected || !isServer)
    return;

A packet is usually sent as a RakNet::BitStream.  You create a RakNet::BitStream, push an ID and some data into it and then send it out.  This one needs no extra data.

    RakNet::BitStream bsOut;
    bsOut.Write((RakNet::MessageID)ID_GO_TO_NET_MENU);

Here I use a wrapper, explained next.

    sendPacket(bsOut);
}

I only ever have one connected remote computer, so I made a couple of simplifying wrappers for sending packets.

RakNet::SystemAddress getConnectedSystem(int index = 0);
void sendPacket(const RakNet::BitStream& bsOut, PacketReliability reliability = RELIABLE_ORDERED);

RakNet uses a RakNet::SystemAddress to represent a connected peer.  This is used to identify who I send packets to.

RakNet::SystemAddress getConnectedSystem(int index)
{
    if(peer == NULL)
        return RakNet::SystemAddress();
    return peer->GetSystemAddressFromIndex(index);
}

Sending packets has several options which I am not dealing with.  RakNet uses UDP packets with an optional custom reliability layer (no TCP).  This means you can choose if packets should be reliably received and if they should arrive in the order they were sent.  I am sending all packets reliably, in order, and right away (RELIABLE_ORDERED and HIGH_PRIORITY).  This is not the right choice for all use cases.  Most games have packets that are not critical if they sometimes aren’t received.  UNRELIABLE sends are very fast and great for frequently-sent data.

void sendPacket(const RakNet::BitStream& bsOut, PacketReliability reliability)
{
    if(!isConnected)
        return;
    peer->Send(&bsOut, HIGH_PRIORITY, reliability, 0, getConnectedSystem(), false);
}

Now I tell the peers how to handle the incoming packets.  My plan for this was to pass in all of the variables that the network uses to communicate with the game.  On this screen, the network can tell us to go to the next screen (startGame) or add a message for the user.

void checkPackets_ConnectMenu(bool& startGame, std::list& messages)
{
    if(peer == NULL)
    return;

This loops through every packet that has been received.

    RakNet::Packet *packet;
    for (packet=peer->Receive(); packet; peer->DeallocatePacket(packet), packet=peer->Receive())
    {

I switch over the packet ID byte.

        switch (packet->data[0])
        {

Most of these can go without explanation.  I tried to do only the packet handling for packet IDs which made sense for this screen.  If a connection is made, the next screen should start.  The server is the one which directs this, so it sends a start game packet when connected.

            case ID_CONNECTION_ATTEMPT_FAILED:
                printf("Connection attempt failed.n");
                messages.push_back("Connection attempt failed. See networking help screen.");
            break;
            case ID_NO_FREE_INCOMING_CONNECTIONS:
                printf("The server is full.n");
                messages.push_back("The server is full.");
            break;
            case ID_CONNECTION_REQUEST_ACCEPTED:
                printf("Connection successful.n");
                messages.push_back("Connection successful.");
                startGame = true;
            break;
            case ID_NEW_INCOMING_CONNECTION:
                printf("A connection is incoming.n");
                messages.push_back("A client has connected.");
                startGame = true;
                // Send "start game" packet
                if(net_IsServer())
                    net_GoToNetMenu();
            break;
            case ID_DISCONNECTION_NOTIFICATION:
                if (isServer){
                    printf("A client has disconnected.n");
                } else {
                    printf("We have been disconnected.n");
                }
                messages.push_back("Connection lost.");
            break;
            case ID_CONNECTION_LOST:
                if (isServer){
                    printf("A client lost the connection.n");
                } else {
                    printf("Connection lost.n");
                }
                messages.push_back("Connection lost.");
            break;
            case ID_GO_TO_NET_MENU:
                {
                    startGame = true;
                }
            break;
            default:
                {
                    printf("Message with identifier %i has arrived.n", packet->data[0]);
                }
        }
    }

After the packet loop, I update the connection status.

    isConnected = (peer->NumberOfConnections() > 0);
}

Doing all that for the first time is the bulk of the work. The next menu is similar.

Networking Menu

The networking menu is where the players wait to start the game.  They can chat here between games or disconnect.

There’s not much to do on this screen, either, once you think about it.  The game can start, a player can disconnect, or a text message can be transferred.  I also added a ping display just so I could try it out.

Starting the game uses net_StartGame():

void net_StartGame()
{
    if(peer == NULL || !isConnected)
        return;
    RakNet::BitStream bsOut;
    bsOut.Write((RakNet::MessageID)ID_START_GAME);
    sendPacket(bsOut);
}

Sending text uses net_SendText():

void net_SendText(const std::string& text)
{
    if(peer == NULL || !isConnected)
        return;
    RakNet::BitStream bsOut;
    bsOut.Write((RakNet::MessageID)ID_TEXT);
    bsOut.Write(RakNet::RakString(text.c_str()));
    sendPacket(bsOut);
}

Pinging and checking the result is easy (the pong is handled automatically):

void net_SendPing()
{
    if(peer == NULL || !isConnected)
        return;
    peer->Ping(getConnectedSystem());
}
int net_GetPing()
{
    if(peer == NULL || !isConnected)
        return 0;
    return peer->GetLastPing(getConnectedSystem());
}

In the menu’s update loop, I keep a timer that causes net_SendPing() to be called once every second.

Checking the packets is just like before, but of course specialized for these packets.

// Returns a string which holds a new chat message.
// This is a bad way to do it because we might overwrite messages if multiple are processed in the same frame...
// but it keeps this function cleaner than it would be if it had to handle the message list clipping.
std::string checkPackets_NetMenu(bool& startGame)
{
    string result;
    if(peer == NULL)
        return result;
    RakNet::Packet *packet;
    for (packet=peer->Receive(); packet; peer->DeallocatePacket(packet), packet=peer->Receive())
    {
        switch (packet->data[0])
        {
            case ID_REMOTE_DISCONNECTION_NOTIFICATION:
                printf("A client has disconnected.n");
            break;
            case ID_REMOTE_CONNECTION_LOST:
                printf("A client has lost the connection.n");
            break;
            case ID_DISCONNECTION_NOTIFICATION:
                if (isServer){
                    printf("A client has disconnected.n");
                } else {
                    printf("We have been disconnected.n");
                }
                // TODO: Go back to connectMenu
            break;
            case ID_CONNECTION_LOST:
                if (isServer){
                    printf("A client lost the connection.n");
                } else {
                    printf("Connection lost.n");
                }
            break;
            case ID_TEXT:
                {
                    RakNet::RakString rs;
                    RakNet::BitStream bsIn(packet->data,packet->length,false);
                    bsIn.IgnoreBytes(sizeof(RakNet::MessageID));
                    bsIn.Read(rs);
                    result = rs.C_String();
                }
            break;
            case ID_START_GAME:
                {
                    startGame = true;
                    if(isServer)
                        net_StartGame();
                }
            break;
            default:
                {
                    printf("Message with identifier %i has arrived.n", packet->data[0]);
                }
        }
    }
    isConnected = (peer->NumberOfConnections() > 0);
    return result;
}

The only interesting thing here is how to read data from a packet.  The ID_TEXT packets actually contain data, instead of being just event triggering packets.

RakNet has its own string class, which is claimed (and it’s very likely true) to be much faster than std::string.

RakNet::RakString rs;

We turn the packet data back into a RakNet::BitStream so we can read it easily.

RakNet::BitStream bsIn(packet->data,packet->length,false);

Skip the byte that contains the ID.

bsIn.IgnoreBytes(sizeof(RakNet::MessageID));

And read the data into our string variable.

bsIn.Read(rs);

Then you’re ready to do stuff with it.  RakNet::BitStream::Read() is overloaded and templated so you can do this same thing with many data types.

Now that we have a handle on how to transfer different data types, there should be no problem with the game loop itself.

Main Game

With all that menu stuff out of the way, I could finally make the actual game work.  From here it’s pretty simple, but there was a need for some refactoring in order for the code to be concise when handling either local or network play.

The thrust of the refactoring was to boil down the way the game responds to player input.  When I can call a single function to respond to a key press, then I can just call that function whenever the corresponding “key press” network event occurs.

For the sake of some order, I pulled all of the main game loop variables, logic, and drawing into a single class (GameData).  GameData::doAttack() and GameData::doBlock() are called in the event processing code on the server (and a packet is fired off to notify the client) and they are called in the packet handling code on the client (so the server ultimately decides if it actually happens).  Other actions are independent of the server/client identity, so that either player can quit or reset the game.

The key press handling code:

// Check if a player made a keypress and handle it.
void GameData::checkPress(const SDL_Event& event)
{
    for(std::map::iterator e = players.begin(); e != players.end(); e++)
    {
        // Obtuse... (!isServer) is the local player's num
        if(!netGame || (netGame && e->first == !net_IsServer())) // If not networked or this is the player we control
        {
            // If it's this player's turn...
            if(turn < 0 || turn == e->first)
            {
                Player* p = e->second;
                int btn = p->checkPress(event);
                // If a button was pressed...
                if(btn >= 0)
                {
                    // If the game is waiting for an attack, do an attack
                    if(currentButton < 0)                     {                         // Attack. The server does it right away. The client will hear back from the server.                         // Send packet                         net_GameAttack(e->first, btn);
                        // If we're the server, process the attack
                        if(!netGame || net_IsServer())
                            doAttack(e->first, btn);
                    }
                    else // Do a block
                    {
                        // Block. The server does it right away. The client will hear back from the server.
                        int reactionMilliseconds = int(SDL_GetTicks() - lastAttackTime);
                        net_GameBlock(e->first, btn, reactionMilliseconds);
                        // If we're the server, process the block
                        if(!netGame || net_IsServer())
                            doBlock(btn, reactionMilliseconds);
                    }
                }
            }
        }
    }
}

The packet handling code:

void checkPackets_Gameloop(GameData& data)
{
    if(peer == NULL)
        return;
    RakNet::Packet *packet;
    for (packet=peer->Receive(); packet; peer->DeallocatePacket(packet), packet=peer->Receive())
    {
        switch (packet->data[0])
        {
            case ID_REMOTE_DISCONNECTION_NOTIFICATION:
                printf("A client has disconnected.n");
                data.quit();
            break;
            case ID_REMOTE_CONNECTION_LOST:
                printf("A client has lost the connection.n");
                data.quit();
            break;
            case ID_DISCONNECTION_NOTIFICATION:
                if (isServer){
                    printf("A client has disconnected.n");
                } else {
                    printf("We have been disconnected.n");
                }
                data.quit();
            break;
            case ID_CONNECTION_LOST:
                if (isServer){
                    printf("A client lost the connection.n");
                } else {
                    printf("Connection lost.n");
                }
                data.quit();
            break;
            case ID_GAME_ATTACK:
            {
                RakNet::BitStream bsIn(packet->data,packet->length,false);
                bsIn.IgnoreBytes(sizeof(RakNet::MessageID));
                int playerNum;
                int button;
                bsIn.Read(playerNum);
                bsIn.Read(button);
                if(data.turn < 0 || data.turn == playerNum) // It's this player's turn                 {                     data.doAttack(playerNum, button);                     if(isServer)                     {                         net_GameAttack(playerNum, button);                     }                 }             }             break;             case ID_GAME_BLOCK:             {                 RakNet::BitStream bsIn(packet->data,packet->length,false);
                bsIn.IgnoreBytes(sizeof(RakNet::MessageID));
                int playerNum;
                int button;
                int reactionMilliseconds;
                bsIn.Read(playerNum);
                bsIn.Read(button);
                bsIn.Read(reactionMilliseconds);
                if(data.turn < 0 || data.turn == playerNum) // It's this player's turn                 {                     data.doBlock(button, reactionMilliseconds);                     if(isServer)                     {                         net_GameBlock(playerNum, button, reactionMilliseconds);                     }                 }             }             break;             case ID_GAME_RESET:             {                 data.reset();                 if(isServer)                 {                     net_GameReset();                 }             }             break;             case ID_GAME_QUIT:             {                 data.quit();                 if(isServer)                 {                     net_GameQuit();                 }             }             break;             case ID_GAME_OVER:             {                 // Only the client receives this message                 if(!isServer)                 {                     RakNet::BitStream bsIn(packet->data,packet->length,false);
                    bsIn.IgnoreBytes(sizeof(RakNet::MessageID));
                    bsIn.Read(data.winningPlayer);
                }
            }
            break;
            default:
            {
                printf("Message with identifier %i has arrived.n", packet->data[0]);
            }
        }
    }
}

Summary

So that’s how I implemented a server and client for Button Battle.  The process is not so bad, as long as you take the time to make a simplified interface for your network events to affect the game just like local events do.  Using RakNet is definitely recommended and I’ve barely even touched the additional features.

Filling the cracks

There are several ways that corner cases can interfere with your networking.  Most of them depend on your particular game, but there are some general things to pay attention to.  Sometimes it helps to be paranoid, so put on your crazy glasses and tinfoil hat.

Desynchronizing

This is really the big beast we are after.  Things get unpredictable when the client and server are no longer tightly coupled.  This happens when they don’t communicate properly.  When either system does something important, make sure the other knows about it.  When implementing networking, I often have to think about how to make my submenus non-blocking.  You don’t want a ton of packets piled up waiting for you when you close your color picker or unpause the game, for instance.

Too many connections

Make sure to specify how many connections you want to max out at and to stop incoming connections once you are playing/moving on (unless that’s a fancy feature of your game).

Duplicate packet ID between screens

Some packet IDs can be reused, but think about what might happen if the systems are slightly desynced and one of these packets gets loose onto the wrong menu/screen.  Say, if I used ID_START_GAME to send the user from the one menu to another and then I use another ID_START_GAME to begin the game, there’s a chance that if the server player clicks repeatedly on the start button in the first menu, he might cause the client to start the game while the server lands in the second menu.

Security and Passwords

RakNet has options for security via encryption and passwords.  I haven’t used any of this yet, but it’s a very important feature for some programs.  Encryption can be used to send sensitive/private data.  Passwords can be used to restrict connections only to players who know the password (e.g. for private game matches).

Extensions

There are several more things that I look forward to trying out with RakNet.  Here are just a few.

Alternate connection methods

Button Battle only connects via IP address.  There are other methods which make the user experience less painful.

Dedicated server

In many cases, a dedicated server works better than a built-in server like I have in Button Battle.  You will usually get better performance by splitting some processing off onto the separate server.  You may also use RakNet’s NAT punchthrough features to avoid the need to mess with router settings to redirect connections from the router to your individual system (see Button Battle’s in-game networking help).  And you can use data encryption safely by not needing to transfer security keys for an official server.

Autopatching

RakNet has an autopatcher plugin that lets you update the game files on the client system.  This is wonderful so users don’t have to locate, download, and install the whole game again whenever there is an update.  You’ll need to host a patch server.

Conclusion

Well, that was long.  I hope you got something out of it!

6 Responses to “Basic networking with RakNet”

  1.  Terry says:

    Wow, thorough! Thanks for the good write-up. Were there any other network libraries that you considered? There must be some other decent cross-platform ones out there.

    •  Jonny D says:

      You’re welcome!

      I’ve heard about others, but I’ve really only tried SDL_net and RakNet.  Some other popular suggestions are enet, Boost.Asio, language-specific standard libs (Java in particular, but most other languages newer than C++ have cross-platform sockets), and engine built-ins (Unity, Unreal, Torque, etc.).

  2.  Merlin says:

    Thanks for that tuto, it really helped me 🙂
    @Terry
    enet doesn’t support mac…
    @jonny D
    Unity only overlay RakNet

    •  Jonny D says:

      Enet is a pretty simple library.  It should be portable to any platform that supports BSD sockets (and certainly that includes Mac OS X!).

  3.  Tanner says:

    This is a pretty awesome write up! I’m just starting to play around with RakNet after doing simple networking things in Java.

    Thanks for taking the time to do this!

  4.  tiger says:

    a big thanks for the writeup. You saved my day when I had to compile a larger project with raknet depencency using cmake for a gnu compiler – while being new to cmake. The cmake did build fine, but the project would not compile. Your report that you got that going gave me the energy to see it through and indeed I got the thing compiled sucessfully.  Helped me a lot to get a feel for the traps you might encounter with cmake lists 🙂

    I looked though you project decription and thinks is very good to follow and informative about raknet hands on. I wish I would know more pages like that. I’ll bookmark your page for sure.
    Keep up good work!
    Cheers
    Tiger

Leave a Reply