How to Set Connection Timeout for TcpClient in C#

By FoxLearn 1/18/2025 2:58:44 AM   65
In C#, the TcpClient class doesn't directly provide a way to set a connection timeout.

Unlike other properties such as SendTimeout or ReceiveTimeout, which control read/write operations, there is no parameter within TcpClient to set a timeout for the initial connection.

However, you can manage connection timeouts effectively by combining TcpClient.ConnectAsync() with Task.WhenAny() and Task.Delay().

When handling a connection attempt, there are three possible outcomes:

  1. The connection completes successfully: The connection is made, and no errors occur.
  2. The connection attempt fails and throws an exception: If the connection fails, the exception needs to be handled and propagated.
  3. The operation times out: If the connection isn't established within the given timeframe, the timeout task completes, and we throw an exception indicating the timeout.

Implementing the TcpClientWrapper Class

For example, you can create a TcpClientWrapper class that implements the connection timeout logic as shown below.

using System;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace TcpClientTimeout
{
    // Custom exception class to handle TCP connection errors with a specific message
    public class TcpException : Exception
    {
        public TcpException(string msg) : base(msg) { }
    }

    // TcpClientWrapper class to handle TCP client connection attempts with a timeout feature
    public class TcpClientWrapper
    {
        // Asynchronous method to connect to a remote IP and port with a specified connection timeout
        public async Task ConnectAsync(string ip, int port, TimeSpan connectTimeout)
        {
            // Create an instance of TcpClient to manage the TCP connection
            using (var tcpClient = new TcpClient())
            {
                // Task.Delay creates a task that completes after the specified connectTimeout
                var cancelTask = Task.Delay(connectTimeout);

                // The ConnectAsync method initiates an asynchronous connection attempt to the specified IP and port
                var connectTask = tcpClient.ConnectAsync(ip, port);

                // Use Task.WhenAny to wait for the first task to complete (either connectTask or cancelTask)
                // This allows us to handle both the successful connection or the timeout scenario
                await await Task.WhenAny(connectTask, cancelTask);

                // If cancelTask completes first, the connection attempt has timed out
                if (cancelTask.IsCompleted)
                {
                    // Throw a custom TcpException to indicate the connection has timed out
                    throw new TcpException("Timed out");
                }
            }
        }
    }
}

In this example:

  • Task.Delay(connectTimeout): It will complete after the specified duration.
  • tcpClient.ConnectAsync(ip, port): This is the asynchronous connection attempt.
  • Task.WhenAny(): This method returns when either the connection task or the delay task completes first. If the connection task completes first, the connection was successful. If the delay task completes first, the connection attempt timed out.

If the delay task completes first, the method throws a custom TcpException to indicate that the operation has timed out.

Using the TcpClientWrapper

For example, you can use the TcpClientWrapper class to test if a TCP port is open as shown below.

using System;
using System.Threading.Tasks;

namespace TcpClientTimeout
{
    class Program
    {
        // Entry point of the application
        static void Main(string[] args)
        {
            // Start the TcpPortTest method asynchronously on a separate task
            Task.Run(TcpPortTest);

            // Inform the user that the port is being tested
            Console.WriteLine("Please wait while the port is tested");

            // Wait for the user to press any key before the application closes
            Console.ReadKey();
        }

        // Asynchronous method to test if a TCP port is open by attempting a connection
        static async Task TcpPortTest()
        {
            // Create an instance of the TcpClientWrapper to manage the TCP connection
            TcpClientWrapper tcpClientWrapper = new TcpClientWrapper();

            try
            {
                // Attempt to connect to the specified IP and port with a 1-second timeout
                await tcpClientWrapper.ConnectAsync("127.0.0.1", 12345, TimeSpan.FromSeconds(1));
                
                // If the connection is successful, inform the user that the port is open
                Console.WriteLine("Port tested - it's open");
            }
            catch (Exception ex)
            {
                // If an exception is thrown (e.g., timeout or connection failure), inform the user
                Console.WriteLine($"Port tested - it's not open. Exception: {ex.Message}");
            }
        }
    }
}

In this example:

  • TcpClientWrapper: The TcpClientWrapper class is used to wrap the TcpClient and apply the connection timeout logic. The ConnectAsync method accepts the IP address, port, and timeout duration.
  • TcpPortTest: This method runs asynchronously and attempts to connect to a given IP and port. If the connection is successful within the timeout period, it prints a message stating that the port is open. If it fails due to a timeout or any other exception, the message indicates the failure along with the exception.

Output

Timeout Scenario (When Task.Delay completes before the connection task):

Please wait while the port is tested
Port tested - it's not open. Exception: Timed out

Connection Failure Scenario (When TcpClient.ConnectAsync() fails to connect):

Please wait while the port is tested
Port tested - it's not open. Exception: No connection could be made because the target machine actively refused it 127.0.0.1:12345

By using Task.WhenAny() with TcpClient.ConnectAsync() and Task.Delay(), you can effectively manage connection timeouts in C#.