7

BinaryReader.ReadChars() メソッドの問題であると思われる問題に遭遇しました。生のソケット NetworkStream に BinaryReader をラップすると、ストリームが破損し、読み取られているストリームが同期しなくなることがあります。問題のストリームには、バイナリ シリアル化プロトコルのメッセージが含まれています。

私はこれを次のように追跡しました

  • Unicode 文字列 (Encoding.BigEndian を使用してエンコードされた) を読み取るときにのみ発生します。
  • 問題の文字列が 2 つの tcp パケットに分割されている場合にのみ発生します (wireshark を使用して確認)。

何が起こっているかは次のとおりだと思います(以下の例のコンテキストで)

  • BinaryReader.ReadChars() が呼び出され、3 文字を読み取るように要求されます (文字列の長さは、文字列自体の前にエンコードされます)。
  • 最初のループは、ネットワーク ストリームから 6 バイト (残りの 3 文字 * 2 バイト/文字) の読み取りを内部的に要求します。
  • ネットワーク ストリームには 3 バイトしかありません
  • ローカル バッファに読み込まれた 3 バイト
  • Decoderに渡されるバッファ
  • デコーダーは 1 文字をデコードし、他のバイトを独自の内部バッファーに保持します。
  • 2 番目のループは、4 バイトの読み取りを内部的に要求します。(残りの 2 文字 * 2 バイト/文字)
  • ネットワーク ストリームには 4 バイトすべてが使用可能です
  • ローカル バッファに読み込まれた 4 バイト
  • Decoderに渡されるバッファ
  • デコーダーは 2 char をデコードし、残りの 4 番目のバイトを内部で保持します。
  • 文字列のデコードが完了しました
  • シリアライゼーション コードは、次の項目の非整列化を試み、ストリームの破損が原因で鳴きます。

    char[] buffer = new char[3];
    int charIndex = 0;
    
    Decoder decoder = Encoding.BigEndianUnicode.GetDecoder();
    
    // pretend 3 of the 6 bytes arrives in one packet
    byte[] b1 = new byte[] { 0, 83, 0 };
    int charsRead = decoder.GetChars(b1, 0, 3, buffer, charIndex);
    charIndex += charsRead;
    
    // pretend the remaining 3 bytes plus a final byte, for something unrelated,
    // arrive next
    byte[] b2 = new byte[] { 71, 0, 114, 3 };
    charsRead = decoder.GetChars(b2, 0, 4, buffer, charIndex);
    charIndex += charsRead;
    

ルートは、各ループで charsRemaining * bytes/char を使用して必要な残りのバイトを計算する .NET コードのバグだと思います。Decoder に余分なバイトが隠されているため、この計算は 1 だけオフになる可能性があり、入力ストリームから余分なバイトが消費されます。

問題の .NET フレームワーク コードは次のとおりです。

    while (charsRemaining>0) { 
        // We really want to know what the minimum number of bytes per char 
        // is for our encoding.  Otherwise for UnicodeEncoding we'd have to
        // do ~1+log(n) reads to read n characters. 
        numBytes = charsRemaining;
        if (m_2BytesPerChar)
            numBytes <<= 1;

        numBytes = m_stream.Read(m_charBytes, 0, numBytes);
        if (numBytes==0) { 
            return (count - charsRemaining); 
        } 
        charsRead = m_decoder.GetChars(m_charBytes, 0, numBytes, buffer, index);

        charsRemaining -= charsRead;
        index+=charsRead;
    }

これがバグなのか、単に API の誤用なのか、完全にはわかりません。この問題を回避するには、必要なバイト数を自分で計算して読み取り、関連する Encoding.GetString() を介して byte[] を実行するだけです。ただし、これは UTF-8 などでは機能しません。

これについて人々の考えを聞いて、私が何か間違ったことをしているのかどうかに興味を持ってください. そしておそらく、次の人は数時間/数日の退屈なデバッグを節約できます.

編集:接続追跡アイテムを接続するために投稿されました

4

4 に答える 4

3

で言及された問題を再現しましたBinaryReader.ReadChars

開発者は、ストリームやデコーダーなどを構成するときに常に先読みを考慮する必要がありますが、BinaryReaderそのクラスはさまざまなタイプのデータで構成されるデータ構造を読み取ることを目的としているため、これはかなり重大なバグのように思えます。この場合、ReadCharsそのバイトを失うことを避けるために、読み取った内容をより保守的にする必要があることに同意します。

を直接使用する回避策に問題はありません。Decoder結局のところ、それがReadChars舞台裏で行われているのです。

Unicode は単純なケースです。任意のエンコーディングについて考えると、バイト数ではなく文字数を渡すときに正しいバイト数が消費されることを保証する汎用的な方法は実際にはありません (さまざまな長さの文字や不正な形式の入力が含まれる場合を考えてみてください)。このため、BinaryReader.ReadChars特定のバイト数の読み取りを回避することで、より堅牢で一般的な解決策が得られます。

http://connect.microsoft.com/visualstudioを介して Microsoft の注意を喚起することをお勧めします。

于 2009-11-26T16:36:12.293 に答える
1

これは、私自身の質問の1つ(HttpResponseStreamからの読み取りが失敗する)を思い出させます。HTTP応答ストリームから読み取るときに、StreamReaderがストリームの最後に到達したと判断して、パーサーが予期せず爆破するという問題がありました。

マークがあなたの問題について提案したように、私は最初にプリバッファリングを試しましたが、MemoryStreamこれはうまく機能しますが、(特にネットワーク/ウェブから)読み取る大きなファイルがある場合は、何か便利なことをする前に長い間待たなければならない可能性がありますそれ。私は最終的に、Readメソッドをオーバーライドし、ReadBlockメソッドを使用してそれらを定義するTextReaderの独自の拡張機能を作成することに決めました(ブロッキング読み取りを実行します。つまり、要求した文字数を正確に取得できるまで待機します)。

BinaryReader.Readあなたの問題はおそらく私のように、たとえば(http://msdn.microsoft.com/en-のドキュメントを見ると、Readメソッドが要求した文字数を返すことが保証されていないという事実によるものです。 us / library / ms143295.aspx)メソッドには、次のように表示されます。

戻り値
タイプ:System..::。Int32
バッファに読み込まれた文字数。これは、その数のバイトが使用できない場合は要求されたバイト数より少ない場合があり、ストリームの終わりに達した場合はゼロになる場合があります。

BinaryReaderにはTextReaderのようなReadBlockメソッドがないため、自分で位置を監視するか、Marcの事前キャッシュを監視するという独自のアプローチを取るだけです。

于 2009-11-26T17:32:22.850 に答える
1

面白い; 「接続」でこれを報告できます。その場しのぎの方法として、 でラッピングを試すこともできますがBufferredStream、これはひび割れの紙張りだと思います (まだ発生する可能性はありますが、頻度は低くなります)。

もちろん、もう 1 つのアプローチは、メッセージ全体 (ただし、ストリーム全体ではない) を事前にバッファリングすることです。次に、次のようなものから読み取りMemoryStreamます-ネットワークプロトコル論理的な(そして理想的には長さがプレフィックスされ、大きすぎない)メッセージがあると仮定します。その後、デコード中にすべてのデータが利用可能になります。

于 2009-11-26T16:34:05.247 に答える
0

Unity3D/Mono atm を使用していますが、ReadChars メソッドにはさらに多くのエラーが含まれている可能性があります。次のような文字列を作成しました。

mat.name = new string(binaryReader.ReadChars(64));

mat.name正しい文字列も含まれていましたが、そのに文字列を追加できました。文字列の後のすべてが消えました。String.Format でも。これまでの私の解決策は、ReadChars メソッドを使用していませんが、データをバイト配列として読み取り、文字列に変換します。

byte[] str = binaryReader.ReadBytes(64);
int lengthOfStr = Array.IndexOf(str, (byte)0); // e.g. 4 for "clip\0"
mat.name = System.Text.ASCIIEncoding.Default.GetString(str, 0, lengthOfStr);
于 2014-08-17T23:51:24.320 に答える