Как я могу отправить несколько типов объектов через Protobuf?

Я реализовываю клиент-серверное приложение и изучаю различные способы сериализировать и передать данные. Я начал работать с Сериализаторами Xml, которые работали скорее хорошо, но генерируйте данные медленно и делайте большие объекты, особенно когда они должны быть отправлены по сети. Таким образом, я начал изучать Protobuf и protobuf-сеть.

Моя проблема заключается в том, что 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-сеть, чтобы сериализировать объект, затем засунуть тот поток между тегами XML, но я рассмотрел это. Существует ли простой способ вытащить эту функциональность из protobuf или protobuf-сети?


Я предложил третье решение и отправил его ниже, но если у Вас есть лучший, отправьте его также!


Информация о поле ограничивает ошибку (использование 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] броски a KeyNotFoundException при поиске 63671269.

Если я заменяю ToInt32 с ToInt16 в хеш-функции значение поля установлено к 29723 и это работает. Это также работает, если я явно определяю System.Stringполе к 1. Явно определяя поле к 600542181 имеет тот же эффект как использование хеш-функции для определения его. Значение сериализируемой строки не изменяет результат.

8
задан dlras2 7 July 2010 в 13:17
поделиться

2 ответа

Эта функциональность фактически встроена, хотя и не очевидно.

В этом сценарии предполагается, что вы назначите уникальный номер для каждого типа сообщения. Перегрузка, которую вы используете, передает их все как «поле 1», но есть перегрузка, которая позволяет вам включать эту дополнительную информацию заголовка (хотя это все еще задача вызывающего кода, чтобы решить, как сопоставить числа с типами). Затем вы можете указать разные типы, поскольку разные поля - это поток (примечание: это работает только со стилем префикса base-128).

Мне нужно дважды проверить, но намерение состоит в том, что должно работать что-то вроде следующего:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ProtoBuf;
static class Program
{
    static void Main()
    {
        using (MemoryStream ms = new MemoryStream())
        {
            WriteNext(ms, 123);
            WriteNext(ms, new Person { Name = "Fred" });
            WriteNext(ms, "abc");

            ms.Position = 0;

            while (ReadNext(ms)) { }            
        }
    }
    // *** you need some mechanism to map types to fields
    static readonly IDictionary<int, Type> typeLookup = new Dictionary<int, Type>
    {
        {1, typeof(int)}, {2, typeof(Person)}, {3, typeof(string)}
    };
    static void WriteNext(Stream stream, object obj) {
        Type type = obj.GetType();
        int field = typeLookup.Single(pair => pair.Value == type).Key;
        Serializer.NonGeneric.SerializeWithLengthPrefix(stream, obj, PrefixStyle.Base128, field);
    }
    static bool ReadNext(Stream stream)
    {
        object obj;
        if (Serializer.NonGeneric.TryDeserializeWithLengthPrefix(stream, PrefixStyle.Base128, field => typeLookup[field], out obj))
        {
            Console.WriteLine(obj);
            return true;
        }
        return false;
    }
}
[ProtoContract] class Person {
    [ProtoMember(1)]public string Name { get; set; }
    public override string ToString() { return "Person: " + Name; }
}

Обратите внимание, что этот не в настоящее время работает в сборке v2 (поскольку "WithLengthPrefix" код неполный), но я пойду и протестирую его на v1. Если это сработает, я использую весь описанный выше сценарий для набора тестов, чтобы убедиться, что он действительно работает в v2.

Изменить:

да, он отлично работает на "v1", с выводом:

123
Person: Fred
abc
10
ответ дан 5 December 2019 в 12:55
поделиться

Я придумал другое решение, но решил поместить его в ответ, а не в вопрос, потому что так мне кажется более логичным. На мой взгляд, оно довольно уродливое, и меня предупредили, что не стоит использовать отражение, поэтому, пожалуйста, прокомментируйте его или дайте лучшие ответы, если они у вас есть. Спасибо!


class Program
{
    static void Main(string[] args)
    {
        Person person = new Person
        {
            Id = 12345,
            Name = "Fred",
            Address = new Address
            {
                Line1 = "Flat 1",
                Line2 = "The Meadows"
            }
        };
        object value;
        using (Stream stream = new MemoryStream())
        {
            Send<Person>(stream, person);
            stream.Position = 0;
            value = Read(stream);
            person = value as Person;
        }
    }

    static void Send<T>(Stream stream, T value)
    {
        Header header = new Header()
        {
            Guid = Guid.NewGuid(),
            Type = typeof(T)
        };
        Serializer.SerializeWithLengthPrefix<Header>(stream, header, PrefixStyle.Base128);
        Serializer.SerializeWithLengthPrefix<T>(stream, value, PrefixStyle.Base128);
    }

    static object Read(Stream stream)
    {

        Header header;
        header = Serializer.DeserializeWithLengthPrefix<Header>
            (stream, PrefixStyle.Base128);
        MethodInfo m = typeof(Serializer).GetMethod("DeserializeWithLengthPrefix",
            new Type[] {typeof(Stream), typeof(PrefixStyle)}).MakeGenericMethod(header.Type);
        Object value = m.Invoke(null, new object[] {stream, PrefixStyle.Base128} );
        return value;
    }
}

[ProtoContract]
class Header
{
    public Header() { }

    [ProtoMember(1, IsRequired = true)]
    public Guid Guid { get; set; }

    [ProtoIgnore]
    public Type Type { get; set; }
    [ProtoMember(2, IsRequired = true)]
    public string TypeName
    {
        get { return this.Type.FullName; }
        set { this.Type = Type.GetType(value); }
    }
}

[ProtoContract]
class Person {
    [ProtoMember(1)]
    public int Id { get; set; }
    [ProtoMember(2)]
    public string Name { get; set; }
    [ProtoMember(3)]
    public Address Address { get; set; }
}

[ProtoContract]
class Address {
    [ProtoMember(1)]
    public string Line1 { get; set; }
    [ProtoMember(2)]
    public string Line2 { get; set; }
}
3
ответ дан 5 December 2019 в 12:55
поделиться
Другие вопросы по тегам:

Похожие вопросы: