Search code examples
socketswinsockwinsock2iocp

Closing sockets that haven't completed AcceptEx - if and how?


I understand that if I issue an AcceptEx call, through a function pointer as recommended by the documentation, then if I specify a receiver buffer size, the call will not complete until some data is sent:

if (!lpfnAcceptEx(sockListen,
    sockAccept,
    PerIoData->Buffer,
    DATA_BUFSIZE - ((sizeof(SOCKADDR_IN) + 16) * 2), /* receive buffer size */
    sizeof(SOCKADDR_IN) + 16,
    sizeof(SOCKADDR_IN) + 16,
    &dwBytes,
    &(PerIoData->Overlapped)
    ))
    {
        DWORD dwLastError = GetLastError();
        // Handle error

    }

From MSDN

If a receive buffer is provided, the overlapped operation will not complete until a connection is accepted and data is read. Use the getsockopt function with the SO_CONNECT_TIME option to check whether a connection has been accepted.

If the socket is not connected, the getsockopt returns 0xFFFFFFFF. Applications that check whether the overlapped operation has completed, in combination with the SO_CONNECT_TIME option, can determine that a connection has been accepted but no data has been received.

It is recommended such connections be terminated by closing the accepted socket, which forces the AcceptEx function call to complete with an error.

Now, this seems to state that I should forcibly close the socket. However, my book "Network Programming for Microsoft Windows - Second Edition" states similar facts, but goes on to say

As a word of warning, applications should not under any circumstances close a client socket handle used in an AcceptEx call that has not been accepted because it can lead to memory leaks. For performance reasons, the kernel-mode structures associated with an AcceptEx call will not be cleaned up when the unconnected client handle is closed until a new client connection is established or until the listening socket is closed.

So I'm not supposed to close it now?? I'm confused.

Two questions:

1) If a socket has not fully completed AcceptEx, I get back 0xFFFFFFFF from getsockopt. This makes it a candidate for foribly closing. But how am I supposed to know how long it has been sitting in this state? I can't add my own timing logic because I don't know when the accept was made because my completion port routine hasn't completed!

2) When I figure out if I need to close the socket, how do I do it? Is closesocket() enough?


Solution

  • OK I found this out myself after examining the code posted by Len Holgate at this link.

    Basically we need to store all the SOCKET objects (that we create in to pass to the AcceptEx function pointer that we obtain as shown above) in order to iterate through them. Programming for Microsoft Windows - Second Edition tells us that a good time to iterate through pending connections is when we have more connections wanting to be accepted than we do outstanding AcceptEx calls. We can determine if that is the case as follows:

    WSAEVENT NewEvent = CreateEvent(0, FALSE, TRUE, 0); // Auto reset event
    WSAEventSelect(sockListen, NewEvent, FD_ACCEPT);
    
    if (::WaitForSingleObject(NewEvent, INFINITE) == WAIT_OBJECT_0)
         // Need to post an AcceptEx
    

    Note the use of an auto-reset event, not a manual one as created by WSACreateEvent(). Now, after posting the AcceptEx, we can loop through our pending sockets, checking the connected duration on each one:

    // Get the time for which this socket has been connected
    ::getsockopt(sock, SOL_SOCKET, SO_CONNECT_TIME, (char *)&nSeconds, &nBytes);
    
    //
    // If we decide the socket has been open for long enough, set SO_LINGER then close it
    //
    LINGER lingerStruct; // *
    lingerStruct.l_onoff = 1;   // Leave socket open...
    lingerStruct.l_linger = 0;  //...for 0 seconds after closesocket() is called
    
    ::setsockopt(sock, SOL_SOCKET, SO_LINGER, (char *)&lingerStruct, sizeof(lingerStruct));
    closesocket(sock);
    

    (*) See here for why this is required.

    The last thing to do is remove the SOCKET from whatever storage we are keeping it in when the AcceptEx call completes.