如何关闭超时的 TcpClient?

How to close the TcpClient with a timeout?

我正在使用 .NET Core 并希望通过 TCP 发送消息。为此,我使用 TcpClient class 并创建了自定义服务。这个解决方案目前有效,不确定我是否可以改进它

class MyTcpService : IMyTcpService
{
    private readonly TcpClient tcpClient = new TcpClient();

    public async Task Send(byte[] bytesToSend)
    {
        if (!tcpClient.Connected) // Check if client was closed before
        {
            await tcpClient.ConnectAsync("127.0.0.1", 5000); // Read values from config
        }

        NetworkStream networkStream = tcpClient.GetStream();
        
        // Send the message
        await networkStream.WriteAsync(bytesToSend, 0, bytesToSend.Length);

        // Read the response
        byte[] responseBuffer = new byte[1024]; // Read value from config
        int amountOfResponseBytes = await networkStream.ReadAsync(responseBuffer, 0, responseBuffer.Length);
        string responseMessage = Encoding.ASCII.GetString(responseBuffer, 0, amountOfResponseBytes);

        // Close the connection with a timeout if true
        if (true) // Read value from config
        {
            networkStream.Close(1000); // Read value from config
            tcpClient.Close();
        }

        // Handle the response message here
        // ...
    }
}

我想将 IMyTcpService 作为临时服务注入。我想知道如何超时关闭客户端? Socket class 有一个 Close 方法接受超时参数

https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.close?view=netcore-3.1#System_Net_Sockets_Socket_Close_System_Int32_

但我找不到 TcpClient 的等价物,只是因为它的 NetworkStream

目前您正在等待 WriteAsyncReadAsync 电话。因此,您对 networkStream.Close(1000) 的调用超时应该没有影响,并且连接将始终立即关闭,因为没有数据等待 sent/received。您没有为写入或读取指定超时,这意味着它们不会 return 直到数据传输完成。

I would like to know how to close the client with a timeout?

不清楚你为什么想要这个或者你想用这个实现什么。 TcpClient is simply a wrapper around a NetworkStream which in turn wraps around a Socket。因此,在 TcpClientNetworkStream 上处理超时没有多大意义。


资源管理:

在您当前的示例中,我首先建议您将 TcpClient 保留在 Send 方法中,而不是 class 字段中。如果您不需要在其他地方使用 TcpClient(期望您不需要,因为您在 Send 函数中关闭它),您应该缩小它的范围以便于资源管理。在这样做时,我建议您使用 using statement 以避免忘记正确处理您的资源。这适用于所有实现 IDisposable 接口的类型(当然也有例外)。

处理超时:

要处理您共享的这段代码中的超时,我建议您在写入和读取操作而不是关闭操作上配置超时,因为您的代码是非常连续的。一个可能看起来像的例子:

class MyTcpService : IMyTcpService
{
    public async Task Send(byte[] bytesToSend)
    {
        string responseMessage;
        using (tcpClient = new TcpClient())
        {
            if (shouldUseTimeout) // From config
            {
                tcpClient.ReceiveTimeout = 1000; // From config
                tcpClient.SendTimeout = 1000; // From config
            }
            await tcpClient.ConnectAsync("127.0.0.1", 5000); // Read values from config

            NetworkStream networkStream = tcpClient.GetStream();
        
            // Send the message
            await networkStream.WriteAsync(bytesToSend, 0, bytesToSend.Length);

            // Read the response
            byte[] responseBuffer = new byte[1024]; // Read value from config
            int amountOfResponseBytes = await networkStream.ReadAsync(responseBuffer, 0, responseBuffer.Length);
            responseMessage = Encoding.ASCII.GetString(responseBuffer, 0, amountOfResponseBytes);
        }
        // The tcpClient is now properly closed and disposed of

        // Handle the response message here
        // responseMessage...
    }
}

更新回复评论 13/10/2020:

hey, thanks for your reply :) I tried to improve your answer, what do you think about this snippet? pastebin.com/7kTvtTv2

来自你的https://pastebin.com/7kTvtTv2

public async Task<string> Send(byte[] messageToSend)
{
    string responseMessage;

    using (TcpClient tcpClient = new TcpClient())
    {
        await tcpClient.ConnectAsync("127.0.0.1", 5000); // From config

        NetworkStream networkStream = tcpClient.GetStream();
        await networkStream.WriteAsync(messageToSend, 0, messageToSend.Length);
        await networkStream.FlushAsync();
        tcpClient.Client.Shutdown(SocketShutdown.Send); // shutdown gracefully

        byte[] responseBuffer = new byte[256]; // This can be of any size
        StringBuilder stringBuilder = new StringBuilder();
        int amountOfResponseBytes;

        do
        {
            amountOfResponseBytes = await networkStream.ReadAsync(responseBuffer, 0, responseBuffer.Length);
            string responseData = Encoding.ASCII.GetString(responseBuffer, 0, amountOfResponseBytes);
            stringBuilder.Append(responseData);
        } while (amountOfResponseBytes > 0);

        responseMessage = stringBuilder.ToString();
    }

    return responseMessage;
}

我觉得不错。只有一些小意见:

await networkStream.FlushAsync() - 当我查看 remarks for the Flush method 时,这似乎是不必要的,但我还没有测试它:

The Flush method implements the Stream.Flush method; however, because NetworkStream is not buffered, it has no effect on network streams. Calling the Flush method does not throw an exception

tcpClient.Client.Shutdown(SocketShutdown.Send) - 此方法只是告诉 Socket 不再允许 writing/sending 数据。由于您的 TcpClient 仅保留在 Send 方法中,因此不会在任何地方共享,因此似乎也没有必要。如果您无法完全控制何时使用 Socket,我认为 Shutdown 方法最相关。

do { ... } while (...) - 我觉得不错。请记住,如果您正在处理 ASCII 字符,responseBuffer 需要是 8 的倍数,这样您就不会最终尝试解码部分字符。

超时处理去哪儿了?您是忘记添加超时处理了还是不再相关了?目前,如果您有大量数据要发送或接收,或者网络速度很慢,WriteAsyncReadAsync 调用可能会花费很长时间。