C# 如何跨Protobuf发送多种类型的对象?
我正在实现一个客户机-服务器应用程序,并正在研究各种序列化和传输数据的方法。我开始使用Xml序列化程序,它工作得相当好,但生成数据的速度很慢,并且生成大型对象,特别是当它们需要通过网络发送时。所以我开始研究Protobuf和Protobuf网络 我的问题在于protobuf没有发送类型信息。使用Xml序列化程序,我能够构建一个包装器,它可以在同一个流上发送和接收任何不同的(可序列化的)对象,因为序列化为Xml的对象包含对象的类型名C# 如何跨Protobuf发送多种类型的对象?,c#,serialization,protocol-buffers,protobuf-net,C#,Serialization,Protocol Buffers,Protobuf Net,我正在实现一个客户机-服务器应用程序,并正在研究各种序列化和传输数据的方法。我开始使用Xml序列化程序,它工作得相当好,但生成数据的速度很慢,并且生成大型对象,特别是当它们需要通过网络发送时。所以我开始研究Protobuf和Protobuf网络 我的问题在于protobuf没有发送类型信息。使用Xml序列化程序,我能够构建一个包装器,它可以在同一个流上发送和接收任何不同的(可序列化的)对象,因为序列化为Xml的对象包含对象的类型名 ObjectSocket socket = new Object
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);
...
我考虑了一些解决方案,包括创建一个主“state”类型的类,它是通过套接字发送的唯一对象类型。不过,这偏离了我使用Xml序列化程序开发的功能,因此我希望避免这种方向
第二个选项是在某种类型的包装器中包装protobuf对象,它定义了对象的类型。(这个包装器还将包含诸如数据包ID和目的地之类的信息。)使用protobuf net序列化一个对象,然后将该流固定在Xml标记之间似乎很愚蠢,但我已经考虑过了。有没有一种简单的方法可以从protobuf或protobuf网络中获得此功能
我提出了第三个解决方案,并将其发布在下面,但如果您有更好的解决方案,请也发布
有关字段边界错误的信息(使用
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);
}
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);
}
}
序列化:
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);
}
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);
}
}
反序列化:
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);
}
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=>\u映射[field]
在查找63671269
时抛出KeyNotFoundException
如果我在散列函数中将
ToInt32
替换为ToInt16
,则字段值设置为29723
,并且可以工作。如果我将System.String
的字段显式定义为1
,它也可以工作。将字段显式定义为600542181
与使用哈希函数定义它具有相同的效果。正在序列化的字符串的值不会改变结果。我想出了另一个解决方案,但我决定将其作为答案,而不是问题,因为这对我更有意义。在我看来,它相当丑陋,而且我已经被警告不要使用反射,所以请对它进行评论,或者如果你有更好的答案,请提供更好的答案。谢谢
类程序
{
静态void Main(字符串[]参数)
{
人=新人
{
Id=12345,
Name=“Fred”,
地址=新地址
{
Line1=“平面1”,
Line2=“草地”
}
};
目标价值;
使用(Stream=newmemoryStream())
{
发送(流、人);
流位置=0;
值=读取(流);
人=人的价值;
}
}
静态无效发送(流,T值)
{
页眉=新页眉()
{
Guid=Guid.NewGuid(),
类型=类型(T)
};
Serializer.SerializeWithLengthPrefix(流、头、PrefixStyle.Base128);
Serializer.SerializeWithLengthPrefix(流、值、PrefixStyle.Base128);
}
静态对象读取(流)
{
收割台;
header=Serializer.DeserializeWithLengthPrefix
(流,前缀为style.Base128);
MethodInfo m=typeof(Serializer).GetMethod(“反序列化WithLengthPrefix”,
新类型[]{typeof(Stream),typeof(PrefixStyle)}.MakeGenericMethod(header.Type);
Object value=m.Invoke(null,新对象[]{stream,PrefixStyle.Base128});
返回值;
}
}
[原始合同]
类标题
{
公共标头(){}
[原成员(1,IsRequired=true)]
公共Guid Guid{get;set;}
[忽略]
公共类型类型{get;set;}
[原成员(2,IsRequired=true)]
公共字符串类型名
{
获取{返回this.Type.FullName;}
设置{this.Type=Type.GetType(value);}
}
}
[原始合同]
班主任{
[原成员(1)]
公共int Id{get;set;}
[原成员(2)]
公共字符串名称{get;set;}
[原成员(3)]
公共广播地址{get;set;}
}
[原始合同]
班级地址{
[原成员(1)]
公共字符串Line1{get;set;}
[原成员(2)]
公共字符串第2行{get;set;}
}
此功能实际上是内置的,尽管不明显
在这种情况下,预计您将为每种消息类型指定一个唯一的编号。您正在使用的重载将它们全部作为“字段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<int, Type> typeLookup = new Dictionary<int, Type>
{
{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; }
}
按照承诺添加到测试套件中:我为低估protobuf net的全面性而感到羞耻!如果我想避免使用神奇的预定义字典,那么使用
obj.GetType().GetHashCode()
生成字段
数字会是一个坏主意吗?@Daniel-只要你有一些方案来缓解散列冲突的微小可能性…@Daniel-一夜之间实现;不使用哈希代码有很好的理由:简单地说,数字必须是=1
(这很容易修复),但更重要的是:哈希代码不应该在给定的应用程序域之外受到信任。他们可以改变;例如,字符串散列算法在1.1和2.0之间更改,并且可以合法地再次更改。最好是用我喜欢的东西