在 asyncio 中,client 通过 request_id 关联 Future 实现非阻塞等待:发送前创建 Future 并存入字典,server 响应时调用 set_result();使用 StreamReader/StreamWriter 配合 JSON 行协议,连接级唯一 ID 防重,禁用 pickle 改用 JSON 序列化。

asyncio 里怎么让 client 等 server 回应而不阻塞?
核心是用 asyncio.Queue 或 asyncio.Future 做请求-响应配对。不能靠全局 dict + while True 轮询,那会吃光 CPU;也不能直接用 await 一个未绑定事件的变量——得让每个请求生成唯一 request_id,server 返回时按 id 找到对应 Future 并 set_result()。
常见错误:client 发完请求就 await queue.get(),但没做超时或取消处理,一旦 server 挂了或丢包,client 就永远卡住。
- client 发请求前,先创建
loop.create_future(),存进本地字典:futures[req_id] = future - 用
asyncio.wait_for(future, timeout=5.0)包一层,避免无限等待 - server 收到请求后,处理完必须调用
futures[req_id].set_result(response)(注意:id 必须和 client 一致) - client 的接收协程要持续监听 socket,收到响应后立即查表并
set_result,否则 future 永远不会完成
用 asyncio.StreamReader/StreamWriter 还是 asyncio.Protocol?
选 asyncio.StreamReader/asyncio.StreamWriter。它更轻、更可控,适合 RPC 这种「一问一答」模型;而 asyncio.Protocol 更适合长连接、多路复用或自定义状态机的场景(比如 HTTP/2),反而容易把简单问题搞复杂。
性能影响明显:Protocol 默认启用 buffer_size 和 flow control,对小包 RPC 反而增加延迟;StreamReader 则允许你精确控制每次 readexactly(n) 或 readuntil(b'\n'),方便解析定长头或分隔符协议。
立即学习“Python免费学习笔记(深入)”;
- server 端用
async def handle_client(reader, writer),每个连接一个协程 - client 端用
asyncio.open_connection(host, port),拿到reader/writer后直接读写 - 协议建议用 JSON 行协议:每条消息
json.dumps({...}) + '\n',server 用await reader.readuntil(b'\n')解包,避免粘包 - 别在 writer.write() 后立刻 close(),要等
await writer.drain(),否则可能丢最后几字节
如何保证 request_id 不重复且可跨连接追踪?
request_id 不能只靠 uuid.uuid4().hex,因为 client 多个并发请求时没问题,但 server 如果重启、或 client 重连,旧 id 可能还在 pending 队列里——导致响应错配。真正安全的做法是:client 每次新连接从 1 开始计数,加上时间戳或连接 ID 做前缀,例如 f"{conn_id}_{seq}"。
容易踩的坑:用 time.time_ns() 当 id,看似唯一,但高并发下纳秒级也可能撞;或者用 threading.local() 存 seq,但在 asyncio 中协程切换不触发 local 变量隔离,会导致混乱。
- client 每建立一个新连接,生成唯一
conn_id = uuid.uuid4().hex[:8] - 每个连接内维护一个原子整数
self._seq = itertools.count(1)(注意不是全局 count) - 发请求时组合 id:
req_id = f"{conn_id}_{next(self._seq)}" - server 不需要保存 conn_id,只需原样透传响应,client 靠完整 id 查自己的 futures 字典
为什么不能直接 pickle 对象传给 asyncio socket?
因为 pickle 不安全:反序列化任意数据可能执行任意代码;而且不同 Python 版本间不兼容,server 用 3.11、client 用 3.9 就可能解不开。RPC 框架必须面向异构环境,JSON 是底线。
有人试过 msgpack 或 protobuf,可以,但前提是 client/server 协议版本严格对齐。简易框架优先选 JSON——调试友好、浏览器能直接看、出错时内容可读。
- 所有请求/响应体必须是 JSON-serializable:dict/list/str/int/float/None/bool
- 函数名、参数、错误信息都走 JSON 字段,例如
{"method": "add", "params": [1,2], "id": "a1b2_3"} - 异常不能直接抛
Exception,要转成{"error": {"code": -32603, "message": "division by zero"}} - 别在 JSON 里塞 bytes 或 datetime,先用
isoformat()或 base64 编码
future.set_result()——这两处没有报错,只有超时,排查时容易绕远。建议第一步先加日志:每发一个请求打一行 f"sending {req_id}",每收到响应打 f"got {req_id}",再看哪边断了。


















