10.5.9 使用协程和流的异步I/O
这一节以上两个示例程序的另一个版本(分别实现一个简单的回送服务器和客户端),这里会使用协程和asyncio流API,而不是使用协议和传输类抽象。与protocol API相比,这些例子在更低的抽象层上操作,不过事件的处理是类似的。
10.5.9.1 回送服务器
服务器首先导入建立asyncio和logging所需的模块,然后创建一个事件循环对象。
import asyncio
import logging
import sys
SERVER_ADDRESS = ('localhost',10000)
logging.basicConfig(
level=logging.DEBUG,
format='%(name)s:%(message)s',
stream=sys.stderr,
)
log = logging.getLogger('main')
event_loop = asyncio.get_event_loop()
# 然后服务器定义一个协程来处理通信。每次一个客户端连接时,都会调用这个协程的一个
# 新实例;因此,在这个函数中,代码一次只与一个客户端通信。Python的语言运行时会
# 管理各个协程实例的状态,所以应用代码不需要管理任何额外的数据结构来跟踪各个
# 客户端。
# 协程的参数是与这个新连接关联的StreamReader和StreamWriter实例。与Transprot一个,
# 可以通过书写器的get_extra_info()方法访问客户端的地址。
async def echo(reader,writer):
address = writer.get_extra_info('peername')
log = logging.getLogger('echo_{}_{}'.format(*address))
log.debug('connection accepted')
# 尽管建立连接时会调用洗车,但此时还没有任何要读取的数据。为了避免读取时阻塞,
# 协程对read()调用了await,从而允许事件循环继续处理其他任务,直到有可以读取的
# 数据。
while True:
data = await reader.read(128)
# 如果客户端发送了数据,则其会从await返回,并且通过把这个数据传递到书写器从而
# 发回给客户端。可以使用多个write()调用来缓冲发出的数据,然后使用drain()刷新
# 输出结果。由于刷新输出网络I/O可能阻塞,所以再次使用await恢复对事件循环的控制,
# 它会监视写套接字,并在可以发送更多数据时调用书写器。
if data:
log.debug('received {!r}'.format(data))
writer.write(data)
await writer.drain()
log.debug('sent {!r}'.foramt(data))
# 如果客户端还没有发送任何数据,那么read()返回一个空字节串来指示连接关闭。服务
# 器需要关闭写客户端的套接字,然后协程返回,以指示它已经完成。
else:
log.debug('closing')
writer.close()
return
# 启动服务器有两个步骤。首先,应用告诉时间循环使用协程、主机名以及监听的套接字来
# 创建一个新的服务器对象。start_server()方法本社是一个协程,所以必须由事件循环
# 处理结果来具体启动服务器。完成协程时会生成一个绑定到事件循环的asyncio.Server
# 实例。
# Create the server and let the loop finish the coroutine before
# starting the real event loop.
factory = asyncio.start_server(echo,*SERVER_ADDRESS)
server = event_loop.run_until_complete(factory)
log.debug('starting up on {} prot {}'.format(*SERVER_ADDRESS))
# 接下来,需要允许事件循环来处理事件和客户请求。对于一个长时间运行的服务,
# run_forever()方法是完成这个工作最简单的方法。事件循环结束时(可能由应用代码结
# 束,也可能通过向进程发出信号来结束),可以关闭服务器以适当地清理套接字。然后
# 可以关闭事件循环,在程序退出前完成所有其他协程的处理。
# Enter the event loop permanently to handle all connections.
try:
event_loop.run_forever()
except KeyboardInterrupt:
pass
finally:
log.debug('close server')
server.close()
event_loop.run_until_complete(server.wait_closed())
log.debug('closing event loop')
event_loop.close()