C# 使用 protobuf-net 反序列化未知类型
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/675349/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Deserialize unknown type with protobuf-net
提问by Nick VanderPyle
I have 2 networked apps that should send serialized protobuf-net messages to each other. I can serialize the objects and send them, however, I cannot figure out how to deserialize the received bytes.
我有 2 个联网应用程序,它们应该相互发送序列化的 protobuf-net 消息。我可以序列化对象并发送它们,但是,我无法弄清楚如何反序列化接收到的字节。
I tried to deserialize with this and it failed with a NullReferenceException.
我试图用它反序列化,但它因 NullReferenceException 而失败。
// Where "ms" is a memorystream containing the serialized
// byte array from the network.
Messages.BaseMessage message =
ProtoBuf.Serializer.Deserialize<Messages.BaseMessage>(ms);
I am passing a header before the serialized bytes that contains message type ID, which I can use in a giant switch statement to return the expected sublcass Type. With the block below, I receive the error: System.Reflection.TargetInvocationException ---> System.NullReferenceException.
我在包含消息类型 ID 的序列化字节之前传递一个标头,我可以在一个巨大的 switch 语句中使用它来返回预期的 sublcas 类型。使用下面的块,我收到错误: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;
Here's the function I use to send a message over the network:
这是我用来通过网络发送消息的函数:
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());
}
}
This the class, with base class, I'm serializing:
这是带有基类的类,我正在序列化:
[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; }
}
}
Fixed修复了使用 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; }
}
}
To receive an object:
接收对象:
//where "this.Ssl" is an SslStream.
BaseMessage message =
ProtoBuf.Serializer.DeserializeWithLengthPrefix<BaseMessage>(
this.Ssl, ProtoBuf.PrefixStyle.Base128);
To send an object:
发送对象:
//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);
采纳答案by Marc Gravell
First; for network usage, there is SerializeWithLengthPrefix
and DeserializeWithLengthPrefix
which handle length for you (optionally with a tag). The MakeGenericMethod
looks OK at first glance; and this actually ties in very closely to the pending commit of the work I've been doing to implement an RPC stack: the pending code has an override of DeserializeWithLengthPrefix
that takes (essentially) a Func<int,Type>
, to resolve a tag to a type to make it easier to deserialize unexpected data on the fly.
第一的; 网络使用,存在SerializeWithLengthPrefix
和DeserializeWithLengthPrefix
其手柄长度为你(任选地与标签)。在MakeGenericMethod
乍看上去OK; 这实际上与我为实现 RPC 堆栈所做的工作的挂起提交密切相关:挂起代码has an override of DeserializeWithLengthPrefix
(本质上)需要 a Func<int,Type>
, 将标签解析为一种类型,以便更容易反序列化意外数据在飞行中。
If the message type actually relates to the inheritance between BaseMessage
and BeginRequest
, then you don't need this; it always goes to the top-most contract type in the hierarchy and works its way down (due to some wire details).
如果消息类型实际上涉及BaseMessage
和之间的继承BeginRequest
,那么你不需要这个;它总是转到层次结构中最顶层的合同类型并向下工作(由于某些线路细节)。
Also - I haven't had chance to test it, but the following might be upsetting it:
另外 - 我还没有机会测试它,但以下内容可能会让它感到不安:
[ProtoMember(1)]
public override UInt16 messageType
{
get { return 1; }
}
It is marked for serialization, but has no mechanism for setting the value. Maybe this is the issue? Try removing the [ProtoMember]
here, since I don't this is useful - it is (as far as serialization is concerned), largely a duplicate of the [ProtoInclude(...)]
marker.
它被标记为序列化,但没有设置值的机制。也许这是问题?尝试删除[ProtoMember]
此处,因为我不认为这很有用 - 它(就序列化而言)主要是[ProtoInclude(...)]
标记的副本。
回答by Todd
Another way to handle this is to use protobuf-net for the "heavy lifting", but to use your own message header. The problem with processing network messages is that they can be broken across boundaries. This typically requires using a buffer to accumulate reads. If you use your own header, you can be sure that the message is there in its entirety before handing it off to protobuf-net.
处理此问题的另一种方法是使用 protobuf-net 进行“繁重工作”,但使用您自己的消息头。处理网络消息的问题在于它们可以跨越边界。这通常需要使用缓冲区来累积读取。如果您使用自己的标头,则可以在将其交给 protobuf-net 之前确保该消息完整存在。
As an example:
举个例子:
To send
发送
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);
}
To receive
接受
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);
}
}
}
The latter method would be "tried" on an accumulator buffer until there was enough data. Once the size requirement is met, the message can be deserialized.
后一种方法将在累加器缓冲区上“尝试”,直到有足够的数据。一旦满足大小要求,就可以对消息进行反序列化。
回答by Alex Burtsev
Serializer.NonGeneric.Deserialize(Type, Stream); //Thanks, Marc.
or
或者
RuntimeTypeModel.Default.Deserialize(Stream, null, Type);