我正在实现客户端 - 服务器应用程序,我正在研究各种序列化和传输数据的方法。我开始使用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制作的功能,所以我想避免这个方向。
第二个选项是将protobuf对象包装在某种类型的包装器中,该包装器定义了对象的类型。 (这个包装器还包括诸如包ID和目的地之类的信息。)使用protobuf-net来序列化一个对象然后在Xml标签之间粘贴该流似乎很愚蠢,但我已经考虑过了。是否有一种简单的方法可以从protobuf或protobuf-net中获取此功能?
我已经提出了第三个解决方案,并在下面发布,但是如果你有更好的解决方案,请发布它!
有关字段边界错误的信息(使用 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
。
如果我在哈希函数中用ToInt32
替换ToInt16
,则字段值设置为29723
并且它可以正常工作。如果我明确将System.String
的字段定义为1
,它也可以使用。将字段明确定义到600542181
与使用散列函数定义它具有相同的效果。被序列化的字符串的值不会改变结果。
答案 0 :(得分:9)
这个功能实际上是内置的,虽然不是很明显。
在这种情况下,预计您将为每种消息类型指定一个唯一编号。您正在使用的重载将它们全部作为“字段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; }
}
请注意,此 当前在v2版本中工作(因为“WithLengthPrefix”代码不完整),但我会在v1上测试它。如果它有效,我将以上所有方案都添加到测试套件中,以确保 在v2中工作。
编辑:
是的,它在“v1”上运行正常,输出:
123
Person: Fred
abc
答案 1 :(得分:3)
我想出了另一个解决方案,但我决定把它作为一个答案,而不是问题,因为这对我来说更有意义。在我看来,它非常丑陋,并且我已经被警告不要使用反射,所以如果你有它,请对它进行评论或提供更好的答案。谢谢!
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<Person>(stream, person);
stream.Position = 0;
value = Read(stream);
person = value as Person;
}
}
static void Send<T>(Stream stream, T value)
{
Header header = new Header()
{
Guid = Guid.NewGuid(),
Type = typeof(T)
};
Serializer.SerializeWithLengthPrefix<Header>(stream, header, PrefixStyle.Base128);
Serializer.SerializeWithLengthPrefix<T>(stream, value, PrefixStyle.Base128);
}
static object Read(Stream stream)
{
Header header;
header = Serializer.DeserializeWithLengthPrefix<Header>
(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; }
}