如何在Protobuf中发送多种类型的对象? [英] How can I send multiple types of objects across Protobuf?

查看:58
本文介绍了如何在Protobuf中发送多种类型的对象?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在实现一个客户端服务器应用程序,并且正在研究各种方法来序列化和传输数据.我开始使用Xml Serializers,它工作得很好,但是生成数据的速度很慢,并且会生成大型对象,尤其是当它们需要通过网络发送时.因此,我开始研究Protobuf和protobuf-net.

I'm implementing a client-server application, and am looking into various ways to serialize and transmit data. I began working with Xml Serializers, which worked rather well, but generate data slowly, and make large objects, especially when they need to be sent over the net. So I started looking into Protobuf, and protobuf-net.

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

My problem lies in the fact that protobuf doesn't sent type information with it. With Xml Serializers, I was able to build a wrapper which would send and receive any various (serializable) object over the same stream, since object serialized into Xml contain the type name of the 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);
...

我已经考虑了一些解决方案,包括创建一个主状态"类型类,这是通过我的套接字发送的唯一对象类型.不过,这偏离了我使用Xml序列化器解决的功能,因此我想避免这个方向.

I've considered a few solutions to this, including creating a master "state" type class, which is the only type of object sent over my socket. This moves away from the functionality I've worked out with Xml Serializers, though, so I'd like to avoid that direction.

第二个选项是将protobuf对象包装在某种类型的包装器中,该包装器定义了对象的类型.(此包装器还将包括数据包ID和目的地之类的信息.)使用protobuf-net序列化对象,然后将该流粘贴在Xml标签之间,这似乎很愚蠢,但是我已经考虑过了.有没有简单的方法可以使此功能脱离protobuf或protobuf-net?

The second option would be to wrap protobuf objects in some type of wrapper, which defines the type of object. (This wrapper would also include information such as packet ID, and destination.) It seems silly to use protobuf-net to serialize an object, then stick that stream between Xml tags, but I've considered it. Is there an easy way to get this functionality out of protobuf or protobuf-net?

我想出了第三个解决方案,并将其发布在下面,但是如果您有更好的解决方案,也请发布它!

I've come up with a third solution, and posted it below, but if you have a better one, please post it too!

关于字段边界的信息错误(使用 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);
    }
}

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

如果我在哈希函数中将 ToInt32 替换为 ToInt16 ,则该字段值将设置为 2972​​3 并且可以正常工作.如果我将 System.String 的字段显式定义为 1 ,它也将起作用.将字段显式定义为 600542181 的效果与使用哈希函数定义该字段的效果相同.要序列化的字符串的值不会更改结果.

If I replace ToInt32 with ToInt16 in the hash function, the field value is set to 29723 and it works. It also works if I explicitly define System.String's field to 1. Explicitly defining the field to 600542181 has the same effect as using the hash function to define it. The value of the string being serialized does not change the outcome.

推荐答案

此功能实际上是内置的,尽管不是很明显.

This functionality is actually built in, albeit not obviously.

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

In this scenario, it is anticipated that you would designate a unique number per message type. The overload you are using passes them all in as "field 1", but there is an overload that lets you include this extra header information (it is still the job of the calling code to decide how to map numbers to types, though). You can then specify different types as different fields is the stream (note: this only works with the base-128 prefix style).

我需要仔细检查,但目的是使类似以下内容的工作正常:

I'll need to double check, but the intention is that something like the following should work:

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中可以正常工作.

Note that this doesn't currently work in the v2 build (since the "WithLengthPrefix" code is incomplete), but I'll go and test it on v1. If it works, I'll all the above scenario to the test suite to ensure it does work in v2.

是的,它在"v1"上确实可以正常工作,输出:

yes, it does work fine on "v1", with output:

123
Person: Fred
abc

这篇关于如何在Protobuf中发送多种类型的对象?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆