2009-03-23 27 views
19

Tengo 2 aplicaciones en red que deben enviar mensajes protobuf-net serializados entre sí. Puedo serializar los objetos y enviarlos, sin embargo, No puedo entender cómo deserializar los bytes recibidos.Deserializar el tipo desconocido con protobuf-net

Intenté deserializar con esto y falló 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); 

estoy pasando una cabecera antes de los bytes serializados que contiene el ID de tipo de mensaje, que puedo utilizar en una sentencia switch gigante para devolver el esperado Tipo sublcass. Con el bloque siguiente, recibo el error: 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; 

Aquí es la función que utilizo para enviar un mensaje a través de la red:

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

Este la clase, la clase base, estoy serializar:

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


Se corrigió el usando la sugerencia de Marc Gravell. Eliminé el atributo ProtoMember de las propiedades de solo lectura. También cambió a usar SerializeWithLengthPrefix. Esto es lo que tengo ahora:

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

Para recibir un objeto:

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

Para enviar un objeto:

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

me olvidó mencionar, estoy serialización en .NET 3.5 en Windows y deserializar en Mono 2.2 y estoy usando los archivos DLL-protobuf neta apropiadas en cada plataforma. –

+0

Volveré para leer esto y publicar una respuesta en aproximadamente media hora ... tengo que salir corriendo en este momento, lo siento. Por cierto, el próximo lanzamiento tiene envolturas no genéricas integradas, todavía en mi computadora portátil en este momento. –

+0

por cierto - Estoy trabajando en la fusión de mi copia local, por lo que puedo confirmar los cambios para que sea más fácil. Tengo una falla de prueba excepcional, pero eso cubre el nuevo código, por lo que estoy contento de confirmarlo (marcado como ignorado) si ayuda. –

Respuesta

7

Primera; para el uso de la red, hay SerializeWithLengthPrefix y DeserializeWithLengthPrefix que manejan la longitud para usted (opcionalmente con una etiqueta). El MakeGenericMethod se ve bien a primera vista; y esto realmente se relaciona muy de cerca con el compromiso pendiente del trabajo que he estado haciendo para implementar una pila RPC: el código pendiente has an override of DeserializeWithLengthPrefix que toma (esencialmente) un Func<int,Type>, para resolver una etiqueta a un tipo para que sea más fácil deserializar datos inesperados sobre la marcha.

Si el tipo de mensaje realmente se relaciona con la herencia entre BaseMessage y BeginRequest, entonces no lo necesita; siempre va al tipo de contrato más alto en la jerarquía y se abre camino hacia abajo (debido a algunos detalles del cable).

También - no he tenido la oportunidad de probarlo, pero lo siguiente podría ser molesto que:

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

se marca para la serialización, pero no tiene ningún mecanismo para establecer el valor. Tal vez este es el problema? Intente eliminar el [ProtoMember] aquí, ya que no es útil, es (en lo que respecta a la serialización), en gran parte, un duplicado del marcador [ProtoInclude(...)].

+0

Lo probaré y comentaré con los resultados. ¡Gracias por responder! –

+1

Cambiar a SerializeWithLengthPrefix acorta mi código. :) Al eliminar el atributo ProtoMember de la propiedad readonly, se solucionó el problema. ¡¡Gracias!! –

3

Otra forma de manejar esto es usar protobuf-net para el "levantamiento pesado", pero para usar su propio encabezado de mensaje. El problema con el procesamiento de mensajes de red es que se pueden romper los límites. Esto generalmente requiere el uso de un buffer para acumular lecturas. Si usa su propio encabezado, puede estar seguro de que el mensaje está allí en su totalidad antes de entregárselo a protobuf-net.

Como un ejemplo:

Para enviar

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

Para recibir

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

El último método sería "trató" en un tampón de acumulador hasta que no había datos suficientes. Una vez que se cumple el requisito de tamaño, el mensaje puede deserializarse.

+4

Sería muy beneficioso si protobuf-net proporcionara una sobrecarga para deserializar con el tipo pasado, p. Ej. ProtoBuf.Serializer.Deserialize (Tipo objectType, memoryStream); ¿Alguien sabe si esto es posible? Esto evitaría una declaración de cambio desordenada si tiene muchos tipos desconocidos que desea deserializar –

+1

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

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

o

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

de hecho, o con la API v1 (que todavía funciona en v2), 'Serializer.NonGeneric.Deserialize (...)' (toma un parámetro 'Type', no un argumento de tipo genérico' ') –

+0

@MarcGravell, gracias de alguna manera no noté la propiedad NonGeneric -) –

Cuestiones relacionadas