CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 1 of 1
  1. #1
    Join Date
    Feb 2003
    Location
    Bangalore, India
    Posts
    1,354

    Visual C++ Network: How do I use a timeout for 'connect()'?

    Q: How do I use a timeout for 'connect()'?

    A: Before discussing how to use a timeout for the 'connect()' function, it will be good taking a look at what exactly happens at the time of establishing a TCP connection. When a TCP connection is initiated, a packet with a SYN flag is send (client) to the remote PC (server). Once the server gets the SYN packet it is acknowledged with an ACK packet. Although the connection is bi-directional, it requires synchronization from both sides. Thus, the stack responds again with a SYN packet. Instead of responding twice, the server combines both ACK and SYN into a single packet and send it as a SYN/ACK packet. Finally, the client sends an ACK packet back, acknowledging the SYN from the server. This process is commonly known as the TCP 'Three-Way Handshake'.

    Now we will look at what happens when the server is not responding. When the client issues a 'connect()', the stack sends a SYN packet. Since the server never gets the SYN packet or does not respond to it, the client never gets SYN/ACK packet. So the client waits for some time, thinking that the packet might be lost on its way, and sends a SYN packet again. This process continues for sometime before the stack abandons the effort and 'connect()' returns with 'WSAETIMEDOUT'.

    Now, the initial timeout value - before the first connection attempt - is 3 seconds. After each retry, the amount of time to wait is doubled (3 seconds, 6 seconds etc.). The number of retries can be configured via the 'TcpMaxConnectRetransmissions' parameter in the registry - the default is set to 3 for Windows NT and 2 for Windows 2000. Thus, the first solution to the connection timeout problem is, to adjust this parameter via the Registry. However, changing the registry is a global change, affecting all applications that use Winsock and/or TCP/IP. The following Knowledge Base article provides detailed information. Nevertheless, one should be careful while playing with these parameters.

    Quote Originally Posted by MSDN

    CAUTION: The Windows NT TCP/IP implementation is largely self tuning. Adjusting registry parameters without careful study may adversely affect system performance
    Winsock API functions such as 'send()', 'recv()', 'connect()' etc. block because we are using blocking sockets. The next solution is to simply use non-blocking sockets instead. For a non-blocking socket, 'connect()' returns with 'WSAEWOULDBLOCK' error. However, the connection process goes on. Thus, one crude but simple way of adding a timeout is to issue a 'connect()' call and wait for a specified amount of time, then call a winsock function like 'getpeername()' or 'recv()' that needs a connected socket. If the error is 'WSAENOTCONN' we can safely assume that the socket is not connected and close the socket. The drawback of this method is that even if the connection occurs quickly, it will only be known after the specified timeout. We can reduce this effect by checking at intervals and abandoning the effort once the total time has elapsed.

    Another way to add a timeout to non-blocking sockets is to use notification functions such as

    • 'select()'
    • 'WSAAsyncSelect()'
    • 'WSAEventSelect()'


    Since the usage of these functions need a great amount of discussion, it will not be attempted here. You are sure to find the usage on the net and other books and also in MSDN which should be the first place of reference.

    Yet another method is to use 'ConnectEx()'. Since this API is a Microsoft specific winsock2 extension, you have to include 'mswsock.h' and link to the library 'mswsock.lib'. 'ConnectEx()' is the only function that allows overlapped calls with 'connect()'. However, this API is available only in Windows XP and later. Lets look at the usage with some example code.

    Code:
    SOCKET      cliSock;
    SOCKADDR_IN sockAddr;
    int         addrLen = sizeof(sockAddr);
    DWORD       dwBytes = 0;
    DWORD       dwErr;
    BOOL        bRet;
    ContextInfo ci;
    In the above declarations the variable 'ci' needs some explanation. The structure 'ContextInfo' contains the context information of the overlapped call. You can assign anything you think would be necessary to maintain the context info. Here, we are assigning only the index number. The structure is defined as

    Code:
    struct ContextInfo
    {
      OVERLAPPED ol;
      DWORD      dwIndex;
    };
    The parameter 'ol' is the overlapped parameter which we use to make an overlapped operation. Here, we are loading the 'ConnectEx()' function at run-time. This becomes necessary if we can't find the API declaration in the 'mswsock.h' file. This kind of loading is also practiced for Microsoft specific extension functions to avoid performance penalty at the time of calling. For this purpose we declare two parameters

    Code:
    LPFN_CONNECTEX lpfnConnectEx = NULL;              // a pointer to the 'ConnectEx()' function
    GUID           GuidConnectEx = WSAID_CONNECTEX;   // The Guid
    Once the socket is created, we have to bind it to a port and interface. While using 'ConnectEx()', binding has to be done explicitly because the main purpose of this API other than allowing overlapped calls is, performance.

    Code:
    dwErr = WSAIoctl(cliSock,
                     SIO_GET_EXTENSION_FUNCTION_POINTER,
                     &GuidConnectEx,
                     sizeof(GuidConnectEx),
                     &lpfnConnectEx,
                     sizeof(lpfnConnectEx),
                     &dwBytes,
                     NULL,
                     NULL); 
    if(dwErr == SOCKET_ERROR)
      // Handle the error using 'WSAGetLastError()'
    Most of the function parameters are self-explanatory. The IO control code 'SIO_GET_EXTENSION_FUNCTION_PONTER' retrieves a pointer to the specified extension function, which is 'ConnectEx()' in our case. The input buffer contains a GUID whose value identifies the function 'ConnectEx()'.

    Code:
    ZeroMemory(&ci.ol, sizeof(ci.ol));
    ci.index = 1;
    bRet = lpfnConnectEx(cliSock,
                         (sockaddr*) &sockAddr,
                         addrLen,
                         NULL,
                         0,
                         NULL,
                         &ci.ol);
    if(!bRet)
    {
      dwErr = WSAGetLastError();
      if(dwErr != ERROR_IO_PENDING)
        // Handle the error
    }
    There is nothing really hard in this API call. Most of the parameters are again self-explanatory. The fourth parameter 'lpSendBuffer' is an optional parameter which points to the buffer to send as soon as the connection is established. If this function is used synchronously, it returns only when the buffer has been sent. The number of bytes sent is returned in the 6th parameter, 'BytesSent'. If the request is successful and not completed immediately, which is quite less likely for connection establishment, we get the error 'ERROR_IO_PENDING' since we initiated an overlapped connection request.

    Now all that is left is to get the overlapped result. There are different ways to get the overlapped result. You can use any one of them according to the preference.

    • 'HasOverlappedIoCompleted()'
    • 'WSAGetOverlappedResult()'
    • 'WaitForSingleObject()'/'WaiteForMultipleObject()'
    • 'GetQueuedCompletionStatus()'


    The 'WSAGetOverlappedResult()' has a parameter 'fWait' that indicates whether the function will complete immediately or has to wait till the overlapped IO is complete. If you are polling in order to know whether or not the IO has completed then the first function 'HasOverlappedIoCompleted()' offers high performance test operation. You can also use the 'hEvent' parameter of the OVERLAPPED structure to get the event notification of the overlapped operation, thereby using the wait functions. If you are using IO completion port, then you have 'GetQueuedCompletionStatus()' to get the status of the overlapped IO operation.

    Of the above mentioned functions, the first two utilizes the poll method, with the drawbacks mentioned earlier. The last two functions get signaled as soon as the connection is complete and will also return if there is a timeout. However using the wait functions or 'HasOverlappedIoCompleted()' requires you to get the overlapped result by the function 'WSAGetOverlappedResult()'.

    Code:
    bRet = WSAGetOverlappedResult(cliSock,
                                  &ci.ol,
                                  &dwBt,
                                  TRUE, 
                                  &dwFlags);
       
    if(!bRet)
      // Handle the error using WSAGetLastError
    
    if(ci.dwIndex == 1) // first connection is successful
      // rest
    Once we get the result of the overlapped function, we can use other members of the 'ContextInfo' structure to get the information associated with the socket or the particular IO.


    Last edited by Andreas Masur; July 25th, 2005 at 02:59 PM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured