2009-03-23 32 views
19

Tôi có 2 ứng dụng nối mạng sẽ gửi các tin nhắn protobuf-net tuần tự cho nhau. Tôi có thể tuần tự hóa các đối tượng và gửi chúng, tuy nhiên, Tôi không thể tìm ra cách để deserialize các byte nhận được.Deserialize loại không xác định với protobuf-net

Tôi đã cố gắng deserialize với điều này và nó đã thất bại với một NullReferenceException.

// Where "ms" is a memorystream containing the serialized 
// byte array from the network. 
Messages.BaseMessage message = 
    ProtoBuf.Serializer.Deserialize<Messages.BaseMessage>(ms); 

Tôi đang chuyển tiêu đề trước các byte được tuần tự hóa chứa ID loại thông báo mà tôi có thể sử dụng trong câu lệnh chuyển đổi khổng lồ để trả về loại sublcass được mong đợi. Với khối bên dưới, tôi nhận được lỗi: System.Reflection.TargetInvocationException ---> System.NullReferenceException.

//Where "ms" is a memorystream and "messageType" is a 
//Uint16. 
Type t = Messages.Helper.GetMessageType(messageType); 
System.Reflection.MethodInfo method = 
    typeof(ProtoBuf.Serializer).GetMethod("Deserialize").MakeGenericMethod(t); 
message = method.Invoke(null, new object[] { ms }) as Messages.BaseMessage; 

Dưới đây là các chức năng tôi sử dụng để gửi tin nhắn qua mạng:

internal void Send(Messages.BaseMessage message){ 
    using (System.IO.MemoryStream ms = new System.IO.MemoryStream()){ 
    ProtoBuf.Serializer.Serialize(ms, message); 
    byte[] messageTypeAndLength = new byte[4]; 
    Buffer.BlockCopy(BitConverter.GetBytes(message.messageType), 0, messageTypeAndLength, 0, 2); 
    Buffer.BlockCopy(BitConverter.GetBytes((UInt16)ms.Length), 0, messageTypeAndLength, 2, 2); 
    this.networkStream.Write(messageTypeAndLength); 
    this.networkStream.Write(ms.ToArray()); 
    } 
} 

này lớp, với lớp cơ sở, tôi serializing:

[Serializable, 
ProtoContract, 
ProtoInclude(50, typeof(BeginRequest))] 
abstract internal class BaseMessage 
{ 
    [ProtoMember(1)] 
    abstract public UInt16 messageType { get; } 
} 

[Serializable, 
ProtoContract] 
internal class BeginRequest : BaseMessage 
{ 
    [ProtoMember(1)] 
    public override UInt16 messageType 
    { 
     get { return 1; } 
    } 
} 


Đã sửa lỗi sử dụng đề xuất của Marc Gravell. Tôi đã loại bỏ thuộc tính ProtoMember khỏi các thuộc tính chỉ đọc. Cũng chuyển sang sử dụng SerializeWithLengthPrefix. Dưới đây là những gì tôi có bây giờ:

[Serializable, 
ProtoContract, 
ProtoInclude(50, typeof(BeginRequest))] 
abstract internal class BaseMessage 
{ 
    abstract public UInt16 messageType { get; } 
} 

[Serializable, 
ProtoContract] 
internal class BeginRequest : BaseMessage 
{ 
    public override UInt16 messageType 
    { 
     get { return 1; } 
    } 
} 

Tiếp nhận một đối tượng:

//where "this.Ssl" is an SslStream. 
BaseMessage message = 
    ProtoBuf.Serializer.DeserializeWithLengthPrefix<BaseMessage>(
    this.Ssl, ProtoBuf.PrefixStyle.Base128); 

Để gửi một đối tượng:

//where "this.Ssl" is an SslStream and "message" can be anything that 
// inherits from BaseMessage. 
ProtoBuf.Serializer.SerializeWithLengthPrefix<BaseMessage>(
    this.Ssl, message, ProtoBuf.PrefixStyle.Base128); 
+0

tôi quên đề cập đến, tôi serializing trong .NET 3.5 trên Windows và deserializing trong Mono 2.2 và đang sử dụng dlls protobuf-net thích hợp trên mỗi nền tảng. –

+0

Tôi sẽ quay lại để đọc và đăng câu trả lời trong khoảng nửa giờ ... phải chạy vào lúc này, xin lỗi. BTW - bản phát hành tiếp theo có các trình bao bọc không chung chung được tích hợp sẵn - vẫn còn trên máy tính xách tay của tôi vào lúc này. –

+0

btw - Tôi đang làm việc để hợp nhất bản sao cục bộ của mình, vì vậy tôi có thể cam kết các thay đổi để làm cho việc này trở nên dễ dàng hơn. Tôi có một kiểm tra lỗi xuất sắc, nhưng điều đó bao gồm mã mới, vì vậy tôi nội dung cam kết nó (được đánh dấu là bỏ qua) nếu nó giúp. –

Trả lời

7

thứ nhất; để sử dụng mạng, có SerializeWithLengthPrefixDeserializeWithLengthPrefix xử lý thời lượng cho bạn (tùy chọn với thẻ). MakeGenericMethod trông OK ngay từ cái nhìn đầu tiên; và điều này thực sự gắn kết chặt chẽ với cam kết đang chờ xử lý của tác phẩm mà tôi đã thực hiện để triển khai ngăn xếp RPC: mã đang chờ xử lý has an override of DeserializeWithLengthPrefix mất (về cơ bản) Func<int,Type>, để phân giải thẻ thành loại để dễ dàng hơn để deserialize dữ liệu bất ngờ khi đang di chuyển.

Nếu loại thư thực sự liên quan đến thừa kế giữa BaseMessageBeginRequest, thì bạn không cần điều này; nó luôn luôn đi đến loại hợp đồng cao nhất trong hệ thống phân cấp và hoạt động theo cách của nó xuống (do một số chi tiết dây).

Ngoài ra - Tôi đã không có cơ hội để kiểm tra nó, nhưng sau đây có thể làm xáo trộn nó:

[ProtoMember(1)] 
public override UInt16 messageType 
{ 
    get { return 1; } 
} 

Nó được đánh dấu để tuần tự, nhưng không có cơ chế để thiết lập các giá trị. Có lẽ đây là vấn đề? Hãy thử loại bỏ các [ProtoMember] ở đây, vì tôi không này là hữu ích - đó là (như xa như serialization là có liên quan), phần lớn là một bản sao của các đánh dấu [ProtoInclude(...)].

+0

Tôi sẽ cung cấp cho nó một shot và bình luận trở lại với kết quả. Cảm ơn vì đã phản hồi! –

+1

Chuyển sang SerializeWithLengthPrefix rút ngắn mã của tôi. :) Xóa thuộc tính ProtoMember khỏi thuộc tính chỉ đọc đã khắc phục sự cố. Cảm ơn bạn!! –

3

Một cách khác để xử lý việc này là sử dụng protobuf-net cho "nâng hạng nặng", nhưng để sử dụng tiêu đề thư của riêng bạn. Vấn đề với việc xử lý tin nhắn mạng là chúng có thể bị phá vỡ trên các ranh giới. Điều này thường đòi hỏi phải sử dụng bộ đệm để tích lũy số lần đọc. Nếu bạn sử dụng tiêu đề của riêng mình, bạn có thể chắc chắn rằng toàn bộ thông điệp đó có sẵn trước khi đưa nó tới protobuf-net.

Như một ví dụ:

Để gửi

using (System.IO.MemoryStream ms = new System.IO.MemoryStream()) 
{ 
    MyMessage message = new MyMessage(); 
    ProtoBuf.Serializer.Serialize<BaseMessage>(ms, message); 
    byte[] buffer = ms.ToArray(); 

    int messageType = (int)MessageType.MyMessage; 
    _socket.Send(BitConverter.GetBytes(messageType)); 
    _socket.Send(BitConverter.GetBytes(buffer.Length)); 
    _socket.Send(buffer); 
} 

Tiếp nhận

protected bool EvaluateBuffer(byte[] buffer, int length) 
{ 
    if (length < 8) 
    { 
     return false; 
    } 

    MessageType messageType = (MessageType)BitConverter.ToInt32(buffer, 0); 
    int size = BitConverter.ToInt32(buffer, 4); 
    if (length < size + 8) 
    { 
     return false; 
    } 

    using (MemoryStream memoryStream = new MemoryStream(buffer)) 
    { 
     memoryStream.Seek(8, SeekOrigin.Begin); 
     if (messageType == MessageType.MyMessage) 
     { 
      MyMessage message = 
       ProtoBuf.Serializer.Deserialize<MyMessage>(memoryStream); 
     } 
    } 
} 

Phương pháp thứ hai sẽ được "thử" trên một đệm ắc cho đến khi có đủ dữ liệu. Khi yêu cầu kích thước được đáp ứng, thông báo có thể được deserialized.

+4

Sẽ rất có ích nếu protobuf-net cung cấp quá tải cho Deserialize với Loại được truyền vào, ví dụ: ProtoBuf.Serializer.Deserialize (Loại objectType, memoryStream); có ai biết nếu điều này là có thể? Điều này sẽ tránh được một câu lệnh chuyển đổi lộn xộn nếu bạn có nhiều loại không xác định mà bạn muốn deserialize –

+1

RuntimeTypeModel.Default.Deserialize (Stream, null, Type); –

9
Serializer.NonGeneric.Deserialize(Type, Stream); //Thanks, Marc. 

hoặc

RuntimeTypeModel.Default.Deserialize(Stream, null, Type); 
+0

thực sự, hoặc với API v1 (vẫn hoạt động trong v2), 'Serializer.NonGeneric.Deserialize (...)' (lấy tham số 'Loại', không phải là một đối số kiểu' 'chung) –

+0

@MarcGravell, cảm ơn bằng cách nào đó tôi đã không nhận thấy tài sản NonGeneric -) –

Các vấn đề liên quan