我写了一个简单的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();
建立在套接字之上的流的本质是你有一个开放的管道来传输和接收数据,直到套接字关闭。
然而,由于客户端/服务器交互的本质,这个管道并不总是保证有内容可以读取。客户端和服务器必须同意通过管道发送内容。
当你采用。net中的Stream
抽象并将其覆盖在套接字的概念上时,客户端和服务器之间的协议需求仍然适用;你可以调用Stream.Read
所有你想要的,但如果你的Stream
连接到另一边的套接字没有发送内容,调用将只是等待,直到有内容。
这就是协议存在的原因。在最基本的层面上,它们帮助定义在双方之间发送的完整消息是什么。通常,机制是这样的:
- 长度前缀的消息,在消息 之前发送要读取的字节数
- 一种用于标记消息结束的字符模式(这取决于正在发送的内容,这种模式不太常见,消息的任何部分越任意,使用这种模式的可能性就越小)
也就是说你没有遵守上面的规定;您对Stream.Read
的调用只是说"读取1024字节",而实际上,可能没有1024字节要读取。如果是这种情况,对Stream.Read
的调用将阻塞,直到填充完成。
对Thread.Sleep
的调用可能有效的原因是,当一秒钟过去时,Stream
有1024字节可读,并且它没有阻塞。
另外,如果您真的想读取1024字节,您不能假设对Stream.Read
的调用将填充1024字节的数据。Stream.Read
方法的返回值告诉您实际读取了多少字节。如果您需要更多的消息,那么您需要额外调用Stream.Read
。
Jon Skeet写了这样做的确切方法,如果你想要一个示例。
尝试重复
int bytesRead = stm.Read(buffer, 0, buffer.Length);
while bytesRead> 0。我记得这是一种常见的模式。当然,不要忘记为buffer传递合适的参数。
你不知道你将要读取的数据的大小,所以你必须设置一个机制来决定。一个是timeout,另一个是使用分隔符。
在你的例子中,你只从一个迭代(read)中读取任何数据,因为你没有设置读取的超时,并使用默认值"0"毫秒。所以你只需要睡1000毫秒。将接收超时时间设置为1000毫秒也可以获得相同的效果
我认为使用数据长度作为前缀并不是真正的解决方案,因为当套接字被双方关闭时,套接字时间等待情况无法妥善处理。相同的数据可以发送到服务器,导致服务器得到异常。我们使用了前缀结束字符序列。每次读取后,我们检查数据的开始和结束字符序列,如果我们不能得到结束字符,我们调用另一次读取。当然,这只有在您控制服务器端和客户端代码时才有效。
在TCP客户端/服务器中,我刚刚写了我生成我想要发送到内存流的数据包,然后取该流的长度并在发送数据时将其用作前缀。这样客户端就知道一个完整的数据包需要读取多少字节的数据。