NetworkStream不会刷新数据。

3
我正在使用套接字编写一个简单的聊天程序。当我发送长消息、清空流并随后发送短消息时,长消息的结尾会附加到短消息上。具体表现如下:
发送 "aaasdsd"
接收 "aaasdsd"
发送 "bb"
接收 "bbasdsd"
通过调试,我发现 Flush 方法并没有清除流中的所有数据。根据 MSDN 的说法,这是因为 NetworkStream 没有缓冲。在这种情况下,我该怎么清除流呢?我可以在每条消息后跟随一个同样长度的空消息(由 \0 字符组成),但我认为这样做不正确,而且它会破坏一些我需要的功能。

1
我怀疑你诊断问题出现了错误 - 我认为,你在接收端重新使用了缓冲区,并且没有检查Receive的返回值以查看实际接收到了多少字节。话虽如此,我也同意@Luaans的答案 - 如果你想要消息,那就由你来实现它们(或选择一个已经提供这种抽象的更高级别的网络库)。 - Damien_The_Unbeliever
我曾经遇到过类似的问题,后来发现我需要在发送的数据末尾添加回车符。可能这是服务器的要求,我不确定,但这样做确实有效。 - Quintonn
1个回答

9
TCP不是这样工作的。就是这么简单。
TCP是一种基于流的协议。这意味着你不应该像UDP那样将其视为基于消息的协议。如果你需要通过TCP发送消息,你必须在TCP之上添加自己的消息协议。
你尝试做的是发送两个独立的消息,并在另一端接收两个独立的消息。这在UDP(基于消息)上可以正常工作,但在TCP上不行,因为TCP是没有组织的流。
所以,Flush完全可以正常工作。只是无论你在一侧调用多少次Flush,在另一侧调用多少次单独的Send,每个Receive都会获取尽可能多的数据来适应其缓冲区,而不考虑其他侧的Send。
你设计的解决方案(几乎是正确的 - 只需使用单个\0分隔字符串)实际上是处理此问题的一种正确方法。通过这样做,你又在流的顶部处理消息了。这称为消息帧 - 它允许你区分各个消息。在你的情况下,你在消息之间添加了分隔符。想象一下在文件中写入相同的数据 - 再次,你需要自己的某种方式来分隔各个消息(例如使用结束行)。
处理消息帧的另一种方法是使用长度前缀 - 在发送字符串本身之前,发送它的长度。然后,在另一端读取时,你就知道在字符串之间应该始终有一个长度前缀,因此读取器知道消息何时结束。
处理固定长度数据的另一种方法可能对你的情况没有多大用处。所以一个消息将始终正好是100个字节,例如。当与预定义的消息类型结合使用时,这非常强大 - 所以消息类型1将包含恰好两个整数,表示某些坐标,例如。
无论哪种情况,你都需要在接收端进行自己的缓冲。这是因为(如你已经看到的)单个接收可以一次读取多个消息,并且同时不能保证一次读取整个消息。编写你自己的网络编程实际上非常棘手 - 除非你正在做这个来真正学习网络编程,否则我建议使用一些现成的技术 - 例如Lindgren(一个很好的网络库,针对游戏进行了优化,但也适用于一般网络)或WCF。对于聊天系统,简单的HTTP(特别是具有双向WebSockets的)可能也足够了。
正如Damien所指出的那样,你的代码似乎还有另一个问题——你似乎忽略了Read的返回值。返回值告诉你实际读取了多少字节。由于接收端有一个固定大小的持久缓冲区(显然),这意味着每个在你刚刚读取后的字节仍将包含旧数据。要解决这个问题,只需确保你只使用Read返回的字节数即可。此外,由于这似乎表明你完全忽略了Read的返回值,请确保正确处理Read返回0的情况,这意味着另一端已经优雅地关闭了它的连接,接收方也应该如此做。

Flush工作得很好,但它到底是做什么的呢?在NetworkStream中它有什么用途? - Irdes
@Irdes 嗯,其实没有什么。缓冲处理比 NetworkStream 更低 - TCP 协议自己处理缓冲。只是 Flush 也不会有帮助,你的问题在于接收端,而不是发送端。在某些情况下,刷新 TCP 流仍然很有用(TCP 设计用于高延迟、低吞吐量网络),但对于您的用例来说并没有太大的区别。例如,除非您发送足够的数据,否则 TCP 默认每 200 毫秒只发送一次数据(在 Windows 上)。 - Luaan
使用TcpClient处理非常简单 - 只需将TcpClient.NoDelay设置为true即可。话虽如此,只有在您希望将延迟降至最低时才有意义 - 而TCP的延迟始终比UDP高。 - Luaan
1
这里甚至不是SendReceive调用之间的不匹配 - OP调用了Receive并使用8个字符填充了他们的缓冲区。 然后,他们再次使用相同的缓冲区调用Receive并接收下两个字符。 但是因为他们没有清除缓冲区,并且因为他们没有检查Receive的返回值,所以他们在缓冲区中查看并看到8个字符(2个新字符和6个未被覆盖的字符)。 - Damien_The_Unbeliever
@Damien_The_Unbeliever 啊,对了。我没注意到那个 :) 我会把它加到我的答案中。 - Luaan
@Irdes,另外,我刚刚完成了一个简单的聊天应用程序示例,作为我的网络示例的一部分。它远非完美(错误处理不足,设计重点在于使示例清晰,而不是成为真正应用程序的良好基础),但它可能会对您有所帮助-https://github.com/Luaancz/Networking/tree/master/Networking%20Part%202。 - Luaan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接