循环直到TcpClient响应完全读取

我写了一个简单的TCP客户端和服务器。 问题在于客户。

我在阅读服务器的整个响应时遇到了一些麻烦。 我必须让线程hibernate以允许发送所有数据。

我已经尝试过几次将此代码转换为一个循环,直到服务器完成发送数据为止。

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

构建在套接字之上的流的本质是,您有一个开放的管道,可以在套接字关闭之前传输和接收数据。

但是,由于客户端/服务器交互的性质,并不总是保证此管道上有要读取的内容。 客户端和服务器必须同意通过管道发送内容。

当您在.NET中使用Stream抽象并将其覆盖在套接字的概念上时,客户端和服务器之间的协议要求仍然适用; 您可以随意调用Stream.Read ,但如果您的Stream连接到另一端的套接字不发送内容,则呼叫将等待直到有内容。

这就是协议存在的原因。 在最基本的层面上,它们有助于定义双方之间发送的完整消息。 通常,该机制有以下几点:

  • 带有长度前缀的消息,其中在消息之前发送要读取的字节数
  • 用于标记消息结尾的字符模式(这种情况不太常见,具体取决于发送的内容,消息的任意部分越随意,使用的可能性越小)

那说你不是坚持上面的; 你对Stream.Read的调用只是说“读取1024字节”,实际上可能没有1024字节需要读取。 如果是这种情况,对Stream.Read的调用将阻塞,直到填充Stream.Read为止。

调用Thread.Sleep可能有效的原因是因为在一秒钟过去的时候, Stream有1024个字节要读取它并且它不会阻塞。

此外,如果您真的想要读取1024个字节,则不能假设对Stream.Read的调用将填充1024个字节的数据。 Stream.Read方法的返回值告诉您实际读取了多少字节。 如果您的消息需要更多内容,则需要对Stream.Read进行额外调用。

如果你想要一个样本, Jon Skeet写下了完成这个的确切方法 。

试着重复一下

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

而bytesRead> 0.这是我记忆中常见的模式。 当然不要忘记为缓冲区传递适当的参数。

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

在您的示例中,您只读取一次迭代(读取)中的任何数据,因为您没有设置读取超时并使用默认值“0”毫秒。 所以你必须只睡1000毫秒。 使用接收时间到1000毫秒可以获得相同的效果。

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

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