As a follow-on from my post on floating point values and endian swapping, I thought I’d write an article on strings. String encoding is fairly complicated due to the fact that we’re now living in a large global community – ASCII simply won’t cut it any more. On the positive side, if you only need ASCII you can send it across the network as-is. Since it’s only a single byte, there is no worry about endianness.
The problem is when dealing with string encoding that can go over a single byte, so-called multi-byte encoding. This is very likely if you need to support languages other than English. Rather than worry about endianness, BOMs, and other character encoding headaches; may I humbly suggest you try UTF-8. UTF-8 is completely backwards-compatible with ASCII, yet supports the entire unicode character set. It uses the minimum number of bytes for each character (good for saving bandwidth) and is inherently immune to endian issues (for for your sanity). In C# encoding to UTF-8 couldn’t be simpler:
The only issue with UTF-8 is that you can’t just say that the string is X number of bytes long based on the number of characters. Characters can require anywhere from 1 byte to 6 bytes. The simplest solution to this problem is to encode the string into a byte array, then prefix with an int that contains the length of the entire string in bytes. When reading the string back in, you can read in the first int then read however many bytes the int specifies.
I’ve seen a few posts & threads around the Internet about network order (aka big-endian) and floating point values in C#. First – all binary values are affected by the underlying architecture – this includes floating point values. It’s akin to Hebrew – all the words are written right-to-left, not just some. Now that we’ve cleared that up, I’m sure you’re wondering just how we reverse the ‘endianness’ of floats given that IPAddress only lets you convert ints.
A 32bit int is merely 32 bits of data. We just label it as an ‘int’ and always interpret it thus. What we need to do, is take a 32bit float and pretend it’s an int, get C# to convert it, then send it down the network pipe. The receiver can then fix the endianness and read it as a float. I’ve written some code that does just that:
/// Convert a float to network order
/// <param name="host">Float to convert</param>
/// <returns>Float in network order</returns>
public static byte HostToNetworkOrder(float host)
byte bytes = BitConverter.GetBytes(host);
/// Convert a float to host order
/// <param name="network">Float to convert</param>
/// <returns>Float in host order</returns>
public static float NetworkToHostOrder(int network)
byte bytes = BitConverter.GetBytes(network);
return BitConverter.ToSingle(bytes, 0);
Remember Network Order is always big-endian. .NET itself can be big-endian or little-endian depending on the underlying architecture. For this reason we always check if we even need to perform a conversion by checking if we’re running on a little-endian system. As an aside Java is always big-endian, regardless of the underlying architecture it’s running on.
This is going to be a slightly more ‘abstract’ post compared to what I normally do, but I think it’ll be useful for anyone making multi-player games. The biggest problem with multi-player games is network latency & bandwidth. This is something you just have to design for with your game mechanics, for example many MMOs have ‘cast times’ for most actions to cover the latency between the various clients and the server.
In this post I’m going to show clock synchronisation, a kind of synchronisation where you can ensure your various clients are all sharing the same timestamp as the host, give or take a few ms. For my game, having the same clock on all clients means I can time actions to occur simultaneously across all users. The code below is Java for Android, but it can be ported across to other platforms easily enough.
First you need to request the host’s current timestamp:
_timeRequest = System.currentTimeMillis();
_timeRequest in this case is a long field, while _networkDroid is my networking layer. Next when you get a response, you need to get the offset between the host’s timestamp and the client’s:
long currentTime = System.currentTimeMillis();
long travelTime = (currentTime - _timeRequest) / 2;
_timeDifference = (int)(currentTime - (remoteTimestamp + travelTime));
_timeRequest = 0;
In this code we first get the client’s current time, we get this as early as possible to minimise the error margin. Next we work out how long it took the packet to arrive from the host – we do this by dividing the total transit time by 2 (the first half was sending the request). Next we work out the difference between the hosts timestamp (with transit time taken into account) and the client’s timestamp.
Finally to get the synchronised timestamp is simple:
return System.currentTimeMillis() - _timeDifference;
And there you have it, synchronised clocks!