Pergunta

Eu tenho 2 aplicativos de rede que devem enviar mensagens protobuf-net serializadas entre si.Posso serializar os objetos e enviá-los, porém, Não consigo descobrir como desserializar os bytes recebidos.

Tentei desserializar com isso e falhou com uma NullReferenceException.

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

Estou passando um cabeçalho antes dos bytes serializados que contém o ID do tipo de mensagem, que posso usar em uma instrução switch gigante para retornar o tipo sublcass esperado.Com o bloco abaixo, recebo o erro: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 é a função que uso para enviar uma mensagem pela rede:

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

Essa é a classe, com classe base, que estou 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; }
    }
}


Fixo usando a sugestão de Marc Gravell.Removi o atributo ProtoMember das propriedades somente leitura.Também mudei para usar SerializeWithLengthPrefix.Aqui está o que tenho agora:

[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 receber um objeto:

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

Para enviar um 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);
Foi útil?

Solução

Primeiro;para uso da rede, há SerializeWithLengthPrefix e DeserializeWithLengthPrefix qual comprimento de alça para você (opcionalmente com uma tag).O MakeGenericMethod parece bom à primeira vista;e isso, na verdade, está intimamente ligado ao commit pendente do trabalho que venho fazendo para implementar uma pilha RPC:o código pendente has an override of DeserializeWithLengthPrefix que leva (essencialmente) um Func<int,Type>, para resolver uma tag para um tipo e facilitar a desserialização imediata de dados inesperados.

Se o tipo de mensagem realmente estiver relacionado à herança entre BaseMessage e BeginRequest, então você não precisa disso;ele sempre vai para o tipo de contrato mais alto na hierarquia e desce (devido a alguns detalhes de transferência).

Além disso - não tive oportunidade de testá-lo, mas o seguinte pode estar perturbando:

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

Está marcado para serialização, mas não possui mecanismo para definir o valor.Talvez este seja o problema?Tente remover o [ProtoMember] aqui, já que não, isso é útil - é (no que diz respeito à serialização), em grande parte uma duplicata do [ProtoInclude(...)] marcador.

Outras dicas

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

ou

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

Outra maneira de lidar com isso é usar o protobuf-net para o "trabalho pesado", mas usar seu próprio cabeçalho de mensagem.O problema com o processamento de mensagens de rede é que elas podem ser quebradas além das fronteiras.Isso normalmente requer o uso de um buffer para acumular leituras.Se você usar seu próprio cabeçalho, pode ter certeza de que a mensagem está lá na íntegra antes de entregá-la ao protobuf-net.

Como um exemplo:

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

Receber

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

O último método seria “tentado” em um buffer acumulador até que houvesse dados suficientes.Depois que o requisito de tamanho for atendido, a mensagem poderá ser desserializada.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top