【问题标题】:C# NetworkStream - distinguish closed socket from 0-byte array on ReadC# NetworkStream - 在读取时将关闭的套接字与 0 字节数组区分开来
【发布时间】:2024-01-20 13:40:01
【问题描述】:

[已解决,问题基于不正确的假设]

在使用 TCP 时,我遇到了 NetworkStream.Read 在两种不同情况下返回值 0 的问题,我很难区分。

一些背景知识 - 我有一个有效的客户端-服务器解决方案,通过 TCP 使用长度前缀消息进行通信。但是,由于大多数通信(除了一些初始消息交换)是从客户端到服务器的,因此服务器没有很好的方法来知道客户端是否仍然连接。找出这个问题的一种方法是不时向客户发送一些东西,这就是我决定做的事情。

我知道我可以在我的协议中添加一个专用的“ping”消息,并在客户端中简单地忽略它,但我也在测试其他可能性。我尝试过的一件事是向客户端发送一个空字节数组,如下所示:

networkStream.Write(new byte[0], 0, 0);

一切看起来都不错,它似乎正在发送一个没有数据的 TCP 数据包......但是!我的客户端代码确实不时需要来自服务器的数据,因此它有一个阻塞 networkStream.Read 的线程,如下所示:

int bytesRead = networkStream.Read(buffer, 0, 4);
if (bytesRead == 0)
    break;

根据文档,如果另一端关闭连接,则 Socket.Read(或 NetworkStream.Read)返回 0。这是真的,但在我的情况下,在发送空字节数组后,Read(...) 也返回 0。

到目前为止,我无法区分这两种情况。在这两种情况下(连接关闭和空字节数组),读取后检查的 Socket.Connected 为 true。有没有其他方法可以解决这个问题?

再一次,我知道发送这个空数组几乎与为此添加新类型的消息相同。我不是在这里寻求解决方案...只是想知道 .NET 的 Socket 是否可以区分空字节数组和连接关闭。

编辑: 我很抱歉用一个问题打扰大家,这最终是基于错误的假设。我的测试没有在我的生产代码上完成,而且太草率了。这导致我得出错误的结论。 基本上,我正在测试的是,如果我在一端执行 Write(new byte[0]...),另一端的 Read(...) 将返回 0。确实如此,但不是由于发送。我用来测试的 TcpClient 超出了范围,这(我假设)导致它被 GC 释放,因此连接关闭,导致读取返回 0。我确实重复了测试,但 TcpClient 没有被释放/丢失,并且无论我发送多少个空字节数组,Read 都不会返回任何内容。

起初,我预计 Nagle 的算法会搞砸,但在这种情况下并非如此 - 1 字节数组会毫无延迟地到达,因为我在 localhost 上进行测试。我可能会使用套接字进行不同的测试,并明确禁用 Nagle 的算法,但我认为这不会改变任何事情。

现在我只需要检查发送这样一个数组是否真的可以让我检测到断开连接,但这是另一回事,不在这个问题的范围内。

编辑 2: 我对此进行了更多测试,并发现尽管有一些建议,例如here(这似乎是一个有效的信息来源),执行空发送不会识别断开的连接。我物理上断开了网络电缆,我的服务器每 5 秒进行一次空发送。就这样持续了几分钟,没有检测到断线。如果我决定发送任何数据(甚至是单个字节),最多会在 20 秒后检测到断开连接。

【问题讨论】:

  • 也许这篇文章会有所帮助social.msdn.microsoft.com/Forums/en-US/…
  • 答案说“当对等套接字关闭时,对 Read 的调用应该只返回零字节”,这是不正确的......我可以让 Read 返回 0 字节而不关闭连接,因此问题
  • 也许尝试在第一篇文章中使用 do{ } while (stream.DataAvailable) 而不是中断 0 字节。
  • @MarcinPawlica - 我确信代码存在问题,您没有在此处显示。这就是为什么每个人都要求海报制作一个显示实际问题的 MCVE。下次请多多关照……

标签: c# .net sockets tcp network-programming


【解决方案1】:

来自 MSDN 的NetworkStream.Read Method (Byte[], Int32, Int32)

读取操作读取尽可能多的数据,直到 size 参数指定的字节数。如果没有数据 可读取,Read方法返回0。


在发送数据时,您正在发送一个空字节数组并使用以下方法写入零字节:

networkStream.Write(new byte[0], 0, 0);

而在读取数据时,您声称

  1. “我的客户端代码确实不时需要来自服务器的数据,所以 它有一个阻塞 networkStream 的线程。”
int bytesRead = networkStream.Read(buffer, 0, 4);
if (bytesRead == 0)
   break;

但是,您又一次尝试将 4 个字节读取到一个字节数组中,这显然会继续等待。那么,您还能期待什么!

  1. "...只是想知道.NET的Socket是否可以区分空 字节数组和连接关闭。”

连接关闭完全是另一回事,它在关闭 Socket 连接之前涉及几个步骤。因此,显然,它与发送或接收零字节不同!

-> 最后,正如 cmets 中@WithMetta 所暗示的,请使用NetworkStream.DataAvailable Property 检查数据是否可供读取。

while(networkStream.DataAvailable) { // your code logic}

【讨论】:

  • 实际上,您引用的 MSDN 文档部分是......也许没有错,但不够清楚。请参阅此处:social.msdn.microsoft.com/Forums/en-US/… 在没有可用数据时读取块。一旦我做更多的测试,我会回到这个...
  • @MarcinPawlica - 如果此答案有帮助,您可能想投票和/或接受答案。如果有进一步的疑问,请随时询问。
最近更新 更多