Pregunta

Tengo 2 aplicaciones en red que deberían enviarse mensajes protobuf-net serializados entre sí.Puedo serializar los objetos y enviarlos, sin embargo, No puedo entender cómo deserializar los bytes recibidos.

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 un encabezado antes de los bytes serializados que contiene el ID del tipo de mensaje, que puedo usar en una declaración de cambio gigante para devolver el tipo de subcass esperado.Con el bloque a continuación, 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;

Esta es la función que uso 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());
  }
}

Esta es la clase, con la clase base, que estoy serializando:

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


Fijado utilizando 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);
¿Fue útil?

Solución

Primero;para el uso de la red, hay SerializeWithLengthPrefix y DeserializeWithLengthPrefix qué longitud de mango necesita (opcionalmente con una etiqueta).El MakeGenericMethod parece bien a primera vista;y esto en realidad se relaciona muy estrechamente con la confirmación pendiente del trabajo que he estado haciendo para implementar una pila RPC:el código pendiente has an override of DeserializeWithLengthPrefix que requiere (esencialmente) un Func<int,Type>, para resolver una etiqueta en 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 necesitas esto;siempre va al tipo de contrato más alto de la jerarquía y avanza hacia abajo (debido a algunos detalles de transferencia).

Además, no he tenido la oportunidad de probarlo, pero lo siguiente podría alterarlo:

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

Está marcado para serialización, pero no tiene ningún mecanismo para establecer el valor.¿Quizás este sea el problema?Intente quitar el [ProtoMember] aquí, ya que no lo sé, esto es útil: es (en lo que respecta a la serialización), en gran medida un duplicado del [ProtoInclude(...)] marcador.

Otros consejos

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

o

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

Otra manera de manejar esto es usar protobuf-red para el "trabajo pesado", pero que usar su propio encabezado del mensaje. El problema con los mensajes de la red de procesamiento es que se puede dividir en varias. Esto requiere típicamente el uso de un tampón para acumular lee. Si utiliza su propia cabecera, puede estar seguro de que el mensaje está ahí en su totalidad antes de entregarlo fuera a protobuf-net.

A modo de 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 se puede deserializar.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top