asyncio.Streams
提供了与 socket
模块类似的编程模型,但所有操作都是非阻塞和异步的。
核心概念
asyncio
数据流主要涉及两个类:
-
StreamReader
: 用于从数据流中读取数据。 -
StreamWriter
: 用于向数据流中写入数据,并管理连接状态。
server端
import asyncio
async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
"""
这个协程在每个客户端连接时被调用。
reader 和 writer 对象代表了与这个特定客户端的连接。
"""
# 获取客户端的地址信息
addr = writer.get_extra_info('peername')
print(f"接收到来自 {addr} 的连接")
try:
while True:
# 从客户端读取数据,最多读取 100 字节
# read(n) 会一直等待,直到收到至少一个字节或连接关闭
data = await reader.read(100)
if not data: # 如果收到空字节(b''),表示客户端关闭了连接
break
# 将收到的数据解码并打印
message = data.decode('utf-8')
print(f"从 {addr} 收到消息: {message!r}")
# 准备回复
response = f"服务器已收到你的消息:{message}"
# 将回复写入数据流
writer.write(response.encode('utf-8'))
# 等待数据被发送(drain() 确保缓冲区数据被刷新到底层socket)
await writer.drain()
print(f"客户端 {addr} 断开连接")
except ConnectionError as e:
print(f"与 {addr} 的连接出现错误: {e}")
finally:
# 关闭连接
writer.close()
# 等待连接完全关闭
await writer.wait_closed()
print(f"与 {addr} 的连接已关闭")
async def main():
# 启动服务器,绑定到本地回环地址(127.0.0.1)的 8888 端口
server = await asyncio.start_server(
handle_client, # 客户端处理函数
'127.0.0.1', # 主机
8888 # 端口
)
# 获取服务器监听的地址
addrs = ', '.join(str(sock.getsockname()) for sock in server.sockets)
print(f'服务器运行在 {addrs}')
# 异步地永远运行服务器,直到被取消
async with server:
await server.serve_forever()
# 运行主函数
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\n服务器被用户关闭")
client端
import asyncio
async def tcp_echo_client(message):
"""
一个简单的 TCP 回声客户端
"""
# 建立到服务器的连接
reader, writer = await asyncio.open_connection(
'127.0.0.1', # 服务器主机
8888 # 服务器端口
)
print(f'发送: {message!r}')
# 编码并发送消息
writer.write(message.encode('utf-8'))
# 确保数据被发送
await writer.drain()
# 等待服务器的回复(这里假设回复不超过 1KB)
data = await reader.read(1024)
print(f'收到: {data.decode("utf-8")!r}')
# 告诉服务器我们不再发送数据(关闭写入端)
writer.close()
# 等待连接完全关闭
await writer.wait_closed()
print('连接已关闭')
async def main():
# 发送一条消息
await tcp_echo_client("Hello, World!")
# 可以并发发送多条消息
# await asyncio.gather(
# tcp_echo_client("Message 1"),
# tcp_echo_client("Message 2"),
# )
# 运行客户端
asyncio.run(main())
关键方法和属性
StreamReader 的常用方法:
-
read(n)
: 读取最多n
个字节。如果流结束(EOF)则返回空字节。 -
readline()
: 读取一行,以b'\n'
结尾。非常适用于基于行的协议(如 HTTP 头、SMTP)。 -
readexactly(n)
: 精确读取n
个字节。如果连接在读取完n
个字节前关闭,会引发IncompleteReadError
。 -
readuntil(separator=b'\n')
: 读取数据直到遇到指定的分隔符。分隔符也会被包含在返回的数据中。
StreamWriter 的常用方法:
-
write(data)
: 将数据写入底层的写缓冲区。此方法是非阻塞的。 -
writelines(data)
: 写入一个字节串或字符串的列表。 -
drain()
: 非常重要! 等待缓冲区数据被发送到底层传输。在write()
后调用它,以确保数据被真正发送出去。这是一个协程。 -
close()
: 关闭连接。 -
wait_closed()
: 等待连接完全关闭。这是一个协程。 -
get_extra_info(name)
: 获取关于连接的额外信息,例如'peername'
(客户端地址)、'sockname'
(服务器地址)等。
参照 https://docs.python.org/zh-cn/3.10/library/asyncio-stream.html#get-http-headers