TcpClient has no direct way to set the connection timeout. It doesn’t have any parameters that allow you to control it, and SendTimeout / ReceiveTimeout don’t apply to the initial connection.
The way I control the connection timeout is by awaiting a Task.WhenAny() with TcpClient.ConnectAsync() and Task.Delay(). Task.WhenAny() returns when any of the tasks complete.
There are 3 possible outcomes:
- Task.ConnectAsync() completes and was successful.
- Task.ConnectAsync() completes, but faulted. In this case, I want the exception to bubble up.
- Task.Delay() completes, indicating the process has timed out.
See below for fully working code. This is a simple port tester (like doing “telnet IP PORT” just to see if a port is open).
TcpClientWrapper – specify the connection timeout
Here is the TcpClientWrapper class:
using System;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace TcpClientTimeout
{
public class TcpException : Exception
{
public TcpException(string msg) : base(msg) { }
}
public class TcpClientWrapper
{
public async Task ConnectAsync(string ip, int port, TimeSpan connectTimeout)
{
using (var tcpClient = new TcpClient())
{
var cancelTask = Task.Delay(connectTimeout);
var connectTask = tcpClient.ConnectAsync(ip, port);
//double await so if cancelTask throws exception, this throws it
await await Task.WhenAny(connectTask, cancelTask);
if (cancelTask.IsCompleted)
{
//If cancelTask and connectTask both finish at the same time,
//we'll consider it to be a timeout.
throw new TcpException("Timed out");
}
};
}
}
}
Code language: C# (cs)
If cancelTask completes first, it means the connection timed out, so it throws a custom TcpException.
Using the TcpClientWrapper
Here’s an example of how to use the TcpClientWrapper class:
using System;
using System.Threading.Tasks;
namespace TcpClientTimeout
{
class Program
{
static void Main(string[] args)
{
Task.Run(TcpPortTest);
Console.WriteLine("Please wait while the port is tested");
Console.ReadKey();
}
static async Task TcpPortTest()
{
TcpClientWrapper tcpClientWrapper = new TcpClientWrapper();
try
{
await tcpClientWrapper.ConnectAsync("127.0.0.1",
12345,
TimeSpan.FromSeconds(1));
Console.WriteLine("Port tested - it's open");
}
catch(Exception ex)
{
Console.WriteLine($"Port tested - it's not open. Exception: {ex.Message}");
}
}
}
}
Code language: C# (cs)
Note: You can change this to either 1) wait for user input that specifies the connection info or 2) get the info as a command line argument.
Running the program
Here’s what this outputs when Task.Delay completes, and results in a timeout:
Please wait while the port is tested
Port tested - it's not open. Exception: Timed out
Code language: plaintext (plaintext)
And here’s what it outputs when Task.ConnectAsync() fails to connect and throws an exception:
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
Code language: plaintext (plaintext)