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