2011-11-06 43 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在另一端连接的套接字没有发送内容,则该通话将等待直到有内容。

这就是协议存在的原因。在他们最基本的层面上,他们帮助确定双方发送的完整信息是什么。通常,该机构是沿着线的东西:其中待读取的字节数被发送

  • 长度前缀消息之前该消息
  • 用来标记的结束字符的图案一个消息(这是不常见的,取决于正在发送的内容,消息的任意部分越可能是任意部分,这个可能性就越小)

这就是说你不遵守以上;您拨打Stream.Read的电话实际上只是说“读取1024个字节”,可能不会有1024个字节需要读取。如果是这种情况,那么电话Stream.Read将被阻止,直到填充。

Thread.Sleep的调用可能起作用的原因是因为在秒钟过去的时候,Stream上有1024个字节需要读取并且不会被阻止。

此外,如果您确实想要读取1024个字节,则无法假定对Stream.Read的调用将填充1024个字节的数据。 Stream.Read方法的返回值告诉您实际读取了多少个字节。如果您需要更多信息,则需要拨打Stream.Read

,如果你想有一个样品Jon Skeet wrote up the exact way to do this

+3

乔恩Skeet的文章是太棒了,谢谢你知道。 +1 –

+0

对象反序列化为消息的长度/结束标记问题提供了一种解决方案。以protobuf为例,'.Deserialize (stream)'会自动读取,直到它具有整个对象。底层,protobuf有自己的方式来确定对象的开始/结束。这假定序列化库正确地实现了流。文件流通常返回的字节数少于完整缓冲区(出于性能原因),因此任何正确写入的读取循环都应该检查读取的实际字节数,而不是假设整个缓冲区被填充到一个'.Read'中。 – AaronLS

+0

一个修正,你应该使用'DeserializeWithLengthPrefix'及其'SerializeWithLengthPrefix'对口按http://stackoverflow.com/a/8901333/84206 – AaronLS

2

尝试重复

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

而bytesRead> 0这是一个常见的模式,因为我记得。 当然,不要忘记为缓冲区传递适当的参数。

0

在我写的TCP客户端/服务器中,我生成了我想要发送到内存流的数据包,然后获取该流的长度,并在发送数据时将其用作前缀。这样客户端就知道它需要为完整的数据包读取多少个字节的数据。

0

你不知道的数据你会读,所以你必须设置一个机制来决定大小。一个是超时,另一个是使用分隔符。

在您的例子中,你阅读只是一个迭代(读)的任何数据,因为你不设置超时时间用于阅读和使用默认值这就是“0” milisecond。所以你不得不睡1000毫秒。使用接收时间达到1000毫秒时效果相同。

我认为使用长度的数据作为前缀不是真正的解决方案,因为当套接字被双方和套接字关闭时,等待情况不能正确处理相同的数据可以发送到服务器并让服务器得到异常。我们使用前缀和结束字符序列并在每次读取后检查数据。在每次读取之后,我们检查开始和结束字符序列的数据,如果我们不能得到结束字符,我们会调用另一次读取。但是,如果您拥有服务器端和客户端代码的控制权,当然这是有效的。

+0

我们是否真的需要自己实现这个”开始/结束字符“,或者.NET已经处理它? – Seva

+1

“开始/结束字符”是自定义的,因此.net不会实现这样的事情。实际上,我们迫使一个系统使用长度的数据作为消息前缀并且系统正常工作。但是你必须实现可靠的套接字架构。您可以使用此代码http://stackoverflow.com/questions/869744/how-to-write-a-scalable-tcp-ip-based-server/20147995#20147995 –