10

私はクライアントサーバーアプリケーションを実装しており、データをシリアル化して送信するためのさまざまな方法を検討しています。私はXmlSerializersを使い始めました。これはかなりうまく機能しましたが、データの生成が遅く、特にネット経由で送信する必要がある場合は大きなオブジェクトを作成します。そこで、Protobufとprotobuf-netを調べ始めました。

私の問題は、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);
...

私はこれに対するいくつかの解決策を検討しました。これには、私のソケットを介して送信される唯一のタイプのオブジェクトであるマスター「状態」タイプクラスの作成が含まれます。ただし、これはXmlシリアライザーで使用していた機能から離れているため、その方向性は避けたいと思います。

2番目のオプションは、オブジェクトのタイプを定義するあるタイプのラッパーでprotobufオブジェクトをラップすることです。(このラッパーには、パケットIDや宛先などの情報も含まれます。)protobuf-netを使用してオブジェクトをシリアル化し、そのストリームをXmlタグの間に貼り付けるのはばかげているようですが、私はそれを検討しました。この機能をprotobufまたはprotobuf-netから取り出す簡単な方法はありますか?


私は3番目の解決策を考え出し、それを以下に投稿しましたが、より良い解決策があれば、それも投稿してください!


フィールド境界のバグに関する情報(を使用 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]KeyNotFoundExceptionを探している間スローし63671269ます。

ハッシュ関数でに置き換えるToInt32ToInt16、フィールド値がに設定され29723て機能します。System.Stringのフィールドをに明示的に定義した場合にも機能し1ます。フィールドを明示的に定義する600542181と、ハッシュ関数を使用してフィールドを定義するのと同じ効果があります。シリアル化される文字列の値は結果を変更しません。

4

2 に答える 2

11

この機能は、明らかにではありませんが、実際には組み込まれています。

このシナリオでは、メッセージタイプごとに一意の番号を指定することが予想されます。使用しているオーバーロードはそれらをすべて「フィールド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
于 2010-06-16T20:55:56.807 に答える
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; }
}
于 2010-06-15T21:26:55.947 に答える