Python之数据流(stream)
本文参考Python官方文档:https://docs.python.org/zh-cn/3.8/library/asyncio-stream.html
本文参考Python官方文档针对官方文档示例进行解析,解析不完整只为了便于理解
流
流是用于处理网络连接的高级async/await-ready原语。流允许发送和接收数据,而不需要使用回调或低级协议和传输。
Stream函数
下面的高级 asyncio 函数可以用来创建和处理流:coroutine asyncio.
open_connection
(host=None, port=None, *, loop=None, limit=None, ssl=None, family=0, proto=0, flags=0, sock=None, local_addr=None, server_hostname=None, ssl_handshake_timeout=None)
-
建立网络连接并返回一对
(reader, writer)
对象。返回的 reader 和 writer 对象是
StreamReader
和StreamWriter
类的实例。注意:使用ayncio.open_connection()方法创建和处理流时只有在await时才返回reader和writer对象
为了方便测试我们在本地搭建一个nginx服务器,首页index.html内容为“Hello World”
示例:
1234567891011121314151617import
asyncio
async
def
wget(host):
connect
=
asyncio.open_connection(host,
80
)
print
(
type
(connect))
reader,writer
=
await connect
print
(
type
(reader),
type
(writer))
async
def
main():
# 获取表头主机列表
hosts
=
[
'192.168.1.100'
]
# 根据主机列表获取一个tasks列表
tasks
=
[asyncio.create_task(wget(host))
for
host
in
hosts]
# 等待任务列表执行结果
await asyncio.gather(
*
tasks)
# 运行
asyncio.run(main()
运行输出如下
12345678910111213<
class
'coroutine'
>
<
class
'asyncio.streams.StreamReader'
> <
class
'asyncio.streams.StreamWriter'
>
Exception ignored
in
: <function _ProactorBasePipeTransport.__del__ at
0x00000236AA956F70
>
Traceback (most recent call last):
File
"C:\ProgramData\Anaconda3\lib\asyncio\proactor_events.py"
, line
116
,
in
__del__
self
.close()
File
"C:\ProgramData\Anaconda3\lib\asyncio\proactor_events.py"
, line
108
,
in
close
self
._loop.call_soon(
self
._call_connection_lost,
None
)
File
"C:\ProgramData\Anaconda3\lib\asyncio\base_events.py"
, line
719
,
in
call_soon
self
._check_closed()
File
"C:\ProgramData\Anaconda3\lib\asyncio\base_events.py"
, line
508
,
in
_check_closed
raise
RuntimeError(
'Event loop is closed'
)
RuntimeError: Event loop
is
closed
解析:运行报错是因为获取了writer对象但是并没有写数据
打印类型可以看到
完善代码使用writer对象发送请求至服务器,然后reader对象就可以收取服务器发送过来的数据
12345678910111213141516171819202122232425262728# asyncio.open_connection创建数据流 start
import
asyncio
async
def
wget(host):
connect
=
asyncio.open_connection(host,
80
)
print
(
type
(connect))
reader,writer
=
await connect
print
(
type
(reader),
type
(writer))
# 定义请求头部,格式是固定格式
header
=
'GET / HTTP/1.0\r\n Host:{0}\r\n\r\n'
.
format
(host)
# 通过writer对象往http服务器发送请求,请求是二进制格式的需要使用encode()方法编码
writer.write(header.encode(
'utf-8'
))
# writer.write方法需要与drain()方法一起使用
await writer.drain()
# 阻塞获取服务器发送过来的所有数据,read()方法一次性获取所有数据,数据多可以使用readline()方法一行行获取
data
=
await reader.read()
# 打印获取的数据,获取数据为二进制格式不加decode()解码则打印原始数据
print
(data.decode())
# 关闭writer需要和writer.wait_closed()一起使用,这里可以省略
writer.close()
await writer.wait_closed()
async
def
main():
hosts
=
[
'192.168.1.100'
]
tasks
=
[asyncio.create_task(wget(host))
for
host
in
hosts]
await asyncio.gather(
*
tasks)
asyncio.run(main())
# asyncio.open_connection创建数据流 end
输出如下
12345678910111213<
class
'coroutine'
>
<
class
'asyncio.streams.StreamReader'
> <
class
'asyncio.streams.StreamWriter'
>
HTTP
/
1.1
200
OK
Server: nginx
/
1.14
.
0
Date: Sat,
30
Oct
2021
09
:
37
:
17
GMT
Content
-
Type
: text
/
html
Content
-
Length:
12
Last
-
Modified: Fri,
29
Oct
2021
07
:
41
:
00
GMT
Connection: close
ETag:
"617ba58c-c"
Accept
-
Ranges: bytes
Hello World
本次代码演示了连接http服务器并且向服务器发送一个GET请求,服务器收到GET请求以后把数据返回给客户,然后通过reader对象获取到服务器发送过来的数据。
注意:本次发送的是一个GET请求,格式是固定的
本次发送的完整数据为
1'GET / HTTP/1.0\r\n Host:192.168.1.100\r\n\r\n'
对应关系如下图
拆分解析如下
1234567891011GET
/
HTTP
/
1.0
\r\n Host:
192.168
.
1.100
\r\n\r\n
GET
#请求方法为GET
# 空格
/
# 请求URL为/即根目录
HTTP
/
1.0
# 协议版本
\r\n
# 回车符和换行符
Host
# 头部字段为Host
:
# 固定格式的符号:
192.168
.
1.100
# Host的值,即本次请求的主机值
\r\n
# 请求头部的回车符和换行符
\r\n
# 最后的回车符和换行符
使用流的TCP回显客户端和服务器
使用流的TCP回显客户端
tcp_stream_client.py
1234567891011121314151617181920import
asyncio
# 回显客户端协程函数,传递参数message发送给服务器端,服务器端接收信息原样返回
async
def
tcp_echo_client(message):
# 创建reader,writer对象分别用于接收和发送信息
reader,writer
=
await asyncio.open_connection(
'127.0.0.1'
,
8888
)
print
(f
'Send:{message!r}'
)
# 往服务器端写信息,需要编码后发送
writer.write(message.encode())
# await writer.drain()
# 从服务器端读取信息读取100个字节
data
=
await reader.read(
100
)
# 打印解码后的信息
print
(f
'Received:{data.decode()!r}'
)
print
(
'Close the connection'
)
# 关闭
writer.close()
# await writer.wait_closed()
asyncio.run(tcp_echo_client(
'Hello World!'
))
注意:这里没有使用writer.drain()和writer.wait_closed()也可以
使用流的TCP回显服务器端
tcp_stream_server.py
12345678910111213141516171819202122232425262728293031323334353637383940414243import
asyncio
# 启动服务后当客户端建立新连接时调用该函数
# 接受参数为reader,writer
# reader是类StreamReader的实例,而writer是类StreamWriter的实例
# 即客户端和服务器端的reader和writer是一一对应的,分别用于接收对方数据流和往对方发送数据流
async
def
handle_echo(reader, writer):
# 服务器从客户端读取信息
# 即客户端通过writer往服务器写的信息
data
=
await reader.read(
100
)
# 信息解码
message
=
data.decode()
# 该方法获取客户端的ip地址信息
addr
=
writer.get_extra_info(
'peername'
)
print
(f
"Received {message!r} from {addr!r}"
)
print
(f
"Send: {message!r}"
)
# 服务器端把从客户端读取的信息又发送给客户端
writer.write(data)
await writer.drain()
# 关闭连接
print
(
"Close the connection"
)
writer.close()
async
def
main():
# start_server()方法启动套接字服务,返回一个server对象
# 当一个新的客户端连接被建立时,回调函数会被调用。该函数会接收到一对参数(reader,writer)
# reader是类StreamReader的实例,而writer是类StreamWriter的实例
# client_connected_cb 即可以是普通的可调用对象也可以是一个 协程函数; 如果它是一个协程函数,它将自动作为 Task 被调度。
server
=
await asyncio.start_server(handle_echo,
'127.0.0.1'
,
8888
)
# 以下方法可以获取启动的ip地址和端口信息返回一个元组其实即使start_server方法传递的ip和端口信息('127.0.0.1',8888)
addr
=
server.sockets[
0
].getsockname()
print
(f
'Serving on {addr}'
)
# 启动服务端
# Server对象是异步上下文管理器。当用于async with语句时,异步上下文管理器可以确保Server对象被关闭
# 并且在async with完成后不接受新的连接。
async with server:
# server_forver()方法
# 开始接受连接,直到协程被取消。server_forever任务的取消将导致服务器被关闭
await server.serve_forever()
asyncio.run(main())
打开两个窗口,先启动服务器端
服务器端开启了8888端口等待客户端连接
运行客户端
运行客户端的时候客户端和服务器端建立了连接才启动协程函数handle_echo
服务器端接收的信息为
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
2017-10-30 Oracle之catalog恢复目录的创建于维护(51CTO风哥rman课程)
2017-10-30 Oracle涂抹oracle学习笔记第8章RMAN说,我能备份