如何在Protobuf中发送多种类型的对象?

我正在实现客户端 – 服务器应用程序,并正在研究序列化和传输数据的各种方法。 我开始使用Xml Serializers,它运行得相当好,但是生成数据很慢,并且生成大对象,特别是当它们需要通过网络发送时。 所以我开始研究Protobuf和protobuf-net。

我的问题在于protobuf没有发送类型信息。 使用Xml Serializers,我能够构建一个包装器,它将在同一个流上发送和接收任何各种(可序列化)对象,因为序列化为Xml的对象包含对象的类型名称。

ObjectSocket socket = new ObjectSocket(); socket.AddTypeHandler(typeof(string)); // Tells the socket the types socket.AddTypeHandler(typeof(int)); // of objects we will want socket.AddTypeHandler(typeof(bool)); // to send and receive. socket.AddTypeHandler(typeof(Person)); // When it gets data, it looks for socket.AddTypeHandler(typeof(Address)); // these types in the Xml, then uses // the appropriate serializer. socket.Connect(_host, _port); socket.Send(new Person() { ... }); socket.Send(new Address() { ... }); ... Object o = socket.Read(); Type oType = o.GetType(); if (oType == typeof(Person)) HandlePerson(o as Person); else if (oType == typeof(Address)) HandleAddress(o as Address); ... 

我已经考虑了一些解决方案,包括创建一个主“状态”类型类,这是通过我的套接字发送的唯一类型的对象。 这远离了我用Xml Serializers开发的function,所以我想避免这个方向。

第二种选择是将protobuf对象包装在某种类型的包装器中,该包装器定义了对象的类型。 (这个包装器还包括诸如包ID和目的地之类的信息。)使用protobuf-net来序列化一个对象,然后在Xml标签之间粘贴该流似乎很愚蠢,但我已经考虑过了。 是否有一种简单的方法可以从protobuf或protobuf-net中获取此function?


我已经提出了第三个解决方案,并在下面发布,但是如果你有更好的解决方案,请发布它!


有关字段边界错误的信息(使用 System.String ):

哈希:

 protected static int ComputeTypeField(Type type) // System.String { byte[] data = ASCIIEncoding.ASCII.GetBytes(type.FullName); MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider(); return Math.Abs(BitConverter.ToInt32(md5.ComputeHash(data), 0)); } 

连载:

 using (MemoryStream stream = new MemoryStream()) { Serializer.NonGeneric.SerializeWithLengthPrefix (stream, o, PrefixStyle.Base128, field); // field = 600542181 byte[] data = stream.ToArray(); _pipe.Write(data, 0, data.Length); } 

Deserializaion:

 using (MemoryStream stream = new MemoryStream(_buffer.Peek())) { lock (_mapLock) { success = Serializer.NonGeneric.TryDeserializeWithLengthPrefix (stream, PrefixStyle.Base128, field => _mappings[field], out o); } if (success) _buffer.Clear((int)stream.Position); else { int len; if (Serializer.TryReadLengthPrefix(stream, PrefixStyle.Base128, out len)) _buffer.Clear(len); } } 

field => _mappings[field]在查找KeyNotFoundException时抛出63671269

如果我在散列函数中用ToInt16替换ToInt32 ,则字段值设置为29723并且它可以工作。 如果我将System.String的字段显式定义为1它也可以工作。 将字段显式定义为600542181与使用哈希函数定义字段具有相同的效果。 被序列化的字符串的值不会改变结果。

这个function实际上是内置的,尽管不是很明显。

在这种情况下,预计您将为每种消息类型指定一个唯一的编号。 您正在使用的重载将它们全部作为“字段1”传递,但是有一个重载允许您包含此额外的标头信息(它仍然是调用代码的工作,以决定如何将数字映射到类型)。 然后,您可以指定不同的类型,因为不同的字段是流(注意:这仅适用于base-128前缀样式)。

我需要仔细检查,但意图是以下内容应该有效:

 using System; using System.Collections.Generic; using System.IO; using System.Linq; using ProtoBuf; static class Program { static void Main() { using (MemoryStream ms = new MemoryStream()) { WriteNext(ms, 123); WriteNext(ms, new Person { Name = "Fred" }); WriteNext(ms, "abc"); ms.Position = 0; while (ReadNext(ms)) { } } } // *** you need some mechanism to map types to fields static readonly IDictionary typeLookup = new Dictionary { {1, typeof(int)}, {2, typeof(Person)}, {3, typeof(string)} }; static void WriteNext(Stream stream, object obj) { Type type = obj.GetType(); int field = typeLookup.Single(pair => pair.Value == type).Key; Serializer.NonGeneric.SerializeWithLengthPrefix(stream, obj, PrefixStyle.Base128, field); } static bool ReadNext(Stream stream) { object obj; if (Serializer.NonGeneric.TryDeserializeWithLengthPrefix(stream, PrefixStyle.Base128, field => typeLookup[field], out obj)) { Console.WriteLine(obj); return true; } return false; } } [ProtoContract] class Person { [ProtoMember(1)]public string Name { get; set; } public override string ToString() { return "Person: " + Name; } } 

请注意,这当前在v2版本中不起作用(因为“WithLengthPrefix”代码不完整),但我会在v1上测试它。 如果它有效,我将上述所有场景都用于测试套件,以确保它在v2 工作。

编辑:

是的,它在“v1”上运行正常,输出:

 123 Person: Fred abc 

我想出了另一个解决方案,但我决定把它作为答案而不是问题,因为这对我来说更有意义。 在我看来,它非常难看,并且我已被警告不要使用reflection,所以如果你有它,请对它进行评论或提供更好的答案。 谢谢!


 class Program { static void Main(string[] args) { Person person = new Person { Id = 12345, Name = "Fred", Address = new Address { Line1 = "Flat 1", Line2 = "The Meadows" } }; object value; using (Stream stream = new MemoryStream()) { Send(stream, person); stream.Position = 0; value = Read(stream); person = value as Person; } } static void Send(Stream stream, T value) { Header header = new Header() { Guid = Guid.NewGuid(), Type = typeof(T) }; Serializer.SerializeWithLengthPrefix
(stream, header, PrefixStyle.Base128); Serializer.SerializeWithLengthPrefix(stream, value, PrefixStyle.Base128); } static object Read(Stream stream) { Header header; header = Serializer.DeserializeWithLengthPrefix
(stream, PrefixStyle.Base128); MethodInfo m = typeof(Serializer).GetMethod("DeserializeWithLengthPrefix", new Type[] {typeof(Stream), typeof(PrefixStyle)}).MakeGenericMethod(header.Type); Object value = m.Invoke(null, new object[] {stream, PrefixStyle.Base128} ); return value; } } [ProtoContract] class Header { public Header() { } [ProtoMember(1, IsRequired = true)] public Guid Guid { get; set; } [ProtoIgnore] public Type Type { get; set; } [ProtoMember(2, IsRequired = true)] public string TypeName { get { return this.Type.FullName; } set { this.Type = Type.GetType(value); } } } [ProtoContract] class Person { [ProtoMember(1)] public int Id { get; set; } [ProtoMember(2)] public string Name { get; set; } [ProtoMember(3)] public Address Address { get; set; } } [ProtoContract] class Address { [ProtoMember(1)] public string Line1 { get; set; } [ProtoMember(2)] public string Line2 { get; set; } }