c# - Deserializar tipo desconocido con protobuf-net
serialization protocol-buffers (3)
Tengo 2 aplicaciones en red que deben enviarse mensajes protobuf-net serializados entre sí. Puedo serializar los objetos y enviarlos, sin embargo, no puedo averiguar 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 contienen el ID de tipo de mensaje, que puedo usar en una instrucción de conmutador gigante para devolver el Tipo de sub-clase 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;
Aquí está 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());
}
}
Esta la clase, con la clase base, 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; }
}
}
Se corrigió con la sugerencia de Marc Gravell. Quité 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);
Otra forma de manejar esto es usar protobuf-net para el "trabajo pesado", pero usar su propio encabezado de mensaje. El problema con el procesamiento de los mensajes de la red es que se pueden dividir entre límites. Esto normalmente requiere el uso de un búfer para acumular lecturas. Si usa su propio encabezado, puede estar seguro de que el mensaje está allí en su totalidad antes de entregarlo a protobuf-net.
Como ejemplo:
Mandar
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 "probado" en un búfer acumulador hasta que hubiera suficientes datos. Una vez que se cumple el requisito de tamaño, el mensaje se puede deserializar.
Primero; para el uso de la red, hay SerializeWithLengthPrefix
y DeserializeWithLengthPrefix
que manejan la longitud por ti (opcionalmente con una etiqueta). El MakeGenericMethod
ve bien a primera vista; y esto en realidad se relaciona muy estrechamente con el compromiso pendiente del trabajo que he estado realizando para implementar una pila RPC: el código pendiente has an override of DeserializeWithLengthPrefix
que toma (esencialmente) una función Func<int,Type>
, para resolver una etiqueta a un tipo para facilitar la deserialización de datos inesperados sobre la marcha.
Si el tipo de mensaje realmente se relaciona con la herencia entre BaseMessage
y BeginRequest
, entonces no necesita esto; siempre va al tipo de contrato más alto en la jerarquía y se reduce (debido a algunos detalles de cableado).
Además, no he tenido la oportunidad de probarlo, pero lo siguiente podría estar alterándolo:
[ProtoMember(1)]
public override UInt16 messageType
{
get { return 1; }
}
Está marcado 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 [ProtoInclude(...)]
.
Serializer.NonGeneric.Deserialize(Type, Stream); //Thanks, Marc.
o
RuntimeTypeModel.Default.Deserialize(Stream, null, Type);