Десериализовать неизвестный тип с помощью protobuf-net

у меня есть 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.Отображение.TargetInvocationException ---> Система.Исключение 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 из свойств readonly. Также включен к использованию 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 ответов


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

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

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

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

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


Serializer.NonGeneric.Deserialize(Type, Stream); //Thanks,  Marc.

или

RuntimeTypeModel.Default.Deserialize(Stream, null, Type); 

другой способ справиться с этим-использовать 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);
}

к r