Десериализовать неизвестный тип с помощью protobuf-net - PullRequest
21 голосов
/ 24 марта 2009

У меня есть 2 сетевых приложения, которые должны отправлять друг другу сериализованные сообщения protobuf-net. Я могу сериализовать объекты и отправить их, однако, Я не могу понять, как десериализовать полученные байты .

Я попытался десериализовать с этим, и это не удалось с NullReferenceException.

// Where "ms" is a memorystream containing the serialized
// byte array from the network.
Messages.BaseMessage message =
  ProtoBuf.Serializer.Deserialize<Messages.BaseMessage>(ms);

Я передаю заголовок перед сериализованными байтами, который содержит идентификатор типа сообщения, который я могу использовать в гигантском операторе switch для возврата ожидаемого типа подкласса. С блоком ниже я получаю сообщение об ошибке: System.Reflection.TargetInvocationException ---> System.NullReferenceException.

//Where "ms" is a memorystream and "messageType" is a
//Uint16.
Type t = Messages.Helper.GetMessageType(messageType);
System.Reflection.MethodInfo method =
  typeof(ProtoBuf.Serializer).GetMethod("Deserialize").MakeGenericMethod(t);
message = method.Invoke(null, new object[] { ms }) as Messages.BaseMessage;

Вот функция, которую я использую для отправки сообщения по сети:

internal void Send(Messages.BaseMessage message){
  using (System.IO.MemoryStream ms = new System.IO.MemoryStream()){
    ProtoBuf.Serializer.Serialize(ms, message);
    byte[] messageTypeAndLength = new byte[4];
    Buffer.BlockCopy(BitConverter.GetBytes(message.messageType), 0, messageTypeAndLength, 0, 2);
    Buffer.BlockCopy(BitConverter.GetBytes((UInt16)ms.Length), 0, messageTypeAndLength, 2, 2);
    this.networkStream.Write(messageTypeAndLength);
    this.networkStream.Write(ms.ToArray());
  }
}

Это тот класс, с базовым классом, я сериализую:

[Serializable,
ProtoContract,
ProtoInclude(50, typeof(BeginRequest))]
abstract internal class BaseMessage
{
  [ProtoMember(1)]
  abstract public UInt16 messageType { get; }
}

[Serializable,
ProtoContract]
internal class BeginRequest : BaseMessage
{
    [ProtoMember(1)]
    public override UInt16 messageType
    {
        get { return 1; }
    }
}


Исправлено с использованием предложения Марка Гравелла. Я удалил атрибут ProtoMember из свойств только для чтения. Также переключился на использование SerializeWithLengthPrefix. Вот что у меня сейчас:
[Serializable,
ProtoContract,
ProtoInclude(50, typeof(BeginRequest))]
abstract internal class BaseMessage
{
  abstract public UInt16 messageType { get; }
}

[Serializable,
ProtoContract]
internal class BeginRequest : BaseMessage
{
    public override UInt16 messageType
    {
        get { return 1; }
    }
}

Для получения объекта:

//where "this.Ssl" is an SslStream.
BaseMessage message =
  ProtoBuf.Serializer.DeserializeWithLengthPrefix<BaseMessage>(
    this.Ssl, ProtoBuf.PrefixStyle.Base128);

Чтобы отправить объект:

//where "this.Ssl" is an SslStream and "message" can be anything that
// inherits from BaseMessage.
ProtoBuf.Serializer.SerializeWithLengthPrefix<BaseMessage>(
  this.Ssl, message, ProtoBuf.PrefixStyle.Base128);

Ответы [ 3 ]

9 голосов
/ 15 июня 2012
Serializer.NonGeneric.Deserialize(Type, Stream); //Thanks,  Marc.

или

RuntimeTypeModel.Default.Deserialize(Stream, null, Type); 
7 голосов
/ 24 марта 2009

Во-первых; для использования в сети есть SerializeWithLengthPrefix и DeserializeWithLengthPrefix, которые обрабатывают длину для вас (опционально с тегом). MakeGenericMethod выглядит нормально на первый взгляд; и это на самом деле очень тесно связано с ожидающей фиксацией работы, которую я выполнял для реализации стека RPC: ожидающий код has an override of DeserializeWithLengthPrefix, который принимает (по существу) Func<int,Type>, чтобы разрешить тег для типа, чтобы сделать легче десериализовать неожиданные данные на лету.

Если тип сообщения действительно относится к наследованию между BaseMessage и BeginRequest, то вам это не нужно; он всегда переходит к самому верхнему типу контракта в иерархии и движется вниз (из-за некоторых подробностей проводов).

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

[ProtoMember(1)]
public override UInt16 messageType
{
    get { return 1; }
}

Он помечен для сериализации, но не имеет механизма для установки значения. Может быть, это проблема? Попробуйте удалить [ProtoMember] здесь, так как я не делаю, это полезно - это (что касается сериализации), в основном, дубликат маркера [ProtoInclude(...)].

3 голосов
/ 05 мая 2009

Другой способ справиться с этим - использовать protobuf-net для «тяжелой работы», но использовать собственный заголовок сообщения. Проблема с обработкой сетевых сообщений заключается в том, что они могут быть разбиты через границы. Это обычно требует использования буфера для накопления чтений. Если вы используете свой собственный заголовок, вы можете быть уверены, что сообщение находится там полностью, прежде чем передать его в protobuf-net.

Как пример:

отправить

using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
{
    MyMessage message = new MyMessage();
    ProtoBuf.Serializer.Serialize<BaseMessage>(ms, message);
    byte[] buffer = ms.ToArray();

    int messageType = (int)MessageType.MyMessage;
    _socket.Send(BitConverter.GetBytes(messageType));
    _socket.Send(BitConverter.GetBytes(buffer.Length));
    _socket.Send(buffer);
}

Для получения

protected bool EvaluateBuffer(byte[] buffer, int length)
{
    if (length < 8)
    {
        return false;
    }

    MessageType messageType = (MessageType)BitConverter.ToInt32(buffer, 0);
    int size = BitConverter.ToInt32(buffer, 4);
    if (length < size + 8)
    {
        return false;
    }

    using (MemoryStream memoryStream = new MemoryStream(buffer))
    {
        memoryStream.Seek(8, SeekOrigin.Begin);
        if (messageType == MessageType.MyMessage)
        {
            MyMessage message = 
                ProtoBuf.Serializer.Deserialize<MyMessage>(memoryStream);
        }
    }
}

Последний метод будет "опробован" в буфере аккумулятора, пока не будет достаточно данных. Как только требование к размеру выполнено, сообщение может быть десериализовано.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...