Подтвердить что ты не робот

Deserialize неизвестный тип с protobuf-net

У меня есть 2 сетевых приложения, которые должны отправлять последовательные сообщения protobuf-net друг другу. Я могу сериализовать объекты и отправлять их, однако Я не могу понять, как десериализовать полученные байты.

Я попытался выполнить десериализацию с этим, и он завершился с исключением NullReferenceException.

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

Я передаю заголовок перед сериализованными байтами, который содержит идентификатор типа сообщения, который я могу использовать в гигантском операторе switch, чтобы вернуть ожидаемый тип подкласса. В следующем блоке я получаю сообщение об ошибке: 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;

Здесь функция, которую я использую для отправки сообщения по сети:

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

Этот класс, с базовым классом, я сериализую:

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


Исправлено с использованием предложения Марка Гравелла. Я удалил атрибут ProtoMember из свойств readonly. Также переключается на использование SerializeWithLengthPrefix. Вот что у меня сейчас:
[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; }
    }
}

Получить объект:

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

Чтобы отправить объект:

//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);
4b9b3361

Ответ 1

Первый; для использования в сети есть SerializeWithLengthPrefix и DeserializeWithLengthPrefix, которые обрабатывают длину для вас (опционально с тегом). MakeGenericMethod выглядит в порядке на первый взгляд; и это действительно тесно связано с ожидающим фиксацией работы, которую я делал для реализации стека RPC: ожидающий код has an override of DeserializeWithLengthPrefix, который принимает (по существу) Func<int,Type>, чтобы разрешить тег для типа облегчить десериализацию неожиданных данных "на лету".

Если тип сообщения действительно относится к наследованию между BaseMessage и BeginRequest, то вам это не нужно; он всегда переходит к наивысшему типу контракта в иерархии и работает вниз (из-за некоторых деталей провода).

Кроме того, у меня не было возможности проверить его, но следующее может его нарушить:

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

Он помечен для сериализации, но не имеет механизма для установки значения. Может, в этом и проблема? Попробуйте удалить [ProtoMember] здесь, так как я не это полезен - это (насколько это касается сериализации), в основном дубликат маркера [ProtoInclude(...)].

Ответ 2

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

или

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

Ответ 3

Еще один способ справиться с этим - использовать protobuf-net для "тяжелого подъема", но использовать свой собственный заголовок сообщения. Проблема с обработкой сетевых сообщений заключается в том, что они могут быть разбиты через границы. Обычно это требует использования буфера для накопления прочитанных данных. Если вы используете свой собственный заголовок, вы можете быть уверены, что сообщение находится целиком, прежде чем передать его в protobuf-net.

В качестве примера:

Отправить

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

Получить

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

Последний метод будет "проверен" на накопительном буфере, пока не будет достаточно данных. После выполнения требования к размеру сообщение можно десериализовать.