TCP Connect Timeout In C# .NET

The Connect methods of the Socket and TcpClient classes fail to provide a means to form a TCP connection while allowing for a timeout.  This is particularly inconvenient because the default timeouts tend to be long, although they vary by platform.  When forced to wait over 10 seconds for a connection to succeed, a user may decide your application has locked-up.

Modern networks are fairly reliable, resulting in little packet loss.  If a response was not returned from a host within 1 second, it is likely because the host is down or the IP address is incorrect.  Therefor, forcing the connection attempt to timeout may be safe and efficient for some applications.

The code below provides a means to initiate a TCP connection, and time out after a specified duration has elapsed.  While there are many means to accomplish this, I’ve found the most stable and efficient to be the use of non-blocking sockets.  This will cause the Connect method to send the TCP syn packet and then return immediately.  The socket is then polled continuously until 1 of 3 things has happened:

  • The connection is established
  • The timeout is reached
  • A socket error occurs

If successful, the ConnectWithTimeout method will return a connected Socket object.  Otherwise, a variety of exceptions could be thrown.

 1 public static Socket ConnectWithTimeout(string host, int port, uint timeout,
 2     AddressFamily addressfamily = AddressFamily.InterNetwork,
 3     SocketType socketType = SocketType.Stream,
 4     ProtocolType protocolType = ProtocolType.Tcp)
 5 {
 6     // 1 tick is 10000 milliseconds
 7     const uint MS_TO_TICKS = 10000;
 8 
 9     // Wait for 1/10 of a millisecond for a socket error
10     const int POLL_DURATION = 100;
11 
12     Socket socket = new Socket(addressfamily, socketType, protocolType);
13     DateTime timeoutDT = DateTime.Now;
14 
15     try
16     {
17         checked
18         {
19             timeoutDT = DateTime.Now.AddTicks(timeout * MS_TO_TICKS);
20         }
21     }
22     catch (OverflowException)
23     {
24         throw new ArgumentOutOfRangeException("timeout");
25     }
26 
27     socket.Blocking = false;
28 
29     try
30     {
31         socket.Connect(host, port);
32     }
33     catch (SocketException socketEx)
34     {
35         // The connect method should throw a WouldBlock error
36         if (socketEx.SocketErrorCode != SocketError.WouldBlock)
37         {
38             throw socketEx;
39         }
40     }
41 
42     while (!socket.Connected)
43     {
44         if (DateTime.Now > timeoutDT)
45         {
46             throw new TimeoutException("Timeout during connection attempt.");
47         }
48 
49         if (socket.Poll(POLL_DURATION, SelectMode.SelectError))
50         {
51             throw new SocketException
52             (
53                 (int)socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Error)
54             );
55         }
56     }
57 
58     return socket;
59 }

The following code shows how the ConnectWithTimeout Method might be used.  The most important part is correctly handling the Exceptions which might be thrown.  Of course an unspecified catch block could catch all of them, but it may be important to know why the connection failed.

 1 try
 2 {
 3     const string IP_ADDRESS = "192.168.1.1";
 4     const int PORT = 80;
 5     const uint TIMEOUT = 2000;
 6 
 7     Socket socket = SocketManager.ConnectWithTimeout(IP_ADDRESS, PORT, TIMEOUT);
 8 
 9     // SUCCESS!
10 
11     socket.Close();
12     socket.Dispose();
13 }
14 catch (SocketException socketEx)
15 {
16     switch (socketEx.SocketErrorCode)
17     {
18         case SocketError.ConnectionRefused:
19             // most likely the port is not open
20             break;
21 
22         case SocketError.NetworkUnreachable:
23             // network configuration problem
24             break;
25 
26         default:
27             // other socket error
28             break;
29     }
30 }
31 catch (TimeoutException)
32 {
33     // time out while connecting to host
34 }
35 catch (SecurityException)
36 {
37     // we may not have permission to form connections
38 }
39 catch (ArgumentOutOfRangeException)
40 {
41     // the timeout was set WAY too high
42 }