reply = 'Echo=>%s at %s' % (data, now())
connection.send(reply.encode())
connection.close()
os._exit(0)
def dispatcher(): # listen until process killed
while True: # wait for next connection,
connection, address = sockobj.accept() # pass to process for service
print('Server connected by', address, end=' ')
print('at', now())
reapChildren() # clean up exited children now
childPid = os.fork() # copy this process
if childPid == 0: # if in child process: handle
handleClient(connection)
else: # else: go accept next connect
activeChildren.append(childPid) # add to active child pid list
dispatcher()
Running the forking server
Parts of this script are a bit tricky, and most of its library calls work only on Unix-like
platforms. Crucially, it runs on Cygwin Python on Windows, but not standard Win-
dows Python. Before we get into too many forking details, though, let’s focus on how
this server arranges to handle multiple client requests.
First, notice that to simulate a long-running operation (e.g., database updates, other
network traffic), this server adds a five-second time.sleep delay in its client handler
function, handleClient. After the delay, the original echo reply action is performed.
That means that when we run a server and clients this time, clients won’t receive the
echo reply until five seconds after they’ve sent their requests to the server.
To help keep track of requests and replies, the server prints its system time each time
a client connect request is received, and adds its system time to the reply. Clients print
the reply time sent back from the server, not their own—clocks on the server and client
may differ radically, so to compare apples to apples, all times are server times. Because
of the simulated delays, we also must usually start each client in its own console window
on Windows (clients will hang in a blocked state while waiting for their reply).
But the grander story here is that this script runs one main parent process on the server
machine, which does nothing but watch for connections (in dispatcher), plus one child
process per active client connection, running in parallel with both the main parent
process and the other client processes (in handleClient). In principle, the server can
handle any number of clients without bogging down.
To test, let’s first start the server remotely in a SSH or Telnet window, and start three
clients locally in three distinct console windows. As we’ll see in a moment, this server
can also be run under Cygwin locally if you have Cygwin but don’t have a remote server
account like the one on learning-python.com used here:
804 | Chapter 12: Network Scripting