简体   繁体   中英

How to close the TcpClient with a timeout?

I'm using .NET Core and want to send messages via TCP. For this I'm using the TcpClient class and created a custom service. This solution works for now, not sure if I can improve it

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
        // ...
    }
}

I want to inject IMyTcpService as a transient service. I would like to know how to close the client with a timeout? The Socket class has a Close method accepting a timeout parameter

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

but I'm not able to find an equivalent for the TcpClient just for its NetworkStream .

At the moment you are awaiting both the WriteAsync and ReadAsync calls. Because of this the timeout in your call to networkStream.Close(1000) should have no impact and the connection will always close immediately as no data is waiting to be sent/received. For neither the write or read you have specified a timeout, which means they won't return until data has finished being transferred.

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

It's not clear why you want this or what you want to achieve with this. TcpClient is simply a wrapper around a NetworkStream which in turn wraps around a Socket . So handling timeouts both on the TcpClient and the NetworkStream doesn't make much sense.


Resource management:

In your current example I would first of all advise you to keep the TcpClient inside the Send method instead of a class field. If you don't need to use the TcpClient in other places (which expect you don't since you are closing it in the Send function) you should narrow it's scope for easier resource management. While doing that I'd suggest you make use of the using statement to avoid forgetting to properly dispose your resources. This applies to all types that implement the IDisposable interface (of course there are exceptions to this).

Handling timeout:

To handle timeouts in this snippet of code you have shared I suggest you configure the timeout on the write and read operations rather than the close operation, since your code is very sequential. An example of what that could look like:

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...
    }
}

Update in response to the comment 13/10/2020:

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

From your 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;
}

Looks pretty good to me. Only some minor comments:

await networkStream.FlushAsync() - it seems like this should be unnecessary when I look at the remarks for the Flush method but I haven't tested it:

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) - this method simply tells the Socket to no longer allow writing/sending data. Since your TcpClient only stays within the Send method and therefore not being shared anywhere it seems a little unnecessary too. I think the Shutdown method is mostly relevant if you don't have complete control over when the Socket is used.

do { ... } while (...) - looks good to me. Just remember the responseBuffer need to be a multiple of 8 if you are dealing with ASCII characters so you don't end up trying to decode a partial character.

Where did the timeout handling go? Did you forget to add timeout handling or is it not relevant anymore? Currently, if you have a lot of data to send or receive or the network is just slow, the WriteAsync and ReadAsync calls may potentially take a long time.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM