2009-03-23 12 views
19

Ho 2 app collegate in rete che dovrebbero inviare messaggi protobuf-net serializzati tra loro. Posso serializzare gli oggetti e inviarli, tuttavia, Non riesco a capire come deserializzare i byte ricevuti.Deserializzare il tipo sconosciuto con protobuf-net

Ho provato a deserializzare con questo e non è riuscito con una NullReferenceException.

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

sto passando un colpo di testa prima che i byte serializzati che contiene il tipo di messaggio ID, che posso utilizzare in un'istruzione switch gigante per restituire il tipo sublcass previsto. Con il blocco sottostante, ricevo l'errore: 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; 

Ecco la funzione che uso per inviare un messaggio attraverso la rete:

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

Questa classe, con classe di base, sto serializzazione:

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


Fixato usando il suggerimento di Marc Gravell. Ho rimosso l'attributo ProtoMember dalle proprietà di sola lettura. Passa anche a usare SerializeWithLengthPrefix. Ecco quello che ho adesso:

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

Per ricevere un oggetto:

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

Per inviare un oggetto:

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

ho dimenticato di dire, sto serializzazione in .NET 3.5 su Windows e deserializzazione in Mono 2.2 e sto usando le DLL protobuf-net appropriate su ogni piattaforma. –

+0

Tornerò a leggere questo e posta una risposta tra circa mezz'ora ... devo correre in questo momento, mi dispiace. BTW - la prossima versione ha wrapper non generici integrati - ancora sul mio portatile al momento, però. –

+0

btw - Sto lavorando per unire la mia copia locale, così posso confermare le modifiche per renderlo più semplice. Ho un eccezionale test fallito, ma quello riguarda il nuovo codice, quindi sono contento di eseguirlo (contrassegnato come ignorato) se aiuta. –

risposta

7

Primo; per l'utilizzo della rete, sono disponibili SerializeWithLengthPrefix e DeserializeWithLengthPrefix che gestiscono la lunghezza per te (facoltativamente con un tag). Il MakeGenericMethod sembra OK a prima vista; e questo in realtà si lega molto strettamente al commit in sospeso del lavoro che ho fatto per implementare uno stack RPC: il codice in sospeso has an override of DeserializeWithLengthPrefix che richiede (essenzialmente) un Func<int,Type>, per risolvere un tag in un tipo per rendere più facile la deserializzazione dati inaspettati al volo.

Se il tipo di messaggio si riferisce effettivamente all'eredità tra BaseMessage e BeginRequest, non è necessario; va sempre al tipo di contratto più in alto nella gerarchia e procede verso il basso (a causa di alcuni dettagli sul filo).

Inoltre - non ho avuto la possibilità di provarlo, ma quanto segue potrebbe essere sconvolgente è:

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

è contrassegnato per la serializzazione, ma non ha alcun meccanismo per impostare il valore. Forse questo è il problema? Prova a rimuovere lo [ProtoMember] qui, poiché non è utile - è (per quanto riguarda la serializzazione), in gran parte un duplicato dell'indicatore [ProtoInclude(...)].

+0

Farò un tentativo e commenterò con i risultati. Grazie per aver risposto! –

+1

Passare a SerializeWithLengthPrefix ha accorciato il mio codice. :) La rimozione dell'attributo ProtoMember dalla proprietà readonly ha risolto il problema. Grazie!! –

3

Un altro modo per gestirlo è utilizzare protobuf-net per il "sollevamento pesante", ma per utilizzare la propria intestazione del messaggio. Il problema con l'elaborazione dei messaggi di rete è che possono essere suddivisi oltre i limiti. Questo in genere richiede l'utilizzo di un buffer per accumulare letture. Se si utilizza la propria intestazione, si può essere sicuri che il messaggio è lì nella sua interezza prima di consegnarlo a protobuf-net.

Ad esempio:

Per inviare

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

Per ricevere

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

Quest'ultimo metodo sarebbe "provato" a un buffer accumulatore finché c'era dati sufficienti. Una volta raggiunto il requisito relativo alle dimensioni, il messaggio può essere deserializzato.

+4

Sarebbe molto vantaggioso se protobuf-net fornisse un sovraccarico per Deserializzare con il Tipo passato, ad es. ProtoBuf.Serializer.Deserialize (Type objectType, memoryStream); qualcuno sa se questo è possibile? Ciò eviterebbe una dichiarazione di interruttore disordinato se si hanno molti tipi sconosciuti che si deserializzano. –

+1

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

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

o

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

, o con l'API v1 (che funziona ancora nella v2), 'Serializer.NonGeneric.Deserialize (...)' (prende un parametro 'Type', non un' 'argomento tipo generico) –

+0

@MarcGravell, grazie in qualche modo non ho notato la proprietà NonGeneric -) –

Problemi correlati