No navigation frame on the left?  Click here.

Sockets, IOCPs, AcceptEx

 

Updated 1 Nov 2000: see end of page for details.

Two days ago, I took at look at the Platform SDK -- at the samples directory, to be more precise, and at the provided Winsock/completion-port sample to be quite exact. I was, mildly put, extremely unhappy with that sample; it manages to hide the important points from everyone who doesn't know already about them (and even such a person has to look around a bit to find the relevant stuff).

If I may say so myself, the small demonstration project below is superior in both readability and practical applicability. It demonstrates a small server that:

bulletuses sockets in overlapped mode, attached to an I/O completion port (IOCP);
bulletuses AcceptEx() to route new connections through the IOCP;
bulletuses GetQueuedCompletionPacket() to retrieve the results of asynchronous (overlapped) I/O requests;
bulletuses PostQueuedCompletionStatus() to send special notifications to the worker threads servicing the IOCP;
bulletuses TransmitFile() like an FTP server might;
bulletand demonstrates a simple state machine implementing an interactive server, which accepts commands and returns meaningful results. Simple echo servers, bah.

Do you want a blow-by-blow description of what this sample does, and how it does it? If so, send mail -- the more mail I get on this, the higher the write-up goes on my to-do list.

To build the sample: Unzip to a directory of your choice, keeping the subdirectory structure intact. Open the sockhim.dsw workspace with VC++ 6.0 (I am using SP3) and build the srv project. (The cli project might develop into a stress-testing client later, but unless there is large and lively interest, don't hold your breath.) Note that I am using the Platform SDK headers/libs, and I strongly recommend that you do the same.

To run the sample: Run srv.exe (or srvd.exe). It understands the following command line switches: -?, -h display a short usage note; -p <number> sets the port for srv to listen on (it binds to INADDR_ANY; binding only to specific interfaces or IP addresses is left as an exercise for you); -s <number> sets the maximum number of simultaneous connections to that number; -t <number> sets the maximum number of threads to that number; -c <number> tells the IOCP how many concurrent threads to allow -- 0 is the default, one per CPU. Run "srv.exe -?" to see the default values.

To play with the sample: Fire up a telnet client and tell it to connect to the port you are using for srv.exe. I recommend enabling local echo. If you don't, you won't see anything until you hit the RETURN key. Commands are one per line, with a blank or tab separating the command from its argument (where applicable). Start with "?" or "help". Note that the "parser" (cough, cough) is extremely simple-minded: it hates extraneous blanks, doesn't understand quoted strings, and offers no line editing at all, not even backspace.

Update [1 Nov 2000]: AcceptEx() has a few peculiarities that merit mention. As long as you give AcceptEx() a non-zero buffer size (remember, the reserved buffer space must be larger than the size you tell AE(), because after the "official" buffer, AE() will store the remote and local network addresses), everything works as expected ... until you have a client that just sits there and does nothing. Until AcceptEx() receives at least one byte of user data, it does not generate an IOCP notification of an accepted connection, it will not time out, etc. -- it just sits there, and you have a socket that cannot accept connections, and worst, you don't know about that. The new version of sockhim kicks off a separate thread to scavenge such sockets -- see ScavengePulingSockets() in worker.cpp. When the thread function finds a socket that (a) is still in stAccepting state (and has therefore not yet sent or completed it's AcceptEx() completion notification) and (b) has been idle for too long, it just closes the socket. That causes an immediate I/O error as the completion result from AcceptEx(), which in turn causes our IOCP worker thread to call DoClose().

Update to the update [1 Nov 2000]: Kudos to Tomas Restrepo for pointing out the obvious. And two wristslaps for me for not even noticing that the feature I claimed to be faulty works fine in every other socket+IOCP server I have written. Gak!

sockhim.zip, 21 KB: Sockets-over-IOCPs sample.