2009-03-23 11 views
19

Mam 2 aplikacje sieciowe, które powinny wysyłać między sobą serializowane komunikaty protobuf-net. Mogę serializować obiekty i wysłać je, jednak, Nie mogę dowiedzieć się, jak deserializować odebrane bajty.Deserializuj nieznany typ za pomocą protobuf-net

Próbowałem deserializować z tym i nie powiodło się z NullReferenceException.

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

Jestem przechodzącą nagłówka przed serializowane bajtów, które zawiera identyfikator typu wiadomości, które można używać w gigantycznej switch powrót oczekiwaną sublcass Type. W poniższym bloku otrzymuję błąd: 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; 

Oto funkcja używać do wysyłania wiadomości za pośrednictwem sieci:

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

Ta klasa, z klasy bazowej, jestem szeregowania:

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


Naprawiono za pomocą sugestii Marca Gravell'a. Usunąłem atrybut ProtoMember z właściwości tylko do odczytu. Przełączono również na użycie SerializeWithLengthPrefix. Oto co mam teraz:

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

Aby otrzymać obiekt:

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

Aby wysłać obiekt:

//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

Zapomniałem wspomnieć, mam szeregowania w .NET 3.5 na Windows i deserializacji w Mono 2.2 i używam odpowiednich protobuf netto DLL na każdej platformie. –

+0

Wrócę, żeby to przeczytać i napisać odpowiedź za około pół godziny ... Muszę w tej chwili uciec, przepraszam. BTW - w następnym wydaniu wbudowano nietypowe opakowania - wciąż na moim laptopie. –

+0

btw - Pracuję nad scaleniem mojej lokalnej kopii, więc mogę zatwierdzić zmiany, aby było to łatwiejsze. Mam jeden wyjątkowy test-fail, ale obejmuje on nowy kod, więc jestem zadowolony, aby go zatwierdzić (oznaczony jako ignorowany), jeśli to pomaga. –

Odpowiedz

7

pierwsze; do korzystania z sieci dostępne są SerializeWithLengthPrefix i DeserializeWithLengthPrefix, które obsługują długość (opcjonalnie ze znacznikiem). MakeGenericMethod wygląda na pierwszy rzut oka dobrze; i to faktycznie ściśle wiąże się z oczekującym zatwierdzeniem pracy, którą wykonywałem, aby zaimplementować stos RPC: oczekujący kod has an override of DeserializeWithLengthPrefix, który zajmuje (zasadniczo) Func<int,Type>, aby rozwiązać znacznik na typ, aby ułatwić deserializację nieoczekiwane dane w locie.

Jeśli typ komunikatu rzeczywiście odnosi się do dziedziczenia między BaseMessage i BeginRequest, to nie jest to konieczne; zawsze przechodzi do najwyższego typu kontraktu w hierarchii i przechodzi w dół (ze względu na drobne szczegóły).

Również - Nie miałem okazję go przetestować, ale dodaje może być denerwujące go:

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

To jest oznaczony do serializacji, ale nie ma mechanizm do ustawiania wartości. Może to jest problem? Spróbuj usunąć tutaj [ProtoMember], ponieważ nie jest to przydatne - jest (o ile chodzi o serializację), w dużej mierze duplikatem znacznika [ProtoInclude(...)].

+0

Zrobię zdjęcie i komentarz z wynikami. Dziękuję za odpowiedź! –

+1

Przełączenie na SerializeWithLengthPrefix skróciło mój kod. :) Usunięcie atrybutu ProtoMember z właściwości readonly rozwiązało problem. Dziękuję Ci!! –

3

Innym sposobem radzenia sobie z tym jest użycie protokołu protobuf-net do "podnoszenia ciężkich przedmiotów", ale użycie własnego nagłówka wiadomości. Problem z przetwarzaniem komunikatów sieciowych polega na tym, że mogą one być łamane poza granice. Zazwyczaj wymaga to użycia bufora do gromadzenia odczytów. Jeśli użyjesz własnego nagłówka, możesz mieć pewność, że wiadomość jest w całości, zanim przekażesz ją protobuf-net.

Jako przykład:

Wysyłanie

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

Aby otrzymać

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

Druga metoda będzie "próbuje" w buforze akumulatora dopóki nie było wystarczającej ilości danych. Po spełnieniu wymogu wielkości wiadomość może zostać przekształcona do postaci szeregowej.

+4

Byłoby bardzo korzystne, gdyby protobuf-net dostarczył przeciążenie do Deserializacji z przekazanym typem, np. ProtoBuf.Serializer.Deserialize (Type objectType, memoryStream); czy ktoś wie, czy jest to możliwe? Unikałoby to niechlujnej instrukcji switch, jeśli masz wiele nieznanych typów, które chcesz deserializować –

+1

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

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

lub

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

rzeczywiście, lub z v1 API (który nadal działa w v2), 'Serializer.NonGeneric.Deserialize (...)' (bierze parametr 'Type', a nie' 'typowy argument typu) –

+0

@MarcGravell, dziękuję jakoś nie zauważyłem właściwości NonGeneric -) –

Powiązane problemy