Protobuf-net序列化/反序列化

10
我检查了一下,似乎无法直接将类序列化为字节数组,并使用Marc Gravell的protobuf-net实现从字节数组反序列化。编辑:我改变了问题并提供了代码,因为不必通过流序列化为byte[]的原始问题显然是微不足道的。请原谅我。更新后的问题:有没有办法不处理泛型,而是通过反射推断出“MessageBody”属性的类型,当它通过构造函数传递时?我假设我不能序列化对象类型,对吗?当前的解决方案看起来非常繁琐,因为每次我实例化一个新消息时都需要传递MessageBody的类型。有更简洁的解决方案吗?我想到了以下方法:
class Program
{
    static void Main(string[] args)
    {
        Message<string> msg = new Message<string>("Producer", "Consumer", "Test Message");

        byte[] byteArray = msg.Serialize();
        Message<string> message = Message<string>.Deserialize(byteArray);

        Console.WriteLine("Output");
        Console.WriteLine(message.From);
        Console.WriteLine(message.To);
        Console.WriteLine(message.MessageBody);

        Console.ReadLine();

    }
}

[ProtoContract]
public class Message<T>
{
    [ProtoMember(1)]
    public string From { get; private set; }
    [ProtoMember(2)]
    public string To { get; private set; }
    [ProtoMember(3)]
    public T MessageBody { get; private set; }

    public Message()
    {

    }

    public Message(string from, string to, T messageBody)
    {
        this.From = from;
        this.To = to;
        this.MessageBody = messageBody;
    }

    public byte[] Serialize()
    {
        byte[] msgOut;

        using (var stream = new MemoryStream())
        {
            Serializer.Serialize(stream, this);
            msgOut = stream.GetBuffer();
        }

        return msgOut;
    }

    public static Message<T> Deserialize(byte[] message)
    {
        Message<T> msgOut;

        using (var stream = new MemoryStream(message))
        {
            msgOut = Serializer.Deserialize<Message<T>>(stream);
        }

        return msgOut;
    }   
}

我想说的是,类似于以下的内容:

创建新消息:

Message newMsg = new Message("生产者", "消费者", Foo); byte[] byteArray = newMsg.Serialize();

然后使用以下代码反序列化该消息:

Message msg = Message.Deserialize(byteArray);

(其中Deserialize是一个静态方法,并且它总是将消息反序列化为类型为Message的对象,并且只需要知道如何反序列化消息体的类型)。


Protobuf.net 不是开源的吗? - Mitch Wheat
这是可以做到的,但我不想调整源代码,因为我喜欢保持跟上新版本而无需进行后续调整,因为该库只是更大项目的一个非常小的组成部分。 - Matt
一个MemoryStream只是一个字节数组的伪装,使用它有什么问题吗? - João Angelo
我知道,但我想知道是否有我可能忽略的函数重载。 - Matt
如果你只想序列化类,那么只需在类上方使用 [Serializable]。它的效果非常好,但是它不能与 ProtoContract 一起使用。只能使用(由)ProtoContract 或 Serializable 功能。 - Minwoo Yu
3个回答

10

这里有几个不同的问题,我会回答我能看到的:如果我漏掉了什么,请告诉我。

首先,如注释所述,MemoryStream是获得byte[]数组的最常见方式。这与大多数序列化器一致 - 例如,XmlSerializer、BinaryFormatter和DataContractSerializer也没有“作为byte[]重载”,但会接受MemoryStream。

泛型:您不需要使用泛型; v1具有Serializer.NonGeneric,它将其封装在内部。在v2中,“核心”是非泛型的,并且可以通过RuntimeTypeModel.Default进行访问;当然,Serializer和Serializer.NonGeneric仍然可以工作。

对于必须包含类型的问题:是的,protobuf规范假定接收者知道他们正在接收什么类型的数据。一个简单的选择是使用一个简单的包装对象作为“根”对象,具有多个用于数据的类型化属性(其中只有一个非空)。另一个选项可能来自于通过ProtoInclude内置继承支持(注意:作为实现细节,这两种方法是相同的)。

在您的特定示例中,考虑以下情况:

[ProtoContract]
[ProtoInclude(1, typeof(Message<Foo>))]
.... More as needed
[ProtoInclude(8, typeof(Message<Bar>))]
public abstract class Message
{   }
[ProtoContract]
public class Message<T> : Message
{
    ...
}

然后只需使用<Message>进行序列化 - API会自动创建正确的类型。

在最新的构建版本中,还有一个DynamicType选项,它会为您包含类型数据,例如:

[ProtoContract]
public class MyRoot {
    [ProtoMember(1, DynamicType=true)]
    public object Value { get; set; }
}

这将适用于任何持有合同类型实例的值(但不适用于原始数据类型,最好不涉及继承)。


Marc,感谢您的评论。您最后的建议看起来正是我所需要的。让我试着操作一下,然后再回复您。 - Matt
@Freddy 我个人会建议使用ProtoInclude,但是无论怎样都可以…… - Marc Gravell
1
看了两个...非常感谢您的建议。顺便说一句,Protobuf-net是一个很棒的库。 - Matt
Marc,我运行了性能测试,认为DynamicType解决方案非常好。我使用Protobuf-net对不需要低延迟的控制消息进行序列化/反序列化。我还运行另一个高吞吐量/低延迟的消息流,每秒处理超过1000万条消息,但基于自己的原始类型自定义序列化例程将其转换为原始字节数组,因为这被证明更快。你的解决方案正是我一直在寻找的,非常感谢你的帮助,特别是在周末。 - Matt
@Freddy protobuf-net的设计与XmlSerializer和DataContractSerializer类似,主要用于“我知道我期望什么”的情况。如果其他方法可行,那也没问题,但这是其主要应用场景。 - Marc Gravell
显示剩余4条评论

5

OP发布的代码对我来说并不完全有效,以下是稍微改进一下的代码,采纳了Marc Gravell的建议。继承自Message是必需的,以防止“不允许循环继承”,并且如下面的代码注释所述,GetBuffer也无法正常工作。

希望能帮助别人,我花了好几个小时才让它全部运行起来...



      [ProtoContract]
      public abstract class Message
      {
        public byte[] Serialize()
        {
          byte[] result;
          using (var stream = new MemoryStream())
          {
            Serializer.Serialize(stream, this);
            result = stream.ToArray(); //GetBuffer was giving me a Protobuf.ProtoException of "Invalid field in source data: 0" when deserializing
          }
          return result;
        }
      }

      [ProtoContract]
      public class Message : Message
      {
        [ProtoMember(1)]
        public string From { get; private set; }
        [ProtoMember(2)]
        public string To { get; private set; }
        [ProtoMember(3)]
        public T MessageBody { get; private set; }

        public Message()
        { }

        public Message(string from, string to, T messageBody)
        {
          this.From = from;
          this.To = to;
          this.MessageBody = messageBody;
        }

        public static Message Deserialize(byte[] message)
        {
          Message result;
          using (var stream = new MemoryStream(message))
          {
            result = Serializer.Deserialize>(stream);
          }
          return result;
        }
      }


1
错误是一个标记问题:Serializer.Deserialize<Message<T>>(stream); - 9swampy

1

我还发现,如果使用 .PROTO 文件(.cs 由 Google.Protobuf 和 Google.Protobuf.Tools 包编译/生成),则在序列化器中使用 record.WriteTo 函数,在反序列化器中使用静态 Parser.ParseFrom 函数。

using Confluent.Kafka;
using System;
using System.IO;
using CodedOutputStream = Google.Protobuf.CodedOutputStream;
using SerializationContext = Confluent.Kafka.SerializationContext;

public class KafkaSerializer: ISerializer<Message>
{
    public byte[] Serialize(Message record, SerializationContext context)
    {
        using (var stream = new MemoryStream())
        {
            using (var codedStream = new CodedOutputStream(stream))
            {
                record.WriteTo(codedStream);
                codedStream.Flush();
                return stream.ToArray();
            }
        }
    }
}

public class KafkaDeserializer : IDeserializer<Message>
{
    public Message Deserialize(ReadOnlySpan<byte> data, bool isNull, SerializationContext context)
    {
        var msg = Message.Parser.ParseFrom(data);
        return msg;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接