8 Sep 2018 • Using WSAAsyncSelect

This API is garbage, the docs are garbage, and every piece of code I could find that uses it is garbage. It works roughly like this:

which looks totally reasonable. But, FD_CLOSE happens when the socket closes and not when you're done reading it, so you can keep getting FD_READs after the FD_CLOSE. On top of that the API seems to be buggy shit and you can get random garbage FD_READs out of nowhere. (the Microsoft sample code runs into this too (key word in that comment being "usually") so it's not just me using it wrong)

So the correct usage code looks like this:

switch( WSAGETSELECTEVENT( lParam ) ) {
	case FD_READ:
	case FD_CLOSE: {
		SOCKET sock = ( SOCKET ) wParam;
		// check this isn't a random FD_READ
		if( !open_sockets.contains( sock ) )
			break;

		while( true ) {
			char buf[ 2048 ];
			int n = recv( fd, buf, sizeof( buf ), 0 );
			if( n > 0 ) {
				// do stuff with buf
			}
			else if( n == 0 ) {
				closesocket( sock );
				open_sockets.remove( sock );
				break;
			}
			else {
				int err = WSAGetLastError();
				if( err != WSAEWOULDBLOCK )
					abort();
				break;
			}
		}

	} break;

	// ...
}

At the top we have a check to make sure it's not a garbage FD_READ, and then we have a while loop to read until WSAEWOULDBLOCK. You need the loop so you know when to actually close the socket. You can't close it in FD_CLOSE because you can get FD_READs after that. Without the loop you'll stop receiving FD_READs after you read everything off the socket, which means you never see the zero length recv and you won't know when to close it. Technically you only need to start looping after FD_CLOSE, but it simplifies the code a bit if you treat both events in the same way.

Lots of samples you see just ignore any errors from recv. Don't do this, you should explicitly handle expected errors (in this case that's just WSAEWOULDBLOCK), and kill the program on anything else because it means your code is wrong.