Я внедряю клиент-серверное приложение и изучаю различные способы сериализации и передачи данных. Я начал работать с сериализаторами Xml, которые работали довольно хорошо, но генерировали данные медленно и создавали большие объекты, особенно когда их нужно отправлять по сети. Поэтому я начал изучать Protobuf и protobuf-net.
Моя проблема заключается в том, что protobuf не отправляет информацию о типе с ним. С сериализаторами Xml я смог создать оболочку, которая будет отправлять и получать любые различные (сериализуемые) объекты по одному и тому же потоку, поскольку объект, сериализованный в Xml, содержит имя типа объекта.
ObjectSocket socket = new ObjectSocket();
socket.AddTypeHandler(typeof(string)); // Tells the socket the types
socket.AddTypeHandler(typeof(int)); // of objects we will want
socket.AddTypeHandler(typeof(bool)); // to send and receive.
socket.AddTypeHandler(typeof(Person)); // When it gets data, it looks for
socket.AddTypeHandler(typeof(Address)); // these types in the Xml, then uses
// the appropriate serializer.
socket.Connect(_host, _port);
socket.Send(new Person() { ... });
socket.Send(new Address() { ... });
...
Object o = socket.Read();
Type oType = o.GetType();
if (oType == typeof(Person))
HandlePerson(o as Person);
else if (oType == typeof(Address))
HandleAddress(o as Address);
...
Я рассмотрел несколько вариантов решения этой проблемы, включая создание основного класса типа «состояние», который является единственным типом объекта, отправляемого через мой сокет. Это отходит от функциональности, с которой я работал в сериализаторах Xml, поэтому я бы хотел избежать этого направления.
Второй вариант - обернуть объекты protobuf в некоторый тип оболочки, который определяет тип объекта. (Эта оболочка также будет содержать такую информацию, как идентификатор пакета и место назначения.) Глупо использовать protobuf-net для сериализации объекта, а затем вставлять этот поток между тегами Xml, но я рассмотрел это. Есть ли простой способ получить эту функциональность из protobuf или protobuf-net?
Я придумал третье решение и разместил его ниже, но если у вас есть лучшее, пожалуйста, опубликуйте его тоже!
Информация об ошибке в полях (используется System.String
):
хеширование:
protected static int ComputeTypeField(Type type) // System.String
{
byte[] data = ASCIIEncoding.ASCII.GetBytes(type.FullName);
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
return Math.Abs(BitConverter.ToInt32(md5.ComputeHash(data), 0));
}
Сериализация:
using (MemoryStream stream = new MemoryStream())
{
Serializer.NonGeneric.SerializeWithLengthPrefix
(stream, o, PrefixStyle.Base128, field); // field = 600542181
byte[] data = stream.ToArray();
_pipe.Write(data, 0, data.Length);
}
Deserializaion:
using (MemoryStream stream = new MemoryStream(_buffer.Peek()))
{
lock (_mapLock)
{
success = Serializer.NonGeneric.TryDeserializeWithLengthPrefix
(stream, PrefixStyle.Base128, field => _mappings[field], out o);
}
if (success)
_buffer.Clear((int)stream.Position);
else
{
int len;
if (Serializer.TryReadLengthPrefix(stream, PrefixStyle.Base128, out len))
_buffer.Clear(len);
}
}
field => _mappings[field]
бросает KeyNotFoundException
при поиске 63671269
.
Если я заменю ToInt32
на ToInt16
в хэш-функции, значение поля будет установлено на 29723
, и оно будет работать. Это также работает, если я явно определяю поле System.String
для 1
. Явное определение поля для 600542181
имеет тот же эффект, что и использование хеш-функции для его определения. Значение сериализуемой строки не меняет результат.