【问题标题】:Deserialize unknown type with protobuf-net使用 protobuf-net 反序列化未知类型
【发布时间】:2010-10-15 02:11:28
【问题描述】:

我有 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 语句中使用它来返回预期的子类类型。使用下面的块,我收到错误: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; }
    }
}


已修复,使用 Marc Gravell 的建议。我从只读属性中删除了 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);

【问题讨论】:

  • 我忘了说,我在 Windows 上的 .NET 3.5 中序列化并在 Mono 2.2 中反序列化,并且在每个平台上使用适当的 protobuf-net dll。
  • 我会在大约半小时后回来阅读这篇文章并发布答案......现在必须运行,抱歉。顺便说一句 - 下一个版本内置了非通用包装器 - 目前仍在我的笔记本电脑上。
  • btw - 我正在合并我的本地副本,因此我可以提交更改以使这更容易。我有一个突出的测试失败,但它涵盖了新代码,所以如果有帮助,我很乐意提交它(标记为忽略)。
  • 重新修复 - 我会进行一些更好的处理,以使这一点在未来更加明显......
  • 我很欣赏提交代码的提议,但我使用泛型和基类让它工作。如果您认为它会更快或更少的代码行,我很乐意尝试一下。

标签: c# serialization protocol-buffers protobuf-net


【解决方案1】:
Serializer.NonGeneric.Deserialize(Type, Stream); //Thanks,  Marc.

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

【讨论】:

  • 确实如此,或者使用 v1 API(在 v2 中仍然有效)Serializer.NonGeneric.Deserialize(...)(采用 Type 参数,而不是 &lt;T&gt; 泛型类型参数)
  • @MarcGravell,谢谢我没有注意到 NonGeneric 属性-)
【解决方案2】:

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

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

【讨论】:

  • 如果 protobuf-net 使用传入的 Type 提供重载来反序列化将非常有益,例如ProtoBuf.Serializer.Deserialize(Type objectType, memoryStream);有谁知道这是否可能?如果您有许多想要反序列化的未知类型,这将避免混乱的 switch 语句
  • RuntimeTypeModel.Default.Deserialize(Stream, null, Type);
【解决方案3】:

首先;对于网络使用,有 SerializeWithLengthPrefixDeserializeWithLengthPrefix 为您处理长度(可选地带有标签)。 MakeGenericMethod 乍一看还可以;这实际上与我为实现 RPC 堆栈所做的工作的待处理提交密切相关:待处理代码 has an override of DeserializeWithLengthPrefix 需要(基本上)Func&lt;int,Type&gt;,将标记解析为要制作的类型动态反序列化意外数据更容易。

如果消息类型实际上与BaseMessageBeginRequest之间的继承有关,那么你不需要这个;它总是转到层次结构中最顶层的合约类型并向下工作(由于一些电汇细节)。

另外 - 我还没有机会对其进行测试,但以下内容可能会让它感到不安:

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

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

【讨论】:

  • 我会试一试,然后对结果发表评论。感谢您的回复!
  • 切换到 SerializeWithLengthPrefix 缩短了我的代码。 :) 从只读属性中删除 ProtoMember 属性解决了这个问题。谢谢!!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多