TcpClient; NetworkStream; ReadAsync; C#

5
请原谅我对任务和异步操作的知识不足。
使用TcpClient类,我正在与一个可用服务器建立连接:
void async RunClientAsync()
{
    TcpClient client = new TcpClient();
    try
    {
        await client.ConnectAsync(IPAddress.Parse("1.1.1.1"), 8889);
        Task.Start(() => ReadClientAsync(client));
    }
    catch (Exception ex)
    {
        HandleException(ex);
    }
}

// -----

void async ReadClientAsync(TcpClient client)
{
    byte[] bf = new byte[2048];
    try
    {
        while(true)
        {
            int br = await client.NetworkStream().ReadAsync();
            if (br > 0) 
            {
                HandleInboundData(bf, br);
            }
        }
    }
    catch (Exception ex)
    {
        HandleException(ex);
    }
}

方法 HandleException(Exception ex) 和 HandleInboundData(byte[] buffer, int length) 会执行预定的任务。

与服务器的连接将是永久性的,从服务器接收的数据长度和频率不确定,想法是抛出一个任务,只有在有数据可用时才接收和处理入站数据。

直接使用 ReadClientAsync(TcpClient client) 显然行不通,因为如果没有可用数据,ReadAsync 将始终返回0字节。

我应该如何使用async/task编写 ReadClientAsync 防止繁忙循环?我以前在这种情况下使用了 BeginRead/EndRead,效果很好。在这种特殊情况下,那是否是解决方案?

谢谢。


感谢您的评论。这是来自ReadAsync的MSDN条目:“表示异步读取操作的任务。TResult参数的值包含读入缓冲区的总字节数。如果当前可用的字节数少于请求的字节数,则结果值可以小于请求的字节数,或者如果已到达流的末尾,则可以为0(零)。”我理解流的末尾意味着流中没有数据可供读取。 - Jam
明白了。谢谢你,Luaan。我相信我知道问题出在哪里了。 - Jam
1个回答

10
不,这不是TCP的工作方式。
当另一端启动(可能是单向)关闭时,NetworkStream被认为处于“流结束”状态。这时候ReadAsync(或者Read)返回零,而不是在任何其他情况下。
MSDN文档很容易被误解 - 主要是因为您正在查看错误的文档部分。 NetworkStream没有重写ReadAsync(没有理由这样做),所以实际上您正在查看通用的Stream.ReadAsync文档。相比之下,NetworkStream.Read的文档如下:
“此方法将数据读入缓冲区参数并返回成功读取的字节数。如果没有可用于读取的数据,则Read方法返回0。读取操作读取尽可能多的数据,最多到由size参数指定的字节数。如果远程主机关闭连接,并且已接收到所有可用数据,则Read方法立即完成并返回零字节。”
请注意最后一句话,它告诉您NetworkStream实际上是什么意思,即"流结束"。这就是TCP连接如何关闭的。
您对此的响应通常应该是从另一侧关闭连接 - 从您的帮助器方法中return并清理套接字。在任何情况下,不要再重复while(true) - 这只会导致无限循环,占用100%的CPU。
如果您想了解如何使用await处理C#异步套接字的一些指针,请查看我的示例:https://github.com/Luaancz/Networking/tree/master/Networking%20Part%202。请注意免责声明 - 这绝不是生产就绪的。但它确实解决了一些在实现TCP通信时人们经常犯的错误。

2
你如何设置ReadAsync或WriteAsync或ConnectAsync的超时时间为10秒?换句话说,它们要么成功完成,要么在10秒后超时? - pixel
@pixel 传递一个设置了CancelAfterCancellationToken;你可以通过CancellationTokenSource.CreateLinkedTokenSource将其与现有的 CancellationToken组合。现在你的客户端最好在没有其他数据要发送时发送保持连接的数据包,否则就告别它们 :p - Ray

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