Sign in to follow this  
caymanbruce

Data-interchange protocol in Websocket browser based game: Use 3rd party or custom protocol?

Recommended Posts

When I first build my browser based game I use JSON as the data-interchange protocol. But then I realize the data in JSON object is too big for transmission. Although JSON serializaiton and deserialization may be the fastest because they are optimized for V8 engine, but NO JSON.

Then I have switched to using a 3rd party protocol or specifically MessagePack protocol as the implementation. I guess using protobuf or other 3rd party protocols give similar result. This binary data-interchange protocol reduce much of the traffic but I realize I am still building an Object before encoding into a binary ArrayBuffer. It seems I can just convert the data directly into TypedArray and send them as binary data without using an object, and that will also eliminate the property names in the objects, saving a few bytes for each message. If the message contains a list of objects it can save more space. This would look like my own custom binary data-interchange protocol which doesn't even need to involve encoding and decoding via MessagePack protocol.

I hope my custom binary data-interchange protocol is framework/language independent so I can implement it in different backend and frontend, not limited to Javascript. But as I have little experience with this before, I hope to get some feedback here. Using a 3rd party library/protocol may give me confidence as it is being used by many projects and the failure rate is low. But I can also get rid of the 3rd party protocol and use my own, which looks much cleaner and I know all the details of the protocol.

At the end of the day my protocol may look something like this:

(Using '|' to separate each type of data but the '|' doesn't put into data transmission as I know how it is organized)

TIMESTAMP|MESSAGE_TYPE|PLAYER_ID|PLAYER_X|PLAYER_Y|PLAYER_ROTATION ...

Edited by caymanbruce

Share this post


Link to post
Share on other sites

Some of the 3rd party binary protocols include the field names in the transmitted message, some do not. And sometimes it doesn't matter because they implement useful compression methods that make up for other inefficiencies. Also, some of these systems allow you to add 'extension' types which can be laid out however you like, including the sequential binary example you gave. Besides, there's nothing forcing you to encode everything as an object, which may become a name/value map - you could just send a list of values instead.

Most games have traditionally used their own low-level binary serialisation - i.e. your second example - but most games were written before MessagePack et al existed, and most games aren't written in Javascript or designed to run over Websockets. It's probably best to use 3rd party code where practical.

Share this post


Link to post
Share on other sites

In this previous GameDev article, I used text messages, delivered with some optimization mechanisms, to transport the positions.

https://www.gamedev.net/resources/_/technical/multiplayer-and-network-programming/optimizing-multiplayer-3d-game-synchronization-over-the-web-r3446

This does not directly answer your question, but I hope it can be useful.

Share this post


Link to post
Share on other sites

Some of the 3rd party binary protocols include the field names in the transmitted message, some do not. And sometimes it doesn't matter because they implement useful compression methods that make up for other inefficiencies. Also, some of these systems allow you to add 'extension' types which can be laid out however you like, including the sequential binary example you gave. Besides, there's nothing forcing you to encode everything as an object, which may become a name/value map - you could just send a list of values instead.

Most games have traditionally used their own low-level binary serialisation - i.e. your second example - but most games were written before MessagePack et al existed, and most games aren't written in Javascript or designed to run over Websockets. It's probably best to use 3rd party code where practical.

 

Thank you. With regards to the layout of the data I can send everything as a list of values and encode/decode the data using MessagePack. However as I mentioned before the encoding and decoding methods in those 3rd party serialization protocols may not be as efficient as using JSON as it is native in Node.js/javascript and is optimized in Google's V8 engine. If I organize my data as shown in the second example and don't convert it into smaller size types before encoding with 3rd party tools, I don't think the 3rd party tools will convert it for me automatically. But if I have already converted the data into smaller size data types, encoding it into another format using the 3rd party serialization method seems unnecessary. For example, I may need to send one part of the message using uint8 instead of the default double precision float type which is 64bit, but the 3rd party tool won't do that for me automatically. Unless I want to convert the entire message, which is the value list into the same data type.

Edited by caymanbruce

Share this post


Link to post
Share on other sites

"the encoding and decoding methods in those 3rd party serialization protocols may not be as efficient as using JSON as it is native in Node.js/javascript and is optimized in Google's V8 engine" - try it and see. Or just read up on it; MessagePack includes benchmarks on its site, for example. What's your actual problem? You already accepted that JSON was too large so you can't have it both ways.

If the question is, "can I send 6 simple data values with this tool and have it automatically serialised into small and efficient values" then the answer is yes.

Share this post


Link to post
Share on other sites

"the encoding and decoding methods in those 3rd party serialization protocols may not be as efficient as using JSON as it is native in Node.js/javascript and is optimized in Google's V8 engine" - try it and see. Or just read up on it; MessagePack includes benchmarks on its site, for example. What's your actual problem? You already accepted that JSON was too large so you can't have it both ways.

If the question is, "can I send 6 simple data values with this tool and have it automatically serialised into small and efficient values" then the answer is yes.

 

My question is a bit complex: 

Say I have a message data like this

text-text-uint8-float64-float64-float64-float64-float64,

I will call this message.

Now I want to encode this message. I want to send it as:

binarytext-binarytext-uint8-uint16-uint16-uint8-uint16-uint8.

The tool won't do it automatically for me, or will it? I am not sure.  But I am sure it doesn't do that for rotation value if I want my rotation to have binary scaling. If I convert the data types by myself before sending the sequence out I don't really need the tool to encode for me.  The tool is there to help me convert things from text/float/integer to binary types but if I want a particular binary data type for a particular field and some sort of binary scaling it won't handle that for me automatically.

Edited by caymanbruce

Share this post


Link to post
Share on other sites

In this previous GameDev article, I used text messages, delivered with some optimization mechanisms, to transport the positions.

https://www.gamedev.net/resources/_/technical/multiplayer-and-network-programming/optimizing-multiplayer-3d-game-synchronization-over-the-web-r3446

This does not directly answer your question, but I hope it can be useful.

 

Thanks that protocol looks very nice. I prefer to use a value list given that I know the order of the fields.

Share this post


Link to post
Share on other sites

Okay, there are several separate issues here:

  • There isn't really a transformation that changes 'text' to 'binary text'. Text is typically sent as-is, either zero-terminated or length-prefixed. All these off-the-shelf binary protocol tools will handle this. If the text is long, sometimes it's worth compressing it first, but I don't think that's relevant here.
  • Converting a float64 to a uint16 is indeed something you'd have to do separately. But that's not 'encoding as binary', that's just an arbitrary data transformation. You'd perform the transformation before passing the data to the tool, and you'd perform the inverse translation on the other end. The presence or absence of the tool is of no interest (except in one way - you may not even need to perform the transformation, because these tools often have their own efficient way of representing values. MessagePack and ProtocolBuffers both compress integers pretty well, for example. (Floats, they do nothing with, I think.))
  • If you're working in Javascript then you usually don't deal with 'uint16' or any other low-level type; normally you have strings and numbers and that's about it. You can go via TypedArray, but that is not worthwhile for a set of heterogeneous types, as in your example (e.g. string, integer, several floats), especially when you have a tool available that will happily serialise them all to a binary format for you.

Share this post


Link to post
Share on other sites

 

Okay, there are several separate issues here:

  • There isn't really a transformation that changes 'text' to 'binary text'. Text is typically sent as-is, either zero-terminated or length-prefixed. All these off-the-shelf binary protocol tools will handle this. If the text is long, sometimes it's worth compressing it first, but I don't think that's relevant here.
  • Converting a float64 to a uint16 is indeed something you'd have to do separately. But that's not 'encoding as binary', that's just an arbitrary data transformation. You'd perform the transformation before passing the data to the tool, and you'd perform the inverse translation on the other end. The presence or absence of the tool is of no interest (except in one way - you may not even need to perform the transformation, because these tools often have their own efficient way of representing values. MessagePack and ProtocolBuffers both compress integers pretty well, for example. (Floats, they do nothing with, I think.))
  • If you're working in Javascript then you usually don't deal with 'uint16' or any other low-level type; normally you have strings and numbers and that's about it. You can go via TypedArray, but that is not worthwhile for a set of heterogeneous types, as in your example (e.g. string, integer, several floats), especially when you have a tool available that will happily serialise them all to a binary format for you.

 

 

Thanks for pointing out the issues. I am trying to use TypedArray now and I can see the benefit of using it in serialization. If all the messages add up for all the players I can probably save several KB per second by simply changing one value in the sequence. Otherwise every number will be stored in float64 and sent as the packet over the network. I have also observed games of similar type and I can see the value in converting float64 into smaller size data types. But if you deem MessagePack and ProtocolBuffers both compress integers pretty well  I think maybe I should keep using these tools as they can compress the integer values after I transform the data types.

Edited by caymanbruce

Share this post


Link to post
Share on other sites

the data in JSON object is too big for transmission


Is that actually a fact? Have you measured it? Have you made an estimate of how much you can save? Are those savings worthwhile?
It may be that paying attention to encodings (making key names short, not sending unnecessary fields, etc) will make it as small as it needs to be, and you can then spend your limited time on something else.

If you need to do binary encoding/decoding, then you should be using an ArrayBuffer with a Int8Array view.

Share this post


Link to post
Share on other sites

 

the data in JSON object is too big for transmission


Is that actually a fact? Have you measured it? Have you made an estimate of how much you can save? Are those savings worthwhile?
It may be that paying attention to encodings (making key names short, not sending unnecessary fields, etc) will make it as small as it needs to be, and you can then spend your limited time on something else.

If you need to do binary encoding/decoding, then you should be using an ArrayBuffer with a Int8Array view.

 

 

I don't know how big it is. But it will be bigger than what I am using now, as the example shows on MessagePack's home page. I used JSON in my first version but as I have iterated several versions from then on all my data is transferred using MessagePack, which has a very easy to use API. Then after I have observed some of the most well known games of similar type I think I better do the binary conversion and do it early. I have used TypedArray in some of my code and the outcome is pretty good. I am just wondering if I need to keep the 3rd party serialization tool or build my own serialization protocol without it.

Share this post


Link to post
Share on other sites

You need to do more research and get to the point where you understand why some formats are more efficient.  Formats like protobuf not sending field names is an obvious one.  Varint compression is huge once you understand how it works (you can send X-bit integers using fewer then X bits).

But the big picture is that it's a combination of a number of things that impact network usage and overall performance.  Using techniques to simply not send data you don't need to send is just as if not more important then optimizing the data format.  Structuring your code to take the best advantage of things like varint compression, makes a huge difference.  

For example a trick I use is I never send floats for stuff like position updates.  I send integers using varint compression and decide on the highest decimal precision I actually need.  I multiply/divide to convert floats to ints and visa versa at that precision.  That results in huge savings for the type of data that makes up most of my network traffic.  

Currently the best general approach I know of is varient/MSB encoding combined with using integers to represent as much as you can.  I've just found it to give the best results over the largest variety of use cases in multiplayer games.  

And I also have to factor in integration with other frameworks I might be using.  Like I might be using Akka or MS Orleans as my core server framework, and if they natively support protobuf, well that means I don't have to take the GC hit to deserilize my format and then serialize again into theirs.  And on the server if you are working with message rates normal to say an mmo or fps game, it's object creation and GC that eventually becomes your bottleneck. 

What I always tell people that are relatively new at this is no, don't even think about creating your own format until you first have a solid understanding of how existing formats work and you have gone through creating at least one working game of the specific genre you are tackling.  That's the best overall advice I can give.

 

 

 

 

 

Share this post


Link to post
Share on other sites

I ran into a similar problem when I was an indie game developer back in 2012.  I found this awesome service called PubNub (disclaimer I now work there), and I created a multiplayer game by sending JSON packets back at forth.  It was called Draw & Guess Online and it got tens of thousands of downloads.  The amount of money I made from it totally outweighed the cost of PubNub since it's basically free if you are sending messages efficiently.  Recently I've been working on a game that showcases how you can efficiently send JSON packets using PubNub that I think you could benefit from since I'm storing all the information you listed above: timestamp, player.x, player.y etc

https://github.com/pubnub/Ninja-Multiplayer-Platformer

Message me at schuetz@pubnub.com if you have any integration problems or want to learn more.  

Share this post


Link to post
Share on other sites

I am just wondering if I need to keep the 3rd party serialization tool or build my own serialization protocol without it.

There's no right or wrong answer. You need to work out what the actual problem is that you need to solve. And since you're not measuring anything yet, you have no data on which to base a choice. Some games work fine with just JSON, some use an off-the-shelf binary protocol, some will pack the bytes directly ourselves, but the choice depends on a lot of factors specific to that game. Just looking at existing games and trying to copy them is not a good approach unless you understand why they made the choices they did. (And throwing in TypedArray when you already have a system for binary serialisation seems like a recipe for bugs, but hey.)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this