2009-03-23 16 views
19

私は、相互にシリアル化された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); 

私は期待sublcassタイプを返すために巨大なswitch文で使用できるメッセージタイプIDが、含まれているシリアル化されたバイトの前にヘッダを渡しています。以下のブロックで、私はエラーを受け取ります: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の提案を使用してを修正しました。私はReadonlyプロパティから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); 
+0

私は言及を忘れてしまった、私はWindows上で.NET 3.5をでシリアライズとMono 2.2にデシリアライズし、各プラットフォーム上の適切ないるProtobufネットのDLLを使用していています。 –

+0

私はこれを読んで約30時間後に答えを返すようになるでしょう...現時点で実行する必要があります、申し訳ありません。 BTW - 次のリリースには非汎用ラッパーが組み込まれていますが、今のところ私のラップトップにはまだあります。 –

+0

btw - 私は自分のローカルコピーをマージしようとしているので、これを簡単にするために変更をコミットできます。私は1つの未解決のテスト失敗を持っていますが、それは新しいコードをカバーしています。 –

答えて

7

まず、ネットワーク使用の場合は、長さを扱うSerializeWithLengthPrefixDeserializeWithLengthPrefixがあります(オプションでタグ付き)。 MakeGenericMethodは一目瞭然です。これはRPCスタックを実装するためにやってきた作業の保留中のコミットと非常に密接に結びついています。つまり、(本質的に)Func<int,Type>を取る保留中のコードhas an override of DeserializeWithLengthPrefixは、タグを型に変換してデシリアライズしやすくします予期しないデータが飛行中に発生します。

メッセージタイプが実際にBaseMessageBeginRequestの間の継承に関連する場合、これは必要ありません。それは常に階層内の一番上の契約タイプになり、(いくつかの電線の詳細のために)途中で止まってしまいます。

も ​​- 私はそれをテストする機会がなかったが、次はそれをひっくり返すことがあります

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

これは、シリアル化が設定されますが、値を設定するためのメカニズムを持っていません。多分これは問題ですか?ここでは[ProtoMember]を削除してみてください。これは役に立ちません。これは、(シリアル化に関する限り)ほとんど、[ProtoInclude(...)]マーカーの複製です。

+0

私はそれにショットをつけて、結果をコメントに戻します。応答していただきありがとうございます! –

+1

SerializeWithLengthPrefixに切り替えてコードを短縮しました。 :) ReadonlyプロパティからProtoMember属性を削除すると、問題が解決しました。ありがとうございました!! –

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

受信に送信します。サイズ要件が満たされると、メッセージをデシリアライズすることができます。

+4

protobuf-netが渡された型を使用してデシリアライズに過負荷を与えた場合、非常に有益です。 ProtoBuf.Serializer.Deserialize(type objectType、memoryStream); これが可能かどうかは誰にでも分かりますか?あなたが逆シリアル化したいたくさんの未知の型を持っているなら、これは厄介なswitch文を避けるでしょう。 –

+1

RuntimeTypeModel.Default.Deserialize(Stream、null、Type); –

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

または

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

実際に、またはv1 API(それはまだv2で動作します)、 'Serializer.NonGeneric.Deserialize(...)'(generic型引数の ''ではなく 'Type'パラメータをとります) –

+0

@MarcGravell、ありがとうどうにか私はNonGenericプロパティに気付かなかった - ) –

関連する問題