Flask-SocketIO必须搭配Eventlet或gevent,因Werkzeug服务器同步阻塞无法维持WebSocket长连接;Eventlet提供协程并发支持,避免退化为轮询;需正确安装、补丁、配置async_mode及CORS,并注意客户端路径与版本匹配。

Flask 本身不支持 WebSocket,直接用 flask_socketio 而不配好异步服务器(比如 Eventlet 或 gevent),请求会卡死、连接超时、connect 事件不触发——这不是代码写错了,是运行时环境没对上。
为什么 Flask-SocketIO 必须搭配 Eventlet(或 gevent)?
Flask-SocketIO 是个封装层,底层靠异步服务器提供 WebSocket 协议支持。默认的 Flask 开发服务器(Werkzeug)是同步阻塞的,根本无法维持长连接。Eventlet 提供了协程式并发,能同时处理成百上千个 WebSocket 连接,且与 flask_socketio 集成最平滑。
- 不装 Eventlet,启动时会看到警告:
Server initialized for eventlet变成Server initialized for threading—— 这意味着退化为轮询模拟,connect延迟高,emit不实时 - macOS 上用
pip install eventlet后可能报ImportError: No module named 'setuptools',需先升级:pip install --upgrade setuptools - Linux 下如果提示
eventlet.hubs.kqueue doesn't support your system,说明内核不支持 kqueue,加环境变量强制用 select:EVENTLET_NO_KQUEUE=1
初始化 SocketIO 实例时的三个关键点
不是简单 SocketIO(app) 就完事。顺序、参数、是否启用消息队列,都影响连接稳定性。
- 必须在
app = Flask(__name__)之后、app.run()之前初始化socketio,否则上下文绑定失败 - 显式指定 async_mode 很重要:
socketio = SocketIO(app, async_mode='eventlet'),避免自动 fallback 到 threading 模式 - 开发阶段可不配消息队列,但一旦部署到多进程(如用 nginx + 多个 gunicorn worker),必须配 Redis:
socketio = SocketIO(app, message_queue='redis://'),否则不同进程间的emit(..., broadcast=True)会丢失
前端连接 URL 容易漏掉 /socket.io/ 路径
很多人照着文档写 io('http://localhost:5000'),结果控制台报错 GET http://localhost:5000/socket.io/?EIO=4&transport=polling&t=... 404 —— 因为 flask_socketio 默认把 Socket.IO 的 HTTP 接口挂载在 /socket.io/,不是根路径。
立即学习“Python免费学习笔记(深入)”;
- 正确写法:
const socket = io('http://localhost:5000');(注意:这里不用手动拼/socket.io/,socket.io-client会自动补全) - 但如果 Flask 应用用了反向代理(如 nginx),且配置了
location /ws { proxy_pass http://backend; },那前端就得显式指定 path:io('http://example.com', { path: '/ws/socket.io/' }) - Chrome 控制台 Network 标签里找
transport=websocket的请求,而不是transport=polling—— 后者说明降级了,可能是跨域、SSL 或路径不对导致握手失败
调试 connect/disconnect 事件不触发的常见原因
写了 @socketio.on('connect') 却没打印日志,不是装饰器失效,而是连接根本没建立成功。
- 检查服务端是否启用了 CORS:
SocketIO(app, cors_allowed_origins="*"),否则浏览器因跨域拦截预检请求(OPTIONS) - 确保客户端脚本加载的是匹配版本的
socket.io-client:flask_socketio5.x 对应 client 4.x,混用 5.x client 会静默失败 - Eventlet 补丁必须在所有 import 之前做:
import eventlet; eventlet.monkey_patch(),否则标准库(如 ssl、socket)未被协程化,TLS 握手会阻塞整个 hub - 使用
socketio.sleep(0)替代time.sleep(),否则在 handler 里调用后者会让整个 Eventlet hub 卡住
真正麻烦的从来不是写几行 @socketio.on,而是让底层异步栈每一层(client → reverse proxy → flask_socketio → eventlet → OS socket)都对齐协议和并发模型。少一个 monkey_patch,少一个 cors 配置,或者 npm 和 pip 装了不兼容的 major 版本,连接就悬在那里,既不断开也不就绪。


















