Désérialiser un type inconnu avec protobuf-net

J’ai 2 applications en réseau qui devraient envoyer des messages sérialisés protobuf-net les uns aux autres. Je peux sérialiser les objects et les envoyer, mais je ne peux pas comprendre comment désérialiser les octets reçus .

J’ai essayé de désérialiser avec cela et cela a échoué avec une NullReferenceException.

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

Je passe un en-tête avant les octets sérialisés contenant l’ID de type de message, que je peux utiliser dans une instruction switch géant pour renvoyer le type de sous-classe attendu. Avec le bloc ci-dessous, le message d’erreur suivant s’affiche: 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; 

Voici la fonction que j’utilise pour envoyer un message sur le réseau:

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

C’est la classe, avec la classe de base, je sérialise:

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

Correction de la suggestion de Marc Gravell. J’ai supprimé l’atsortingbut ProtoMember des propriétés en lecture seule. Également basculé vers l’utilisation de SerializeWithLengthPrefix. Voici ce que j’ai maintenant:

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

Pour recevoir un object:

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

Pour envoyer un object:

 //where "this.Ssl" is an SslStream and "message" can be anything that // inherits from BaseMessage. ProtoBuf.Serializer.SerializeWithLengthPrefix( this.Ssl, message, ProtoBuf.PrefixStyle.Base128); 

Premier; pour l’utilisation du réseau, il existe SerializeWithLengthPrefix et DeserializeWithLengthPrefix qui gèrent la longueur pour vous (éventuellement avec une balise). La MakeGenericMethod semble correcte à première vue; et cela est en fait très étroitement lié à la validation en attente du travail que j’ai effectué pour implémenter une stack RPC: le code en attente has an override of DeserializeWithLengthPrefix qui prend (essentiellement) un Func , pour résoudre une balise à un type facilitant la désérialisation à la volée de données inattendues.

Si le type de message concerne réellement l’inheritance entre BaseMessage et BeginRequest , vous n’en avez pas besoin. il passe toujours au type de contrat le plus haut dans la hiérarchie et fonctionne de manière descendante (en raison de certains détails de connexion).

Aussi – je n’ai pas eu la chance de le tester, mais ce qui suit pourrait le perturber:

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

Il est marqué pour la sérialisation, mais ne dispose d’aucun mécanisme pour définir la valeur. Peut-être que c’est le problème? Essayez de supprimer le [ProtoMember] ici, car ce n’est pas utile – c’est (en ce qui concerne la sérialisation) un duplicata du [ProtoInclude(...)] .

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

ou

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

Une autre façon de gérer cela consiste à utiliser protobuf-net pour le «soulèvement lourd», mais en utilisant votre propre en-tête de message. Le problème avec le traitement des messages réseau est qu’ils peuvent être dépassés. Cela nécessite généralement l’utilisation d’un tampon pour accumuler les lectures. Si vous utilisez votre propre en-tête, vous pouvez être sûr que le message est dans son intégralité avant de le transmettre à protobuf-net.

Par exemple:

Envoyer

 using (System.IO.MemoryStream ms = new System.IO.MemoryStream()) { MyMessage message = new MyMessage(); ProtoBuf.Serializer.Serialize(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); } 

Recevoir

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

Cette dernière méthode serait “essayée” sur une mémoire tampon d’accumulateur jusqu’à ce qu’il y ait suffisamment de données. Une fois que la taille requirejse est remplie, le message peut être désérialisé.