Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/300.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何跨Protobuf发送多种类型的对象?_C#_Serialization_Protocol Buffers_Protobuf Net - Fatal编程技术网

C# 如何跨Protobuf发送多种类型的对象?

C# 如何跨Protobuf发送多种类型的对象?,c#,serialization,protocol-buffers,protobuf-net,C#,Serialization,Protocol Buffers,Protobuf Net,我正在实现一个客户机-服务器应用程序,并正在研究各种序列化和传输数据的方法。我开始使用Xml序列化程序,它工作得相当好,但生成数据的速度很慢,并且生成大型对象,特别是当它们需要通过网络发送时。所以我开始研究Protobuf和Protobuf网络 我的问题在于protobuf没有发送类型信息。使用Xml序列化程序,我能够构建一个包装器,它可以在同一个流上发送和接收任何不同的(可序列化的)对象,因为序列化为Xml的对象包含对象的类型名 ObjectSocket socket = new Object

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

我的问题在于protobuf没有发送类型信息。使用Xml序列化程序,我能够构建一个包装器,它可以在同一个流上发送和接收任何不同的(可序列化的)对象,因为序列化为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);
...
我考虑了一些解决方案,包括创建一个主“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之间更改,并且可以合法地再次更改。最好是用我喜欢的东西