Flask-SocketIO 的长连接到底是怎么建立的?服务端怎么知道客户端掉线了?多 worker 部署时广播为什么会丢?这篇用真实的 gunicorn、python-socketio 客户端和自己起的 redis,把握手包、断线时间和广播范围逐项打印出来。
GET /socket.io/?EIO=4&transport=polling → 200 text/plain; charset=UTF-8
正文: 0{"sid":"NQT5ExlZvCmWUVbuAAAA","upgrades":["websocket"],"pingTimeout":20000,"pingInterval":25000,"maxPayload":1000000}
POST 40(Socket.IO 的 CONNECT 包)→ 200 'OK'
GET 轮询取回 → 200 40{"sid":"htnndKup-mtkXqI1AAAB"}
发带 ack 编号的事件 421["echo","hi"] 后,轮询依次取回: ['431[{"pid":63933,"echo":"hi"}]']
用不存在的 sid 轮询 → 400 "Invalid session nope"上一篇:Flask-Mail;下一篇:Flask-CORS。
connect() 返回时(+103 ms)传输方式:websocket;升级后(+103 ms):websocket 服务端事件日志: ['connect sid=eCieyrwTzJztgxPEAAAB auth=None transport=polling'] 只允许 websocket 时 transport = websocket 只允许 polling 时 transport = polling (1.5 秒后仍是)
call("echo", {a:1}) 的返回值(来自服务端处理函数的 return,即 ack): {'pid': 64066, 'echo': {'a': 1}}
服务端 emit 回来的事件 joined: [{'room': 'r1', 'pid': 64066}]
connect 处理函数 return False 时客户端: ConnectionError: One or more namespaces failed to connect: /
服务端日志: ["connect sid=0Wz0BXaI7Lqg039wAAAB auth={'token': 'good'} transport=websocket", "connect sid=3IomF-AtMqiqGgcrAAAD auth={'token': 'bad'} transport=websocket"] A 向 r1 发 m1(include_self=True): {'A': ['m1'], 'B': ['m1'], 'C': []}
A 向 r1 发 m2(include_self=False): {'A': [], 'B': ['m2'], 'C': []}
A 广播 bcast(include_self=False): {'A': [], 'B': [('bcast', 'hello all')], 'C': [('bcast', 'hello all')]}客户端主动 disconnect():服务端 disconnect 的 reason = ['transport close'] kill -9 客户端进程(系统关掉 TCP 连接):服务端 0.01 秒后触发 disconnect,reason=transport close SIGSTOP 冻住客户端(进程还在、TCP 连接没关,只是不回应 ping):服务端 1.87 秒后触发 disconnect,reason=ping timeout
reason 的取值依赖库版本;这里只记录了 transport close 与 ping timeout 两种在本次实验里观察到的值。
无 message_queue 8 个客户端分布在 2 个 worker([2, 6]),A 广播(不含自己)后收到的客户端数:5(最多 7) message_queue=redis://127.0.0.1:9652 8 个客户端分布在 2 个 worker([1, 7]),A 广播(不含自己)后收到的客户端数:7(最多 7)
客户端用的都是 websocket-only 传输。轮询传输在多 worker 下需要粘性会话,这一点本篇没有验证。
polling 中位数 1.30 ms,最大 1.8 ms websocket 中位数 0.28 ms,最大 2.1 ms