||[Jul. 11th, 2012|07:19 pm]
I've been working with Windows Sockets recently, and I want to summarise a few things that I've learned, just in case I'll need to remember them in the future.
(1) Berkeley Sockets are supported, and work fine for console applications. They aren't ideal if you need to mix in threads or message pumps. Also, select() behaves slightly differently on Windows than it does on Unix. If you are waiting for 2 sockets and only the second is signalled, then in Unix FD_ISSET(0, read_set) is false and FD_ISSET(1, read_set) is true. In Windows, read_set.fd_count is 1, and read_set.fd_array is the handle of the second socket (since sockets aren't created in a nice arithmetic progression).
(2) There's a way to have select() work directly with window messages: WSAAsyncSelect(). But you need to create a window in that thread, which obviously limits the current number of running threads. There's also WSAEventSelect(), which requires a call to WaitForMultipleObjects() instead of a window. It's slightly simpler to use.
(3) The most straightforward way to write multi-socket servers is to use IoCompletionPorts. Every operation is asynchronous; once the operation finishes you can find out the result by calling a blocking function GetQueriedCompletionStatus (from any thread--which is a big advantage).
(4) In order to use techniques 2 or 3, you need to create "overlapped" sockets (using socket() or WSASocket(WSA_FLAG_OVERLAPPED). If you're calling accept()/WSAAccept(), then the new socket will be "overlapped" if the listening socket is overlapped (which is important!). I still can find no reason to create a non-overlapped socket intentionally, since passing a NULL lpOverlapped converts any function into a blocking function (this is true only for sockets).
(5) Neither accept() nor WSAAccept() (same applies for variants of connect()) are overlapped calls, so they cannot be serviced by the threads that are looping over GetQueriedCompletionStatus(). You can either have a thread dedicated to accept() (or connect()) calls, or use a bizarre WinSock2 extension called SIO_GET_EXTENSION_FUNCTION_POINTER, which associates your listening socket (or connecting socket) with a Completion Port, so that you can have homogeneous loops. Here's what it looks like:
GUID guidAcceptEx = WSAID_ACCEPTEX;
unsigned long dummy = 0;
SOCKET listeningSocket = CreateListeningSocket(80);
CreateIoCompletionPort((HANDLE) listeningSocket, currentCompletionPort, 42, 0);
// Load the AcceptEx extension function from the provider for this socket
&dummy, NULL, NULL);
It's not as bad as it looks--but depending on the design of your application, WSAAsyncSelect may be all that you need. :-)