2010-06-15 11 views
7

Tôi đang triển khai ứng dụng máy khách-khách và đang xem xét các cách khác nhau để tuần tự hóa và truyền dữ liệu. Tôi bắt đầu làm việc với Xml Serializers, hoạt động khá tốt, nhưng tạo dữ liệu từ từ và tạo các đối tượng lớn, đặc biệt khi chúng cần được gửi qua mạng. Vì vậy, tôi bắt đầu nhìn vào Protobuf, và protobuf-net.Làm cách nào để gửi nhiều loại đối tượng trên Protobuf?

Vấn đề của tôi nằm trong thực tế là protobuf không gửi thông tin loại với nó. Với Xml Serializers, tôi đã có thể xây dựng một wrapper mà sẽ gửi và nhận bất kỳ đối tượng (serializable) khác nhau trên cùng một dòng, kể từ khi đối tượng serialized vào Xml chứa tên kiểu của đối tượng.

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); 
... 

Tôi đã xem xét một vài giải pháp cho việc này, bao gồm tạo lớp loại "trạng thái" chính, là loại đối tượng duy nhất được gửi qua ổ cắm của tôi. Điều này di chuyển ra khỏi các chức năng tôi đã làm việc với Xml Serializers, mặc dù, vì vậy tôi muốn tránh hướng đó.

Tùy chọn thứ hai sẽ là bọc các đối tượng protobuf trong một số loại trình bao bọc, xác định loại đối tượng. (Trình bao bọc này cũng sẽ bao gồm các thông tin như ID gói và đích.) Có vẻ ngớ ngẩn khi sử dụng protobuf-net để tuần tự hóa một đối tượng, sau đó gắn luồng đó giữa các thẻ Xml, nhưng tôi đã xem xét nó. Có một cách dễ dàng để có được chức năng này ra khỏi protobuf hoặc protobuf-net?


Tôi đã đưa ra giải pháp thứ ba và đăng nó bên dưới, nhưng nếu bạn có giải pháp tốt hơn, hãy đăng nó lên!


Thông tin về giới hạn lĩnh vực lỗi (sử dụngSystem.String):

Băm:

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)); 
} 

serialization:

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] ném một KeyNotFoundException trong khi tìm kiếm 63671269.

Nếu tôi thay thế ToInt32 bằng ToInt16 trong hàm băm, giá trị trường được đặt thành 29723 và nó hoạt động. Nó cũng hoạt động nếu tôi xác định rõ ràng trường của System.String đến 1. Xác định rõ ràng trường là 600542181 có cùng tác dụng như sử dụng hàm băm để xác định nó. Giá trị của chuỗi được tuần tự hóa không thay đổi kết quả.

Trả lời

9

Chức năng này được thực sự được xây dựng trong, mặc dù không rõ ràng.

Trong trường hợp này, người ta dự đoán rằng bạn sẽ chỉ định một số duy nhất cho mỗi loại thông báo. Quá tải bạn đang sử dụng chuyển tất cả chúng thành "trường 1", nhưng có quá tải cho phép bạn thêm thông tin tiêu đề bổ sung này (tuy nhiên vẫn là công việc của mã gọi để quyết định cách ánh xạ số tới các kiểu). Sau đó bạn có thể chỉ định các loại khác nhau như các trường khác nhau là luồng (lưu ý: điều này chỉ hoạt động với kiểu tiền tố cơ sở-128).

tôi sẽ cần phải kiểm tra tăng gấp đôi, nhưng ý định là một cái gì đó như sau nên làm việc:

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; } 
} 

Lưu ý rằng này không hiện đang làm việc trong v2 xây dựng (kể từ khi "WithLengthPrefix "mã chưa hoàn thành), nhưng tôi sẽ đi và kiểm tra nó trên v1. Nếu nó hoạt động, tôi sẽ tất cả các kịch bản trên để các bộ thử nghiệm để đảm bảo nó hiện làm việc trong v2.

Edit:

có, nó không hoạt động tốt trên "v1", với sản lượng:

123 
Person: Fred 
abc 
+0

gia tăng để kiểm tra bộ như đã hứa: http://code.google.com/p/protobuf -net/source/browse/trunk/Ví dụ/MultiTypesWithLengthPrefix.cs –

+0

Xấu hổ với tôi vì đã đánh giá thấp tính toàn diện của protobuf-net! Sử dụng 'obj.GetType(). GetHashCode()' có phải là một ý tưởng tồi khi tạo các số 'trường', nếu tôi muốn tránh một từ điển được xác định trước? – dlras2

+0

@Daniel - miễn là bạn có một số lược đồ để giảm thiểu chống lại cơ hội * mỏng * của xung đột băm ... –

3

Tôi đã đưa ra một giải pháp khác, nhưng tôi đã quyết định đặt câu trả lời đó thay vì trong câu hỏi, vì điều đó có ý nghĩa hơn với tôi. Nó khá xấu xí, theo ý kiến ​​của tôi, và tôi đã được cảnh báo chống lại việc sử dụng sự phản chiếu, vì vậy hãy bình luận về nó hoặc cung cấp câu trả lời tốt hơn nếu bạn có chúng. Cảm ơn!


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; } 
}