On .Net Serialization, Part 3

Published March 28, 2005
Advertisement
Having decomposed a sample output from the BinaryFormatter class, we can now move on to writing our own version, keeping in mind that we are going for optimum size. So, lets first think about what all we need to send for our JoinRequest. Looking over the class, we know that we will need to send the integer, and the string. The string should be length prefixed, of course, to allow for flexability. However, we also need to send some form of information that will allow us to identify that this is a JoinRequest and not, for instance, a Position object.
Quote:
namespace Kent.Shared.Packets.Client {	[Serializable]	public struct JoinRequest {		public JoinRequest(int version, string name) {			Version = version;			PlayerName = name;		}		public int Version;		public string PlayerName;	}}

The ordering of the data also matters. How will we know how to associate the data with a particular field in our class unless we order it in some known manner. More importantly, without a certain ordering, we cannot guarantee that both sides of the serialization will agree on the order in which the fields should appear (more on this later).

This can be solved using reflection. We can easily query the fields in a particular type, their names, type information, and if they can be serialized or not. We can also get and set the values of fields within unknown objects, provided we have an instance of one. Reflection will also allow us to create instances of the types, which will be required in order to deserialize the object from the raw data. So using reflection we could query the fields that an object has, then serialize those fields out to a stream. On the other side of the stream we could deserialize those types back into their appropriate fields, since we know the types of the fields via reflection as well.

However, there are a couple of problems with this, the first being ordering. When you query for the fields in a type, there is no guarantee about the ordering in which they will be returned. So in order to make sure that both sides match, you must sort them. So, in our case, it will be sorted by name. Secondly, our serializer is going to be pretty simplistic. This means that it won't really be able to handle complex graphs of objects, especially not ones which include cyclical references. While this is a minor limitation, for the case of sending game data, it can be quite annoying if you try and apply this same serializer to other projects.

Assuming you're still with me so far, lets see what a theoretical hex dump of our JoinRequest should look like:
Quote:
00000000  01 00 00 00 05 57 61 73  68 75 01 00 00 00        .....Washu....

The first four bytes are our type identifier, indicating that this is a JoinRequest (01 00 00 00). Following that is a 7 bit encoded string length (05), and then the string ('Washu'). Finally comes the Version integer (01 00 00 00). You will notice that the integers are in the machine native format still, which is little endian in my case. This might pose a problem for you if you intend to port your game to other systems which use a different endianess. You can easily solve this by using the System.Net.IPAddress.HostToNetworkOrder and System.Net.IPAddress.NetworkToHostOrder to encode your integer values. Comparing this packet to the original one, which was 181 bytes long, then you can see that this is a significant decrease in size, being only 14 bytes (about a 1300% decrease in size, if you like big numbers [grin]).
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement