This server follows the age-old Berkeley select() server
style. This allows it to handle many connections within a single
thread, at the cost of roughly twice as much code compared to the multithreaded server. For low numbers of
connections, this server is also less efficient, because of all the setup
select() requires. (Note that this code could be optimized a bit by
keeping separate pristine copies of the fd_sets to reduce setup time.)
select() has one big advantage, though: it's portable to
virtually all flavors of Unix. It also scales a bit better than threads,
simply because threads cause context switches, which eventually becomes
much more significant than the overhead in setting up fd_sets.
The select() -based server has several similarities to the
threaded server code. The only real difference is that all the I/O in this
version is handled in the AcceptConnections() function, rather than
in a bunch of concurrent threads. In fact, AcceptConnection() is
pretty much the main function in this version, whereas in the threaded
server, it just accepts connections and passes them off to handler
threads.
Notice that we have to keep a list of clients and an I/O buffer for
each client in this version. That's the price of non-synchronous I/O:
you have to keep a lot of state around so you can juggle between many
connections within a single thread.
Also notice that we do not call WSAGetLastError() in this
program, but instead call getsockopt() asking for the SO_ERROR
value. With select() , several sockets can go into an error state
at once, so the "last" error is not necessarily the error value we're
interested in. We have to find the last error that occured on a
particular socket instead.
Building the Program
The only module you will need to compile this program, aside from
the common files listed on the main examples page,
is select-server.cpp. The comment at the top of the file
gives complete compilation instructions; alternately, you can use the
common Makefile.
|