Chapter 7 ■ Server arChiteCture
131
if not more_data:
if data:
print('Client {} sent {!r} but then closed'
.format(address, data))
else:
print('Client {} closed socket normally'.format(address))
return
data += more_data
answer = zen_utils.get_answer(data)
writer.write(answer)
if name == 'main':
address = zen_utils.parse_command_line('asyncio server using coroutine')
loop = asyncio.get_event_loop()
coro = asyncio.start_server(handle_conversation, *address)
server = loop.run_until_complete(coro)
print('Listening at {}'.format(address))
try:
loop.run_forever()
finally:
server.close()
loop.close()
Comparing this listing with the earlier efforts at servers, you will recognize all of the code. The while loop
calling recv() repeatedly is the old framing maneuver, followed by a write of the reply to the waiting client, all
wrapped up in a while loop that is happy to keep responding to as many requests as the client would like to
make. But there is a crucial difference that prevents you from simply reusing the earlier implementations of this
same logic. Here it takes the form of a generator that does a yield from everywhere that the earlier code simply
performed a blocking operation and waited for the operating system to respond. It is this difference that lets this
generator plug into the asyncio subsystem without blocking it and preventing more than one worker from making
progress at a time.
PEP 380 recommends this approach for coroutines because it makes it easy to see where your generator might
get paused. It could stop running for an indefinite period of time every time it does a yield. Some programmers
dislike festooning their code with explicit yield statements, and in Python 2 there are frameworks like gevent
and eventlet that take normal networking code with normal blocking I/O calls and specially intercept those
calls to perform what is really asynchronous I/O under the hood. These frameworks have not, as of this writing,
been ported to Python 3, and if ported, they will still face competition from the fact that asyncio is now built into
the Standard Library. If they ever arrive, then programmers will have to choose between the verbose but explicit
approach of an asyncio coroutine where you can see a “yield” everywhere a pause might take place and the
implicit but more compact code possible when calls like recv() return control to the asynchronous I/O loop while
looking like innocent method calls in the code itself.
The Legacy Module asyncore
In case you run across any services written against the asyncore Standard Library module, Listing 7-9 uses it to
implement the sample protocol.