Question

J'ai 2 applications en réseau qui doivent s'envoyer des messages protobuf-net sérialisés.Je peux sérialiser les objets et les envoyer, cependant, Je n'arrive pas à comprendre comment désérialiser les octets reçus.

J'ai essayé de désérialiser avec ceci 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<Messages.BaseMessage>(ms);

Je passe un en-tête avant les octets sérialisés qui contient l'ID de type de message, que je peux utiliser dans une instruction switch géante pour renvoyer le type de sous-cass attendu.Avec le bloc ci-dessous, je reçois l'erreur :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, que 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; }
    }
}


Fixé en utilisant la suggestion de Marc Gravell.J'ai supprimé l'attribut ProtoMember des propriétés en lecture seule.Également passé à 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 objet :

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

Pour envoyer un objet :

//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);
Était-ce utile?

La solution

D'abord;pour l'utilisation du réseau, il y a SerializeWithLengthPrefix et DeserializeWithLengthPrefix qui gère la longueur pour vous (éventuellement avec une balise).Le MakeGenericMethod ça a l'air bien à 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 pile RPC :le code en attente has an override of DeserializeWithLengthPrefix cela prend (essentiellement) un Func<int,Type>, pour résoudre une balise en un type afin de faciliter la désérialisation des données inattendues à la volée.

Si le type de message concerne effectivement l'héritage entre BaseMessage et BeginRequest, alors vous n'avez pas besoin de ça ;il va toujours au type de contrat le plus élevé dans la hiérarchie et descend (en raison de certains détails de fil).

De plus, je n'ai pas eu l'occasion de le tester, mais les éléments suivants pourraient 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.C'est peut-être là le problème ?Essayez de supprimer le [ProtoMember] ici, puisque je ne le sais pas, c'est utile - c'est (en ce qui concerne la sérialisation) en grande partie un double du [ProtoInclude(...)] marqueur.

Autres conseils

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

ou

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

Une autre façon de gérer cela est d'utiliser protobuf-net pour le "gros travail", mais d'utiliser votre propre en-tête de message.Le problème du traitement des messages réseau est qu’ils peuvent être franchis au-delà des frontières.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 présent 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<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);
}

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

Cette dernière méthode serait "essayée" sur un tampon accumulateur jusqu'à ce qu'il y ait suffisamment de données.Une fois l’exigence de taille remplie, le message peut être désérialisé.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top