循环直到 TcpClient 响应完全读取 [重复]
Posted
技术标签:
【中文标题】循环直到 TcpClient 响应完全读取 [重复]【英文标题】:Loop until TcpClient response fully read [duplicate] 【发布时间】:2011-12-23 01:58:14 【问题描述】:我写了一个简单的 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();
【问题讨论】:
客户端需要某种方式来确定服务器何时完成(例如,特殊终止序列的长度前缀) 尝试使用 StreamReader msdn.microsoft.com/en-us/library/system.io.streamreader.aspx StreamReader 将如何提供帮助?也不知道服务器什么时候结束。 【参考方案1】:在套接字之上构建的流的本质是,您有一个开放的管道,可以在套接字关闭之前传输和接收数据。
但是,由于客户端/服务器交互的性质,并不总是保证此管道上有可供读取的内容。客户端和服务器必须同意通过管道发送内容。
当你把Stream
abstraction in .NET覆盖在sockets的概念上时,客户端和服务器之间协议的要求仍然适用;您可以随意调用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如果你想要样品。
【讨论】:
Jon Skeet 的文章非常棒,感谢您的告知。 +1 对象反序列化为长度/消息结束标记问题提供了解决方案。以 protobuf 为例,.Deserialize<SomeMessageType>(stream)
将自动读取,直到它拥有整个对象。在底层,protobuf 有自己的方法来确定对象的开始/结束。这假设序列化库正确实现了流。文件流也经常返回小于完整缓冲区的字节数(出于性能原因),因此任何正确编写的读取循环都应检查实际读取的字节数,而不是假设完整缓冲区已填充到单个 .Read
。
一个更正,你应该使用DeserializeWithLengthPrefix
和它的SerializeWithLengthPrefix
对应的***.com/a/8901333/84206
当您编写此答案时,情况可能有所不同,但 Stream.Read
在读取请求的字节数之前不会阻塞。第三个参数记录为“要从当前流中读取的最大字节数。”,其返回值记录为“总字节数读入缓冲区的字节数。如果当前不可用那么多字节,则该字节数可能小于请求的字节数;如果已到达流的末尾,则该字节数为零 (0)。"【参考方案2】:
尝试重复
int bytesRead = stm.Read(buffer, 0, buffer.Length);
而 bytesRead > 0。据我所知,这是一种常见的模式。 当然不要忘记为缓冲区传递适当的参数。
【讨论】:
【参考方案3】:您不知道要读取的数据大小,因此您必须设置一个机制来决定。一个是超时,另一个是使用分隔符。
在您的示例中,您仅从一次迭代(读取)中读取任何数据,因为您没有设置读取超时并使用默认值“0”毫秒。所以你只需要睡 1000 毫秒。使用接收超时到 1000 毫秒可以获得相同的效果。
我认为使用长度数据作为前缀并不是真正的解决方案,因为当双方都关闭套接字时,套接字时间等待情况无法正确处理。相同的数据可以发送到服务器并导致服务器异常。我们使用前缀结束字符序列。每次读取后,我们检查数据的开始和结束字符序列,如果我们无法获得结束字符,我们调用另一个读取。但当然,这只有在您控制服务器端和客户端代码时才有效。
【讨论】:
我们真的需要自己实现这个“开始/结束字符”,还是 .NET 已经处理了它? “开始/结束字符”是自定义的,因此 .net 没有实现这些东西。实际上,我们使用长度数据作为消息前缀来实现系统,并且系统运行正常。但是你必须实现健壮的套接字架构。您可以使用此代码***.com/questions/869744/…【参考方案4】:在我刚刚编写的 TCP 客户端/服务器中,我生成了要发送到内存流的数据包,然后获取该流的长度并在发送数据时将其用作前缀。这样,客户端就知道它需要读取多少字节的数据才能读取一个完整的数据包。
【讨论】:
以上是关于循环直到 TcpClient 响应完全读取 [重复]的主要内容,如果未能解决你的问题,请参考以下文章
python 如何循环读取字典中的keys所对应的values