🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

please critique my serialization system (.NET / reflection)

Started by
2 comments, last by graveyard filla 17 years, 3 months ago
Hi, I'm working on a 3d MMORPG in C#. Anyway, I spent some time coming up with a very simple and high level messaging and serialization system (e.g. how do I serialize / deserialize my packets and send/receive them). Yes I did check out the FAQ and the thread mentioned there [grin]. Basically, I have one base class called 'GameMessage'. It has a constructor which takes a byte[] and deserialize it to the object, and a function called ToByte() which serializes it to a byte array. It uses serialization to do all the work. Now all my messages simply inherit from this base class, and define their members and a constructor which just calls the base constructor, thats it! The beauty of this is that I DON'T have to write any serialization code now, all I have to do is inherit from the GameMessage and my CTOR and ToByte() will work as expected automatically. This is really cool because making new messages is very simple. Now I have 2 questions 1) How will this scale with potentially hundreds of players hitting a single World Server? Is the serialization going to choke and burn under much stress? I have to do some tests, but I'm curious if this whole idea is even feasable for a real time game. 2) Anything you would do differently? Some crazy reflection stuff that I'm not aware of that will make things easier or faster? One thing you'll notice I had to do was sort the FieldInfo class, because apparently GetFields() gives you the fields in a random order, not the order you define them. Here's the code.. (base GameMessage class)

        public class GameMessage 
        {
     
            private static int SortFieldInfo(FieldInfo left, FieldInfo right)
            {
                if (left.Equals(right))
                {
                    return 0;
                }

                if (right.Name == "ID")
                    return 1;
                else if (right.FieldType.Name == "String")
                    return -1;
                else 
                    return 0;
            }
            public GameMessage(){}
            public GameMessage(byte[] data)
            {
                List<byte> bytes = new List<byte>();
                List<FieldInfo> info = new List<FieldInfo>(this.GetType().GetFields());

                info.Sort(SortFieldInfo);
                int idx = 0;

                for (int i = 0; i < info.Count; i++)
                {
                    if (info.FieldType.Name == "String")// is string)
                    {
                        string value;
                        UInt16 size;

                        if (i != info.Count - 1)
                        {
                            size = BitConverter.ToUInt16(data, idx);
                            idx += 2;

                            value = ASCIIEncoding.ASCII.GetString(data, idx, size);
                            idx += size;
                        }
                        else
                        {
                            value = ASCIIEncoding.ASCII.GetString(data, idx, data.Length - idx);
                            idx += data.Length - idx;
                        }
                      
                        info.SetValue(this,value);
                    }
                    else
                    {
                        if (info.Name != "ID")
                        {
                            Type[] types = new Type[2] { data.GetType(), idx.GetType() };
                            object[] values = new object[2] { data, idx };

                            info.SetValue(this, typeof(BitConverter).GetMethod("To" + info.FieldType.Name, types).Invoke(null, values));
                            idx += Marshal.SizeOf(info.FieldType);
                        }
                        else
                        {
                            idx += sizeof(ID);
                        }
                    }
                }
      
            }
            public byte[] ToByte()
            {
                List<byte> bytes = new List<byte>();
                List<FieldInfo> info = new List<FieldInfo>(this.GetType().GetFields());
                info.Sort(SortFieldInfo);

                for(int i=0;i<info.Count;i++)
                {
                    if (info.FieldType.Name == "String")
                    {
                        if (i != info.Count - 1)
                        {
                            bytes.AddRange(BitConverter.GetBytes(((UInt16)((string)info.GetValue(this)).Length)));
                        }

                        bytes.AddRange(ASCIIEncoding.ASCII.GetBytes((string)info.GetValue(this)));
                    }
                    else
                    {
                        bytes.AddRange((byte[])typeof(BitConverter).GetMethod("GetBytes", new Type[] { info.FieldType }).Invoke(null, new object[] { info.GetValue(this) }));
                    }
              }

                return bytes.ToArray();
            }
        }








Now here's 2 sample messages we define (this is how a World Server registers with the Match / 'Master server')

        public class WSTOMSRegister : GameMessage
        {
            public const ID ID = 0;
            public int WorldServerID;
            public string Password;

            public WSTOMSRegister(byte[] buffer) : base(buffer){}
            public WSTOMSRegister(int worldServerID, string password) { this.WorldServerID = worldServerID;
                                                                        this.Password = password; }
        }

        public class MSTOWSRegisterACK : GameMessage 
        {
            public MSTOWSRegisterACK(byte[] buffer) : base(buffer) { }
            public MSTOWSRegisterACK(bool result) {Result = result;}

            public const ID ID = 1;
            public bool Result;
        }








Now this is how I send the message on the World Server, and process the ACK

          matchServer.SendMessage(PacketHelper.GetNetMessage(new WSTOMSRegister(worldServerID,"omgwtfbbq")),NetChannel.Sequenced1);

            NetMessage ack = null;

            while ((ack = matchServer.ReadMessage()) == null)
            {
                matchServer.Heartbeat();
                Thread.Sleep(0);
            }

            //get the ACK from the server
            MSTOWSRegisterACK response = new MSTOWSRegisterACK(ack.ReadAllBytes());//PacketSerializer<MSTOWSRegisterACK>.DeSerialize(ack.ReadBytes());

            this.isRegisteredWithMatchServer = response.Result;
            this.running = response.Result;




This is how I process it on the Match Server

            NetMessage msg = server.ReadMessage();

            if (msg != null)
            {
                ID id = msg.ReadInt(); //TODO READ BYTES INTO A TEMPLATE FUNCTION TO CONVERT ID

                switch (id)
                {
                    //a world server wants to register with us
                    case WSTOMSRegister.ID:
                        {
                            WSTOMSRegister regRequest = new WSTOMSRegister(msg.ReadAllBytes());
                            bool valid = regRequest.Password == "omgwtfbbq";
                 
                            server.SendMessage(PacketHelper.GetNetMessage(new MSTOWSRegisterACK(valid)), msg.Sender, NetChannel.Sequenced1);

                            if (valid)
                            {
                                WorldServer ws = new WorldServer(regRequest.WorldServerID);
                                WorldServerNode wsn = new WorldServerNode(msg.Sender,ws);// ws);
                                worldServerNodes.Add(wsn);
                            }
                            break;
                        }
                }
            }








Thanks for any suggestions! [Edited by - graveyard filla on February 17, 2007 4:24:07 PM]
FTA, my 2D futuristic action MMORPG
Advertisement
Quote: Is the serialization going to choke and burn under much stress?


The throughput of your network connection is, what, 180 kilobyte per second? For that to stress your computer, each byte you send would have to require 1,800,000,000/180,000 or about 10,000 instructions, assuming a CPU speed of 1.8 GHz. Chances are, you'll be OK.

However, C# built-in serialization is not the most space efficient, as it can't assume the same rigid knowledge that you can get form a custom game protocol. You could, for example, use System.Reflection.Emit to generate custom serializers for each protocol entity to get around that. However, I wouldn't start worrying about that until I got to the point where I noticed degradation from the current method.
enum Bool { True, False, FileNotFound };
Overall this is a decent serializer/deserializer, you do have a few problems though:
1. Don't compare the field name against "String", instead use typeof(string).
2. You are only enforcing a weak ordering upon your fields. The standard does not specify (and it has changed in different versions of the CLR) in what order the fields will be returned. As such your code can break between systems due to a different field ordering being returned. I would suggest changing to a strict ordering.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.

Thanks for the reply

I'm confused though, the reason I'm sorting it is because the order is not guaranteed... so what is wrong with my sorting technique exactly? I understand it's a bit awkward, but I need to keep the ID at the front for parsing inbound messages, and I want to keep all strings in the back so I can avoid writing the size for the last string. In the case where there is more then 1 string, it will write the size for the ones not on the end. It basically saves a few bytes off many messages which simply means I can handle more players for a given bandwidth.
FTA, my 2D futuristic action MMORPG

This topic is closed to new replies.

Advertisement