Chapter 7 ■ Server arChiteCture
135
listener.settimeout(8.0)
try:
zen_utils.accept_connections_forever(listener)
except socket.timeout:
print('Waited 8 seconds with no further connections; shutting down')
Of course, this server is of the same primitive single-threaded design with which I started this chapter.
In production, you are likely to want a more robust design, and you can use any of the approaches discussed in this
chapter. The only requirement is that they be able to take an already-listening socket and run accept() on it over and
over again, forever. This is simple if you are happy for your server process, once launched by inetd, never to exit. It can
get a bit more complicated (and beyond the scope of this book) if you want the server to be able to time out and shut
down after a period of inactivity since it can be tricky for a group of threads or processes to confirm that none of them
are currently talking to a client and that none of them have received a client connection recently enough to warrant
keeping the server alive.
There is also a simple access control mechanism based on IP address and hostname that is built into some
versions of inetd. The mechanism is the descendent of an old program named tcpd that once worked in conjunction
with inetd before being rolled into the same process. Its /etc/hosts.allow and /etc/hosts.deny files can,
depending on their rules, prevent some (or all!) IP addresses from connecting to one of your services. Be sure to read
your system documentation and review how your system administrator has configured these files if you are debugging
a problem where clients cannot reach one of your inetd-powered services!
Summary
The example network servers of Chapter 2 and Chapter 3 were capable of interacting with only one client at a time,
while all others had to wait until the previous client socket had closed. There are two techniques for expanding
beyond this roadblock.
From a programming perspective, the simplest is multithreading (or multiprocessing) where the server code can
usually remain unchanged, and the operating system is tasked with the job of switching invisibly between workers so
that waiting clients get responses quickly while idle clients consume no server CPU. This technique not only allows
several client conversations to make progress simultaneously but also makes better use of a server CPU that might
otherwise sit idle most of the time waiting for more work from one client.
The more complicated but powerful approach is to embrace an asynchronous programming style that lets a
single thread of control switch its attention between as many clients as it wants, by giving the operating system the
full list of sockets with which it is currently having conversations. The complication is that this requires the logic of
reading a client request and building a response to be split into small, nonblocking pieces of code that can hand
control back to the asynchronous framework when it is time to wait on the client again. While an asynchronous
server can be written manually using a mechanism like select() or poll(), most programmers will want to rely on a
framework, like the asyncio framework built into the Standard Library in Python 3.4 and newer.
Arranging for a service you have written to get installed on a server and to start running when the system boots is
called deployment, and it can be automated using many modern mechanisms, either by using tools like supervisord
or by handing control to a platform-as-a-service container. The simplest possible deployment for a baseline Linux
server may be the old inetd daemon, which provides a bare-bones way to make sure your service is launched the
moment that a client first needs it.
You will see the topic of servers again in this book. After Chapter 8 tackles a number of basic network-based
services upon which modern Python programmers rely, Chapter 9 through Chapter 11 will look at the design of
the HTTP protocol and the Python tools for acting as both a client and a server, where you will see the designs
presented by this chapter available all over again in the choice between a forking web server such as Gunicorn and an
asynchronous framework like Tornado.