使用 protobuf-net 反序列化未知类型
-
21-08-2019 - |
题
我有 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.TargetInitationException ---> 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);
解决方案
第一的;对于网络使用,有 SerializeWithLengthPrefix
和 DeserializeWithLengthPrefix
适合您的手柄长度(可选带有标签)。这 MakeGenericMethod
乍一看还不错;这实际上与我为实现 RPC 堆栈所做的工作的待提交密切相关:待处理的代码 has an override of DeserializeWithLengthPrefix
这需要(本质上)一个 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);
}
受到
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);
}
}
}
后一种方法将在累加器缓冲区上“尝试”,直到有足够的数据。一旦满足大小要求,就可以对消息进行反序列化。