23

シリアル化された protobuf-net メッセージを相互に送信する必要がある 2 つのネットワーク アプリがあります。オブジェクトをシリアル化して送信できますが、受信したバイトを逆シリアル化する方法がわかりません

これでデシリアライズしようとしましたが、NullReferenceException で失敗しました。

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

メッセージ タイプ ID を含むシリアル化されたバイトの前にヘッダーを渡しています。これを巨大な switch ステートメントで使用して、予想される sublcas Type を返すことができます。以下のブロックでは、次のエラーが表示されます: 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; }
    }
}


Marc Gravell の提案を使用して修正しました。読み取り専用プロパティから 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);
4

3 に答える 3

4

これを処理する別の方法は、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);
        }
    }
}

後者の方法は、十分なデータが得られるまでアキュムレータ バッファで「試行」されます。サイズ要件が満たされると、メッセージを逆シリアル化できます。

于 2009-05-05T19:04:13.480 に答える