PDA

Просмотр полной версии : [Статья] Кастомная бинарная сериализация


Yukikaze
26.06.2012, 17:29
Пишу сервер войс чата(может даже по нему статью напишу), ну думаю значит, чего велосипед изобретать, воспользуюсь сериализацией. Описал класс [Serializable]
public class Packet
{
public Command Command { get; set; } //энумератор наследованный от byte
public string Sender { get; set; }
public byte[] Data { get; set; }

public Packet(Command cmd, string sender, byte[] data)
{
this.Command = cmd;
this.Data = data;
this.Sender = sender;
}
}, 3 поля, ничего лишнего. Затем для удобства описал статичный, универсальный класс public static class SerializeHelper
{
public static T Deserialize<T>(byte[] data)
{
BinaryFormatter serializer = new BinaryFormatter();
return (T)serializer.Deserialize(new MemoryStream(data));
}

public static byte[] Serialize<T>(T obj)
{
BinaryFormatter serializer = new BinaryFormatter();
MemoryStream stream = new MemoryStream();
serializer.Serialize(stream, obj);
return stream.ToArray();
}
} с двумя методами которые сериализируют объект в массив байт ну и в обратном направлении.
Каково было мое удивление когда я увидел, что на выходе я получаю массив в 257+ байт, откуда? Посмотрел в отладчике, а там пол массива нулей, я так и присел на месте, что же это за клиент-сервер если он пустыми пакетами в 250+ байт разбрасывается?
Думал не долго, первое, что пришло на ум, это "научить" сериализатор "правильной" сериализации, как говориться с преферансом и куртизанками. Реализовал в своем классе интерфейс ISerializable, [Serializable]
public class Packet : ISerializable
{
public Command Command { get; set; }
public string Sender { get; set; }
public byte[] Data { get; set; }

public Packet(Command cmd, string sender, byte[] data)
{
this.Command = cmd;
this.Data = data;
this.Sender = sender;
}

public Packet(SerializationInfo info, StreamingContext context)
{
// Конструктор для десериализации
}

public void GetObjectData(SerializationInfo info, StreamingContext context)
{
// Именно тут происходит сериализация
}
} принял новый вид, описал метод GetObjectData, ничего нового не изобретал.
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
MemoryStream stream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(stream);
writer.Write((byte)this.Command); // т.к. энумератор я наследовал от байта то и привести его к байту тоже не проблема
writer.Write(this.Sender);
writer.Write((short)this.Data.Length);
writer.Write(this.Data);
info.AddValue("X", stream.ToArray(), typeof(byte[]));
}
Запись производиться в поток "как есть", без всяких смещений, все данные записываются по очереди, сначала 1 байт команды, затем строка, а так как она заканчивает нуль символом (\0) то и ее длину передавать не нужно, затем идет длинна массива Data, т.к. массивы больше 65536 байт я передавать не собираюсь привел длину к Int16, то есть short, и сэкономил на этом 2 байта :d, ну а дальше уже идет сам массив. В последней строке метода происходит интересное действие, мы передаем сериализатору первым параметром "имя" передаваемых во втором параметре данных и в третьем параметре говорим, что тип переданных данных это массив байт.
Затем идет конструктор для сериализации, там происходит все по аналогии, только в обратную сторону
public Packet(SerializationInfo info, StreamingContext context)
{
byte[] byteArray = (byte[])info.GetValue("X", typeof(byte[]));
MemoryStream stream = new MemoryStream(byteArray);
BinaryReader reader = new BinaryReader(stream);
this.Command = (Command)reader.ReadByte();
this.Sender = reader.ReadString();
int dataLength = reader.ReadInt16();
this.Data = reader.ReadBytes(dataLength);
}

В итоге я все таки попробовал сериализировать полученное творение, передал в параметрах пустую команду(1 байт), пустую строку(еще 1 байт) и массив длинной 0 байт
Packet p = new Packet(Command.None, string.Empty, new byte[0]);
byte[] data = SerializeHelper.Serialize(p);
на выходе получил 139 байт(это минимальный размер :( ), что вполне неплохо, т.к. пакет несет в себе всю необходимую для десериализации информацию, такую как имя сборки, версию и т.д.

Пользуйтесь на здоровье, с вами как всегда был Yukikaze