web-dev-qa-db-ja.com

TcpClient応答が完全に読み取られるまでループします

簡単なTCPクライアントとサーバーを作成しました。問題はクライアントにあります。

サーバーからの応答全体を読み取るのに問題があります。すべてのデータを送信できるようにするには、スレッドをスリープ状態にする必要があります。

このコードを、サーバーがデータの送信を終了するまで実行されるループに変換することを数回試みました。

// Init & connect to client
TcpClient client = new TcpClient();
Console.WriteLine("Connecting.....");
client.Connect("192.168.1.160", 9988);

// Stream string to server
input += "\n";
Stream stm = client.GetStream();
ASCIIEncoding asen = new ASCIIEncoding();
byte[] ba = asen.GetBytes(input);
stm.Write(ba, 0, ba.Length);

// Read response from server.
byte[] buffer = new byte[1024];

System.Threading.Thread.Sleep(1000); // Huh, why do I need to wait?

int bytesRead = stm.Read(buffer, 0, buffer.Length);
response = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine("Response String: "+response);

client.Close();
12
jim

ソケットの上に構築されるストリームの性質は、ソケットが閉じられるまでデータを送受信するオープンパイプラインがあることです。

ただし、クライアント/サーバーの相互作用の性質上、このパイプラインには、読み取られるコンテンツが常に含まれているとは限りません。クライアントとサーバーは、パイプラインを介してコンテンツを送信することに同意する必要があります。

。NETのStream抽象化 を取得し、それをソケットの概念にオーバーレイする場合でも、クライアントとサーバー間の合意の要件が適用されます。 Stream.Read 必要なだけ呼び出すことができますが、反対側でStreamが接続されているソケットがコンテンツを送信していない場合、呼び出しは次のように待機します。コンテンツがあります。

これがプロトコルが存在する理由です。最も基本的なレベルでは、2者間で送信される完全なメッセージを定義するのに役立ちます。通常、メカニズムは次のようなものです。

  • 読み取られるバイト数がメッセージの前に送信される長さプレフィックス付きメッセージ
  • メッセージの終わりを示すために使用される文字のパターン(これは、送信されるコンテンツによってはあまり一般的ではなく、メッセージの任意の部分が任意であるほど、これが使用される可能性は低くなります)

それはあなたが上記に固執していないと言った。 Stream.Readへの呼び出しは、実際には1024バイトが読み取られない可能性があるのに、「1024バイトを読み取る」と言っているだけです。その場合、Stream.Readの呼び出しは、それが入力されるまでブロックされます。

Thread.Sleep の呼び出しが機能する理由は、1秒が経過するまでに、Streamに1024バイトの読み取りがあり、ブロックされないためです。

さらに、本当に1024バイトを読み取りたい場合は、Stream.Readの呼び出しで1024バイトのデータが入力されるとは限りません。 Stream.Readメソッドの戻り値は、実際に読み取られたバイト数を示します。メッセージにさらに必要な場合は、Stream.Readに追加の呼び出しを行う必要があります。

Jon Skeetがこれを行う正確な方法を作成しました サンプルが必要な場合。

28
casperOne

繰り返してみてください

int bytesRead = stm.Read(buffer, 0, buffer.Length);

一方、bytesRead> 0。私が覚えているように、これは一般的なパターンです。もちろん、バッファに適切なパラメータを渡すことを忘れないでください。

2
Grigory

読み取るデータのサイズがわからないため、決定するメカニズムを設定する必要があります。 1つはタイムアウトで、もう1つは区切り文字を使用しています。

あなたの例では、読み取りとデフォルト値の「0」ミリ秒を使用するためのタイムアウトを設定していないため、1回の反復(読み取り)からすべてのデータを読み取ります。したがって、1000ミリ秒だけスリープする必要があります。 1000ミリ秒までの受信タイムアウトを使用しても同じ効果が得られます。

ソケットが両側で閉じられていると、ソケットの時間待機状況を適切に処理できないため、プレフィックスとしてデータの長さを使用することは実際の解決策ではないと思います。同じデータをサーバーに送信して、サーバーに例外を発生させることができます。プレフィックス終了文字シーケンスを使用しました。読み取りのたびに、開始文字と終了文字のシーケンスのデータを確認します。終了文字を取得できない場合は、別の読み取りを呼び出します。ただし、もちろん、これはサーバー側とクライアント側のコードを制御できる場合にのみ機能します。

1
Ahmet Arslan

先ほど書いたTCPクライアント/サーバー)で、メモリストリームに送信するパケットを生成し、そのストリームの長さを取得して、データを送信するときにプレフィックスとして使用します。クライアントが完全なパケットのために読み取る必要があるデータのバイト数を知る方法。

0
Bradley Uffner