Chapter 7 ■ Server arChiteCture
119
The next two functions provide some common startup code that will be shared among the servers.
The parse_command_line() function provides a common scheme for reading command-line arguments, while
create_srv_socket() can build the listening TCP socket that a server needs in order to receive incoming connections.
But it is in the final four routines that the listing begins to demonstrate the central patterns of a server process.
The cascade of four functions simply repeats gestures which you have already learned about in Chapter 3, which
was about creating a TCP server for a listening socket, and in Chapter 5, which was about framing data and
handling errors.
• accept_connections_forever() is a simple listen() loop that announces each connecting
client with print() before handing its socket over to the next function for action.
• handle_conversation() is an error-catching routine to wrap an unlimited number of
request-response cycles in a way that is designed to make it impossible for any problems with
the client socket to crash the program. The exception EOFError is caught in its own specific
except clause because it is how the innermost data-reception loop will signal that a client has
finished making requests and has finally hung up—which, in this particular protocol (as in
HTTP), is normal and not a truly an exceptional event. But all other exceptions are treated as
errors and are reported with print() after being caught. (Recall that all normal Python errors
inherit from Exception and will therefore be intercepted by this except clause!) The finally
clause makes sure that the client socket is always closed, regardless of the code path by which
this function exits. Running close() like this is always safe because already-closed file and
socket objects in Python allow close() to be called again for good measure as many times as a
program wants.
• handle_request() performs a single back-and-forth with the client, reading its question and
then replying with an answer. Note the careful use of send_all() because the send() call by
itself cannot guarantee the delivery of an entire payload.
• recv_until() performs the framing, using the practice outlined in Chapter 5. Repeated
calls are made to the socket’s recv() until the accumulated byte string finally qualifies as a
complete question.
These routines are the tool chest from which you will build several servers.
To exercise the various servers in this chapter, you need a client program. One is provided in Listing 7-2 as a
simple command-line tool.
Listing 7-2. Client Program for Example Zen-of-Python Protocol
#!/usr/bin/env python3
Foundations of Python Network Programming, Third Edition
https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter07/client.py
Simple Zen-of-Python client that asks three questions then disconnects.
import argparse, random, socket, zen_utils
def client(address, cause_error=False):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(address)
aphorisms = list(zen_utils.aphorisms)
if cause_error:
sock.sendall(aphorisms[0][:-1])
return