Вопрос

У меня есть 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);

Я передаю заголовок перед сериализованными байтами, который содержит идентификатор типа сообщения, который я могу использовать в операторе giant switch для возврата ожидаемого типа sublcass.С помощью приведенного ниже блока я получаю сообщение об ошибке:Система.Отражение.Исключение 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 из свойств, доступных только для чтения.Также перешел на использование 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);
Это было полезно?

Решение

Первый;для использования в сети существует 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(...)] маркер.

Другие советы

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

или

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

Другой способ справиться с этим - использовать 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);
        }
    }
}

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top