2011-11-06 3 views
11

私は単純なTCPクライアントとサーバーを作成しました。問題はクライアントにあります。TcpClientの応答が完全に読み取られるまでのループ

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

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

// 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(); 
+4

クライアントは、サーバがいつ終了するかを知るために何らかの方法が必要です(例えば、特別な終了シーケンスの長さプレフィックス) – SLaks

+1

StreamReader http://msdn.microsoft.com/en-us/library/systemを試してみてください。 io.streamreader.aspx – David

+4

StreamReaderはどのように役立ちますか?サーバーがいつ終了するかはわかりません。 –

答えて

25

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

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

Stream abstraction in .NETを受け取り、それをソケットの概念に重ねると、クライアントとサーバーの間の合意の要件が適用されます。 Stream.Readに電話することができますが、相手側のStreamに接続されているソケットがコンテンツを送信していない場合は、コンテンツがあるまで呼び出すだけです。

これはプロトコルが存在する理由です。彼らの最も基本的なレベルでは、2者間で送信される完全なメッセージが何であるかを定義するのに役立ちます。

  • 読み出されるバイトの数がメッセージ
  • の終わりをマークするために使用される文字のパターン前が送信され、長さ接頭辞メッセージ:通常、機構は、線に沿って何かでありますメッセージ(これは送信されるコンテンツによってはあまり一般的ではありませんが、メッセージの任意の部分を使用する可能性は低くなります)。

あなたはあなたが上記。 Stream.Readへのあなたの呼び出しは、実際には1024バイトが読み込まれないかもしれないときに、単に "read 1024 bytes"と言っているだけです。その場合、Stream.Readへの呼び出しはブロックされるまでブロックされます。

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

また、1024バイトを本当に読みたい場合は、Stream.Readへの呼び出しで1,024バイトのデータが入力されることは想定できません。 Stream.Readメソッドの戻り値は、実際に読み取られたバイト数を示します。あなたのメッセージのためにもっと必要があれば、Stream.Readへの追加の電話をする必要があります。

Jon Skeet wrote up the exact way to do thisサンプルが必要な場合は、

+3

Jon Skeetの記事は素晴らしいです、知らせてくれてありがとう。 +1 –

+0

オブジェクトの逆シリアル化は、長さ/メッセージ終了マーカーの問題を解決します。たとえば '.Deserialize (stream)'というprotobufを使うと、オブジェクト全体があるまで自動的に読み込まれます。フードの下で、protobufはオブジェクトの開始/終了を決定する独自の方法を持っています。これは、シリアライズライブラリがストリームを適切に実装していることを前提としています。ファイルストリームは多くの場合、(パフォーマンス上の理由から)バイトのフルバッファよりも少ないバイトを返すので、適切に書かれたリードループは実際のバイト数をチェックし、フルバッファが単一の '.Read'で満たされているとは仮定しません。 – AaronLS

+0

一つの訂正で、あなたは 'DeserializeWithLengthPrefix'とそれに対応する' SerializeWithLengthPrefix'を使ってください。http://stackoverflow.com/a/8901333/84206 – AaronLS

2

は、私が覚えているとしてそれは、そのための一般的なパターンですbytesRead> 0ながら

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

を繰り返すようにしてください。 もちろん、バッファに適切なパラメータを渡すことを忘れないでください。

0

私はちょうど私がメモリストリームに送信し、そのストリームの長さを取ってデータを送信するときにプレフィックスとして使用したいパケットを生成すると書いたTCPクライアント/サーバー。このようにして、クライアントは、フルパケットのために読み取る必要があるデータのバイト数を知ることができます。

0

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

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

プレフィックスは実際の解決策ではないと思うのですが、ソケットが両方のソケットとソケットのタイムウェイト状況によって閉じられると、正しく処理できないデータがサーバーに送信され、サーバーに例外が発生する可能性があります。私たちは接頭辞と終わりの文字列を使用し、読み込みのたびにデータを検査しました。すべての読み込みの後、開始文字列と終了文字列のデータをチェックします。終了文字を取得できない場合、別の読み込みを呼び出します。しかし、もちろんこれは、サーバー側とクライアント側のコードを管理している場合に機能します。

+0

この "開始/終了文字" .NETはすでにそれを処理していますか? – Seva

+1

"開始/終了文字"はカスタムです.netはそのようなものを実装していません。実際には、メッセージのプレフィックスとしてデータの長さを使用してシステムを実装し、システムは正しく動作しています。しかし、堅牢なソケットアーキテクチャを実装する必要があります。このコードを使用できます。http://stackoverflow.com/questions/869744/how-to-write-a-scalable-tcp-ip-based-server/20147995#20147995 –

関連する問題