Detecting and terminating aborted TCP/IP connections

Figure 1: Chat Server and Chat Client

Environment: VC5 SP3, NT4 SP3

When one side of the TCP-IP connection goes down abruptly without notifying the other end, the other end is left hanging. This is especially important for a server handling hundreds of connections. For such a server, even a single hanging socket is tantamount to wasting precious resources. Winsock does not provide a function which directly checks for abrupt termination of the TCP/IP connection at the other end.
The function HasConnectionDropped checks if the other end of the socket is still connected.
The pseudo code is:

[1] Check if the socket is readable.
[2] If yes, peek at the incoming data.
[3] Check the return values, error values to see if the network connection is still up.

It is important that we just peek at the incoming data. The MSG_PEEK flag does just that. The data is copied into the buffer but is not removed from the input queue. Thus we ensure that we do not disturb the functionality of the system.
We check the error values in case the recv call returns an error. Note that, if the recv call returns 0, it signifies a graceful disconnect from the other side. Hence, we consider a 0 return value as an indication of dropped connection.

As an example, consider the ChatServer and Chatter client example from MSDN. Communication between the client and server is via CMsg objects. A CMsg object contains two members:
m_bClose: To signify that the connection is being closed.
m_strText: The text to be posted on to the chat server.

In case of a normal termination, the client sends a message with m_bClose set to TRUE. The server closes down it's end of the connection on receiving this message and sends a close message to the client. The client closes down it's end after receiving this message.
So far so good. However, if the client terminates abruptly without sending the close message, the corresponding end at the server is never closed until the server shuts down.

I have modified the examples to illustrate the use of the HasConnectionDropped function.

On the server side, the menu handler for the menu item "Check Connections" iterates over the list of client connections to check if all connections are still up. Any client sockets for dropped connections are flushed out.

On the client side, the menu handler for the menu item "Kill myself" kills the client without giving it a chance to send a "Close" message to the server.

To run the demo:

[1] Start the server.

[2] Start one or more clients.

[3] Kill one (or more) client(s) by clicking "Kill myself" menu item. Note that, a message saying "xyz has left the discussion" does not appear on the board. We do have a hanging connection here!

[4] Click on "Check connections". A messagebox pops up showing the number of connections that were closed because they were dropped from the other side.

Note: I have written the function HasConnectionDropped to be a member of the CClientSocket (which depends upon the CSocket MFC class) to illustrate this technique. However the function does not depend on any CSocket/MFC/Windows functionality. Basic Winsock functions select and recv are used. Hence one can easily adapt this function to work in a non-MFC raw Winsock environment. The only change that has to be made is to accept the socket handle as a function parameter. (instead of as a member variable as in the present case)



BOOL CClientSocket::HasConnectionDropped( void )
{
	BOOL bConnDropped = FALSE;
	INT iRet = 0;
	BOOL bOK = TRUE;
	
	struct timeval timeout = { 0, 0 };
	fd_set readSocketSet;
	
	FD_ZERO( &readSocketSet );
	FD_SET( m_hSocket, &readSocketSet );
	
	iRet = ::select( 0, &readSocketSet, NULL, NULL, &timeout );
	bOK = ( iRet > 0 );
	
	if( bOK )
	{
		bOK = FD_ISSET( m_hSocket, &readSocketSet );
	}
	
	if( bOK )
	{
		CHAR szBuffer[1] = "";
		iRet = ::recv( m_hSocket, szBuffer, 1, MSG_PEEK );
		bOK = ( iRet > 0 );
		if( !bOK )
		{
			INT iError = ::WSAGetLastError();
			bConnDropped = ( ( iError == WSAENETRESET ) ||
				( iError == WSAECONNABORTED ) ||
				( iError == WSAECONNRESET ) ||
				( iError == WSAEINVAL ) ||
				( iRet == 0 ) ); //Graceful disconnect from other side.
		}
	}
	
    return( bConnDropped );
}


Downloads

Download demo project - 6 Kb