When making a multiplayer game one of the important thing you need to do is to design your networking handling: UDP vs. TCP, interpolation, lag compensation, client-side prediction, traffic forgery (cheating), message serialisation etc. In this article I'm going to focus on message serialisation in the specific context of building browser-based game.
Why serialisation is important
If you're making anything even slightly more dynamic than chess you're going to need to send a lot of updates to every client. Let's say hypothetically that you want to send a position of each player on a 2 dimensional map, something like this:
{
"direction": "south",
"frame_id": 1337,
"msg_type": 3,
"player_id": 1,
"x": 42.84,
"y": 55.43
}
Let's say you have 16 players, you're going to send the above message 16 times... to 16 different clients... 20 times a second! Using JSON, above message is 103 bytes, which means that each client needs to receive approx 32 KB of data each second and your server is going to need to transmit something around half a MB of data each second. This is definitely not ideal and even though nowadays 32 KB/s sounds like nothing it can introduce undesired lag in your application. On the plus side JSON parsing is well optimised in most programming languages including client-side JavaScript which means using JSON introduces relatively small overhead in terms of CPU time required for message processing. Generally speaking you want to produce smallest messages as fast as possible.
Solutions
As mentioned above JSON is obvious and super-easy but not very efficient method of message serialisation.
- Fast serialisation
- Super-easy to implement
- Mature, widely adopted
- Suboptimal bandwidth usage
One way around the bandwidth problem is to use msgpack which will convert your object/array/hash into a binary string for network transmission and then back into your structure on the other end. This will help you save some bandwidth (about a third in the example message above) and the serialisation is blisteringly fast (faster than JSON).
- Super-fast serialisation
- Moderate bandwidth usage
- Relatively easy to implement
- Mature, widely adopted
- Suboptimal bandwidth usage
However using msgpack still doesn't solve the main issue we're having which is how wasteful it is to include keys (player_id
, x
, y
) in each message. But there's an answer to that: Google's Protocol Buffers. With Protocol buffer we can create a message definition that both the server and all the clients would know and then only send the actual data by using Protocol Buffers serialisation mechanism. This way we save a massive amount of bandwidth instead introducing some extra processing time. Actually lots of it, protobufs seem to be rather slow (benchmarks below). And that's not where the problems end unfortunately.
In your game you're most likely going to have multiple types of messages: a "player joined" message with player name and maybe his avatar or equipment, "player killed", "player left", "player sent a message" etc. With Protobuf there's no easy way of recognizing what message type has just arrived on the wire (remember it's all a binary stream) so you would need to work around this. Also when packing more than one message into the packet/stream/websocket message, you need to take care of where one message ends and another starts.
- Bandwidth optimal
- Mature, widely adopted
- Challenge with message identification and delimiting
- Relatively hard to implement
- Relatively slow to serialise
In the search of high performance, non-wasteful solution I've came up with Schema Messages (in Python and JS). The idea behind it is similar to the one used by protobuf - you share the schema between the client and the server and then transmit only the bytes you need to represent your data. Because Schema Messages is simpler (but more limited!) than Protobuf, it serialises much faster and produces slightly smaller messages. Furthermore messages are self-limiting and can be identified by looking at just the first byte (or bytes, depending on the number of messages defined in the schema).
- Bandwidth optimal
- Relatively fast serialisation
- Relatively easy to implement
- Serialisation is slower than MsgPack
- Immature
Which solution to choose?
To answer this question I've actually built a little demo which is supposed to simulate a websocket-based network game. In this demo, the client requests configurable amount of pixels and the server packs that many pixels as separate messages into a single websocket message, rinse, repeat until the picture is fully transmitted. While it's a horrendously inefficient way of transmitting an image, it nicely mocks real-world scenario where client sends occasional input and server sends large amount of updates. With the help of link conditioner I've measured the performance of each technology with different amount of bandwidth available:
As you can see from the chart above, both JSON and Protobuf are quite inefficient - JSON because of the message size and Protobuf because of serialisation complexities. MsgPack is the generally the fastest to serialize/deserialize but with bandwidth limited to 5mbps, having smaller messages serialised by schema-messages pays off with more efficient data transmission.
Summary
Each of the solutions has its advantages and disadvantages and you need to pick one that works best in your case. If you're writing chess you can probably get away with JSON. For more dynamic games you'll probably need msgpack or schema messages depending on how your messages look like. Either way if you're making a browser-based multiplayer game, I would love to hear about it - ping me or leave a comment below!