使用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(ms); 

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

这是我用来通过网络发送消息的function:

 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( this.Ssl, ProtoBuf.PrefixStyle.Base128); 

发送对象:

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

第一; 对于网络使用,有SerializeWithLengthPrefixDeserializeWithLengthPrefix为您处理长度(可选择带标签)。 MakeGenericMethod乍一看看起来MakeGenericMethod ; 这实际上与我为实现RPC堆栈所做的工作的挂起提交非常接近:挂起的代码has an override of DeserializeWithLengthPrefix ,它基本上采用了一个Func来解析一个标签到一种类型,以便更容易动态地反序列化意外数据。

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

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

 [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(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(memoryStream); } } } 

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