3

私は他の人が尋ねた質問への答えを見つけるためにここに来る人の一人であり、新しい人は自分で何かを尋ねたと思いますが、2日間検索に失敗した後、自分で何かを尋ねる時が来たと判断しました. だからここにある...

SocketAsyncEventArgsを使用して、C#、.NET 4、非同期ソケットで記述された TCP サーバーとクライアントがあります。プレフィックス長付きのメッセージ フレーミング プロトコルを使用しています。全体的にはすべて問題なく動作しますが、1 つの問題に悩まされ続けています。

状況は次のとおりです(例として小さな数字を使用します):

サーバーの送信バッファー長が 16 バイトであるとします。6 バイトの長さのメッセージを送信し、 4 バイトの長さのプレフィックスを前に付けます。メッセージの合計の長さは 6+4= 10です。

クライアントはデータを読み取り、16 バイト長のバッファーを受け取ります (10 バイトのデータと 6 バイトがゼロに等しい)。

受信バッファは次のようになります: 6 0 0 0 56 21 33 1 5 7 0 0 0 0 0 0

したがって、長さのプレフィックスである最初の 4 バイトを読み取り、メッセージの長さが 6 バイトであると判断し、それも読み取り、これまでのところすべて問題ありません。次に、16-10 = 6 バイトを読み取る必要があります。それは私の長さの接頭辞であるため、それらはすべてゼロです。したがって、キープアライブ パケットとして許可される長さゼロのメッセージです。

読み取る残りのデータ: 0 0

ここで問題が「発生」します。読み取る残りの 2 バイトしかありません。長さ4 バイトのプレフィックス バッファを完了するには十分ではありません。そのため、これらの 2 バイトを読み取り、さらに着信データを待ちます。サーバーは、私がまだ長さのプレフィックスを読み取っていることを認識せず (バッファー内のすべてのゼロを読み取っているだけです)、4 バイトで正しくプレフィックスされた別のメッセージを送信します。クライアントは、サーバーが不足している 2 バイトを送信すると想定しています。クライアント側でデータを受け取り、最初の 2 バイトを読み取って、完全な 4 バイト長のバッファーを形成します。結果はそのようなものです

lengthBuffer = 新しいバイト[4]{ 0, 0, 42, 0 }

これは、 2752512 のメッセージ長に変換されます。したがって、私のコードはメッセージを完了するために次の2752512バイトを読み続けます...

したがって、すべてのメッセージ フレーミングの例で、長さゼロのメッセージがキープアライブとしてサポートされているのを見てきました。そして、私が見たすべての例は、私よりも何もしません。問題は、サーバーからデータを受信したときにどれだけのデータを読み取らなければならないかわからないことです。ゼロでバッファを部分的に埋めたので、それらのゼロは接続の反対側から送信したキープアライブである可能性があるため、すべてを読み取る必要があります。

長さ 0 のメッセージを削除し、最初の空のメッセージの後にバッファの読み取りを停止することができました。この問題は修正され、キープアライブ メカニズムにカスタム メッセージを使用する必要があります。しかし、私が見たすべてのコード例には同じ問題があるように見えるので、何かが欠けているのか、何か間違っているのか知りたいです (?)

アップデート

マーク・グラベル、あなたは私の口から言葉を引き出しました。問題がデータの送信にあることを更新しようとしていました。問題は、最初に .NET ソケットと SocketAsyncEventArgs を調査したときに、次のサンプルに出くわしたことです: http://archive.msdn.microsoft.com/nclsamples/Wiki/View.aspx?title=socket%20performance 再利用可能なバッファーのプールを使用します。単純に、事前に定義された最大クライアント接続数 (10 など) を取り、単一バッファーの最大サイズ (512 など) を取り、それらすべてに対して 1 つの大きなバッファーを作成します。したがって、512 * 10 * 2 (送受信の場合) = 10240 したがって、byte[] buff = new byte[10240]; となります。次に、接続するクライアントごとに、この大きなバッファーの一部を割り当てます。最初に接続されたクライアントは、データ読み取り操作のために最初の 512 バイトを取得し、データ送信操作のために次の 512 バイト (オフセット 512) を取得します。したがって、コードは、サイズが 512 の送信バッファーを既に割り当てていることになります (クライアントが後で BytesTransferred として受け取る正確な数)。このバッファにはデータが入力され、これらの 512 バイトのうち残りのスペースはすべてゼロとして送信されます。

奇妙なことに、この例は msdn のものです。単一の巨大なバッファーがある理由は、バッファーが固定されて GC がそれを収集できない場合などに、断片化されたヒープ メモリを回避するためです。

提供された例の BufferManager.cs からのコメント (上記のリンクを参照):

このクラスは、各ソケット I/O 操作で使用するために、分割して SocketAsyncEventArgs オブジェクトに割り当てることができる単一の大きなバッファーを作成します。これにより、バッファを簡単に再利用できるようになり、ヒープ メモリの断片化を防ぐことができます。

したがって、問題はかなり明確です。これをどのように解決すべきかについての提案は大歓迎です:)断片化されたヒープメモリについて彼らが言うことは本当ですか?「その場で」データバッファを作成しても大丈夫ですか? その場合、サーバーが数百または数千のクライアントに拡張されたときにメモリの問題が発生しますか?

4

3 に答える 3

2

問題は、読み取ったバッファ内の末尾のゼロをデータとして扱っていることだと思います。これはデータではありません。ゴミです。誰もあなたに送ったことはありません。

このStream.Read呼び出しは、実際に読み取られたバイト数を返します。バッファの残りを解釈しないでください。

問題は、サーバーからデータを受信したときにどれだけのデータを読み取らなければならないかわからないことです。

はい、そうです: からの戻り値を使用しますStream.Read

于 2012-09-06T10:04:51.457 に答える
1

これは、送信コードまたは受信コードのバグのように思えます。実際に送信されたデータとしてのみ取得BytesTransferredするか、フラグメントで到着する場合はそれよりも小さい数値を取得する必要があります。私が最初に疑問に思うのは、送信を正しくセットアップしましたか? つまり、バッファが大きすぎる場合、正しい実装は次のようになります。

args.SetBuffer(buffer, 0, actualBytesToSend);
if (!socket.SendAsync(args)) { /* whatever */ }

actualBytesToSendよりもはるかに小さい場合がありますbuffer.Length。私の最初の疑いは、あなたが次のようなことをしているということです:

args.SetBuffer(buffer, 0, buffer.Length);

したがって、実際に入力したよりも多くのデータを送信します。

強調しておきますが、送信または受信のいずれかに問題があります。少なくとも例がなければ、ここで BCL に根本的な根本的なバグがあるとは思いません。私は非同期 API を広範囲に使用しており、正常に動作します。ただし、送受信しているデータを正確に追跡する必要がありますすべてのポイント。

于 2012-09-06T10:49:56.207 に答える
0

「サーバーは、私がまだ長さのプレフィックスを読み取っていることを認識せず (バッファー内のすべてのゼロを読み取っているだけです)、4 バイトで正しくプレフィックスされた別のメッセージを送信します。」.

なんで?サーバーは、あなたが読んでいるものと読んでいないものをどのように認識していますか? サーバーがメッセージの一部を再送信した場合、それはエラーです。TCP はすでにそれを行っています。

サーバーに根本的な問題があるようです。

于 2012-09-06T10:05:20.833 に答える