使用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);
第一; 对于网络使用,有SerializeWithLengthPrefix
和DeserializeWithLengthPrefix
为您处理长度(可选择带标签)。 MakeGenericMethod
乍一看看起来MakeGenericMethod
; 这实际上与我为实现RPC堆栈所做的工作的挂起提交非常接近:挂起的代码has an override of DeserializeWithLengthPrefix
,它基本上采用了一个Func
来解析一个标签到一种类型,以便更容易动态地反序列化意外数据。
如果消息类型实际上与BaseMessage
和BeginRequest
之间的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); } } }
后一种方法将在累加器缓冲区上“尝试”,直到有足够的数据。 一旦满足大小要求,就可以反序列化消息。