为什么 NetworkStream.ReadAsync 没有超时?



我正在使用NetworkStreamTcpClient

  1. 首先,我设置了我的 tcp 客户端:

    tcp = new TcpClient(AddressFamily.InterNetwork)
    { NoDelay = true, ReceiveTimeout = 5000};
    
  2. 我的主要数据接收循环:

    while (true)
    {
    //read available data from the device
    int numBytesRead = await ReadAsync();
    Console.WriteLine($"{numBytesRead} bytes read"); //BP2
    }
    
  3. 而实际的TCP数据读数:

    public Task<int> ReadAsync()
    {
    var stream = tcp.GetStream();
    return stream.ReadAsync(InBuffer, 0, InBuffer.Length); //BP1
    }
    

我将其连接到测试平台,可以让我发送手动数据包。通过设置断点和调试,我检查了stream.ReadTimeout是否从tcp中获取值 5000。

如果我经常发送数据,一切都按预期工作。但是如果我不发送任何数据,则 5 秒后似乎什么都不会发生,没有超时。我看到断点BP1在调试器中被命中,但在我从测试平台发送数据之前,BP2没有命中。我可以让它停留一分钟或更长时间,它似乎只是坐等,但在一分钟后收到发送的数据,这似乎是不正确的行为。5 秒后应该会发生一些事情,当然(据我所知是例外(?

已经很晚了,所以我期待一些非常基本的东西,但谁能看到我的错误是什么和解决方案?


补遗

好的,所以当我为我正在使用的实际 .Net 版本进行 RTFM 时(我怎么会被 MS 默认为 .Net Core 3 发现,我确实说晚了(我在评论中看到ReadTimeout

此属性仅影响通过调用 读取方法。此属性不影响异步读取 通过调用 BeginRead 方法执行。

我现在不清楚我是否可以使用现代可等待的调用来安全地读取套接字数据,特别是超时。除了超时之外,它正在工作,但我不确定ReadAsync如何在NetworkStream中没有覆盖.我必须做一些丑陋的黑客还是有一个简单的解决方案?

就我而言,5000 是我可以期望在得出结论之前不会接收数据的最长时间 - 该协议没有 ping 机制,所以如果没有任何内容出现,我假设连接已死。因此,认为具有 5000 毫秒超时的异步读取会很好而且整洁。

网络对象的超时值仅适用于同步操作。例如,从文档中:

此选项仅适用于同步接收呼叫。

对于Socket.ReceiveTimeoutTcpClient.ReceiveTimeoutNetworkStream.ReadTimeout,这些实现最终都会导致对SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, ...)的调用,而进而有效地调用本机setsockopt()函数。从该文档中:

SO_RCVTIMEODWORD设置阻止接收呼叫的超时(以毫秒为单位(。

(强调我的(

正是基础本机 API 中的这种限制是托管 API 中出现相同限制的原因。超时值不适用于网络对象上的异步 I/O。

您需要自己实现超时,方法是在超时发生时关闭套接字。例如:

async Task<int> ReadAsync(TcpClient client, byte[] buffer, int index, int length, TimeSpan timeout)
{
Task<int> result = client.GetStream().ReadAsync(buffer, index, length);
await Task.WhenAny(result, Task.Delay(timeout));
if (!result.IsCompleted)
{
client.Close();
}
return await result;
}

有关此主题的其他变体可以在其他相关问题中找到:
具有取消令牌的 NetworkStream.ReadAsync 永远不会
取消 C# 4.5 Tcp客户端 ReadAsync 超时

关闭插座实际上是您所能做的。即使对于同步操作,如果发生超时,套接字也将不再可用。没有可靠的方法来中断读取操作并期望套接字保持一致。

当然,您可以选择在关闭套接字之前提示用户。但是,如果要这样做,您将在应用程序体系结构中的更高级别实现超时,这样 I/O 操作本身就完全不知道超时。

最新更新