使用protobuf-net反序列化未知类型

时间:2009-03-23 21:40:58

标签: c# serialization protocol-buffers 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);

我在包含消息类型ID的序列化字节之前传递一个标头,我可以在一个巨大的switch语句中使用它来返回预期的sublcass Type。使用下面的块,我收到错误:System.Reflection.TargetInvocationException ---&gt; 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; }
    }
}

<小时/> 修正了使用Marc Gravell的建议。我从readonly属性中删除了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 个答案:

答案 0 :(得分:9)

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

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

答案 1 :(得分:7)

首先;对于网络使用,有SerializeWithLengthPrefixDeserializeWithLengthPrefix处理长度(可选择带标记)。 MakeGenericMethod乍一看看起来不错;这实际上与我为实现RPC堆栈所做的工作的挂起提交密切相关:待处理代码has an override of DeserializeWithLengthPrefix(基本上)Func<int,Type>,将标记解析为一种类型,可以更轻松地动态反序列化意外数据。

如果消息类型实际上与BaseMessageBeginRequest之间的继承有关,那么您不需要这个;它总是进入层次结构中最顶层的合同类型并且向下运行(由于一些电线细节)。

另外 - 我没有机会测试它,但以下可能会让它感到不安:

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

标记为序列化,但没有设置值的机制。也许这就是问题?尝试删除此处的[ProtoMember],因为我认为这不是很有用 - 它(就序列化而言)很大程度上是[ProtoInclude(...)]标记的重复。

答案 2 :(得分:3)

处理此问题的另一种方法是使用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);
        }
    }
}

后一种方法将在累加器缓冲区上“尝试”,直到有足够的数据。一旦满足大小要求,就可以反序列化消息。