Chapter 7 ■ Server arChiteCture
130
You can see that the actual socket object is carefully protected from the protocol code in Listing 7-7. You ask
the framework, not the socket, for the remote address. Data is delivered via a method call that shows you only the
string that has arrived. The answer you want transmitted is handed off to the framework with its transport.write()
method call, leaving your code out of the loop—quite literally—about when, exactly, that data will be handed off to
the operating system for transmission back to the client. The framework assures you that it will happen as soon as
possible, so long as it does not block progress on other client connections that need attention.
Asynchronous workers usually become more complicated than this. A common example is when responses
to the client cannot be composed trivially, as they can here, but involve reading from files on the file system or
consultation with back-end services such as databases. In that case, your client code will have to face in two different
directions: it will defer to the framework both when sending and receiving data to the client, and when sending and
receiving data from the filesystem or database. In such cases, your callback methods might themselves build futures
objects that provide yet further callbacks, to be invoked when the database or filesystem I/O has finally completed.
See the official asyncio documentation for details.
Coroutine-Style asyncio
The other means of constructing protocol code for the asyncio framework is to construct a coroutine, which is a
function that pauses when it wants to perform I/O—returning control to its caller—instead of blocking in an I/O
routine itself. The canonical form in which the Python language supports coroutines is through generators: functions
that have one or more yield statements inside of them and that therefore reel off a sequence of items instead of
terminating with a single return value when called.
If you have written generic generators before, whose yield statements simply offer up items for consumption,
then you will be a bit surprised at how asyncio-targeted generators look. They take advantage of the extended yield
syntax developed in PEP 380. The extended syntax not only allows a running generator to reel off all the items yielded
by another generator with the yield from statement but allows yield to return a value to the inside of the coroutine,
and even to raise an exception if the consumer demands it. This allows a pattern in which the coroutine does a
result = yield of an object describing some operation that it would like performed—maybe a read on another
socket or access to the filesystem—and either receive back the result of the successful operation in result or
experience, right there in the coroutine, an exception indicating that the operation failed.
Listing 7-8 illustrates the protocol implemented as a coroutine.
Listing 7-8. An asyncio Server in the Coroutine Style
#!/usr/bin/env python3
Foundations of Python Network Programming, Third Edition
https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter07/srv_asyncio2.py
Asynchronous I/O inside an "asyncio" coroutine.
import asyncio, zen_utils
@asyncio.coroutine
def handle_conversation(reader, writer):
address = writer.get_extra_info('peername')
print('Accepted connection from {}'.format(address))
while True:
data = b''
while not data.endswith(b'?'):
more_data = yield from reader.read(4096)