文章目录
Flask-SocketIO 是 Flask 框架的一个扩展,它简化了在 Flask 应用中集成 WebSocket 的流程,支持实时双向通信。与原生 WebSocket 库相比,它不仅实现了 WebSocket 协议,还提供了自动降级(在不支持 WebSocket 的环境下使用轮询等方式)、事件驱动、房间机制(分组通信)等高级功能,非常适合开发实时聊天、实时数据展示、协作工具等场景。
一、核心功能与优势
- 实时双向通信:客户端与服务器可随时互相发送消息,无需频繁 HTTP 请求;
- 事件驱动:通过“事件名”绑定处理函数,结构清晰(类似回调机制);
- 房间机制:支持将客户端分组(如“聊天室”),实现精准消息推送;
- 兼容性:自动适配不支持 WebSocket 的浏览器(降级为长轮询等);
- Flask 无缝集成:与 Flask 应用上下文、会话(Session)等特性兼容。
二、安装与环境准备
首先需要安装 Flask-SocketIO 及依赖的异步引擎(推荐 eventlet 或 gevent,用于处理并发连接):
# 安装核心库
pip install flask-socketio
# 安装异步引擎(二选一,eventlet 性能更好)
pip install eventlet # 推荐
# 或
pip install gevent gevent-websocket
三、基本使用流程(代码示例)
以下通过一个“实时聊天应用”示例,展示 Flask-SocketIO 的核心用法,包括:后端事件处理、前端连接与通信、消息广播、房间机制等。
3.1 后端实现(Flask 服务器)
# app.py
from flask import Flask, render_template
from flask_socketio import SocketIO, emit, join_room, leave_room
# 初始化 Flask 应用
app = Flask(__name__)
# 配置 SECRET_KEY(用于会话管理,生产环境需替换为安全密钥)
app.config['SECRET_KEY'] = 'secret!'
# 初始化 SocketIO,指定异步引擎(eventlet)
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='eventlet') # 允许跨域(开发环境)
# 存储在线用户(示例用,实际可存在数据库/Redis)
online_users = {}
# 1. 处理客户端连接事件(内置事件:'connect')
@socketio.on('connect')
def handle_connect():
print('客户端已连接')
# 向客户端发送连接成功消息
emit('server_response', {'data': '连接成功!'})
# 2. 处理客户端断开连接(内置事件:'disconnect')
@socketio.on('disconnect')
def handle_disconnect():
# 从在线用户中移除
user_id = None
for uid, sid in online_users.items():
if sid == request.sid: # request.sid 是当前连接的唯一标识
user_id = uid
break
if user_id:
del online_users[user_id]
# 广播用户离开
emit('user_status', {'data': f'用户 {user_id} 已离开'}, broadcast=True)
print('客户端已断开连接')
# 3. 自定义事件:用户登录(客户端发送用户名)
@socketio.on('user_login')
def handle_login(user_id):
# 记录用户 ID 与连接标识(sid)的映射
online_users[user_id] = request.sid
# 广播用户加入
emit('user_status', {'data': f'用户 {user_id} 已加入'}, broadcast=True)
# 向当前用户发送在线列表
emit('online_users', {'users': list(online_users.keys())})
# 4. 自定义事件:发送公共消息(广播给所有用户)
@socketio.on('public_message')
def handle_public_message(data):
"""处理公共消息,格式:{'user_id': 'xxx', 'message': 'xxx'}"""
user_id = data['user_id']
message = data['message']
print(f'收到公共消息:{user_id} 说:{message}')
# 广播消息给所有客户端(包括发送者)
emit('new_public_message', {
'user_id': user_id,
'message': message,
'time': '刚刚'
}, broadcast=True)
# 5. 房间机制:加入指定房间(如“聊天室1”)
@socketio.on('join_room')
def handle_join_room(data):
"""加入房间,格式:{'user_id': 'xxx', 'room': 'xxx'}"""
user_id = data['user_id']
room = data['room']
# 加入房间(内部维护房间与用户的映射)
join_room(room)
# 向房间内所有用户发送通知(除了自己)
emit('room_notice', {
'data': f'用户 {user_id} 加入了房间 {room}'
}, room=room, include_self=False)
# 向自己发送确认
emit('room_notice', {
'data': f'已成功加入房间 {room}'
})
# 6. 房间机制:发送房间消息(仅房间内用户可见)
@socketio.on('room_message')
def handle_room_message(data):
"""发送房间消息,格式:{'user_id': 'xxx', 'room': 'xxx', 'message': 'xxx'}"""
user_id = data['user_id']
room = data['room']
message = data['message']
# 向指定房间广播消息
emit('new_room_message', {
'user_id': user_id,
'message': message,
'room': room
}, room=room)
# 路由:渲染聊天页面
@app.route('/')
def index():
return render_template('chat.html')
if __name__ == '__main__':
# 启动服务器(使用 socketio.run 而非 app.run)
socketio.run(app, host='0.0.0.0', port=5000, debug=True)
3.2 前端实现(HTML/JavaScript)
前端需要引入 Socket.IO 客户端库(与后端版本兼容),通过事件与服务器通信:
<!-- templates/chat.html -->
<!DOCTYPE html>
<html>
<head>
<title>Flask-SocketIO 聊天示例</title>
<style>
.container { max-width: 800px; margin: 0 auto; padding: 20px; }
#messages { height: 400px; border: 1px solid #ccc; padding: 10px; overflow-y: auto; margin-bottom: 10px; }
.input-area { display: flex; gap: 10px; }
input, button { padding: 8px; flex: 1; }
button { flex: 0 0 80px; }
.room-section { margin-top: 20px; padding-top: 20px; border-top: 1px solid #eee; }
</style>
</head>
<body>
<div class="container">
<h1>实时聊天</h1>
<!-- 登录区域 -->
<div>
<input type="text" id="user_id" placeholder="输入用户名">
<button onclick="login()">登录</button>
</div>
<!-- 公共消息区域 -->
<div id="messages"></div>
<div class="input-area">
<input type="text" id="public_msg" placeholder="输入公共消息">
<button onclick="sendPublicMessage()">发送</button>
</div>
<!-- 房间聊天区域 -->
<div class="room-section">
<h3>房间聊天</h3>
<div>
<input type="text" id="room_id" placeholder="房间号">
<button onclick="joinRoom()">加入房间</button>
</div>
<div class="input-area">
<input type="text" id="room_msg" placeholder="输入房间消息">
<button onclick="sendRoomMessage()">发送到房间</button>
</div>
</div>
</div>
<!-- 引入 Socket.IO 客户端(版本需与后端兼容,推荐 4.x) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
<script>
// 连接到 Flask-SocketIO 服务器(自动处理 WebSocket 或降级方案)
const socket = io.connect('http://' + document.domain + ':' + location.port);
let currentUser = null; // 当前登录用户
let currentRoom = null; // 当前房间
// 1. 监听服务器连接成功事件
socket.on('server_response', function(data) {
addMessage(`系统消息:${data.data}`);
});
// 2. 监听用户状态变化(加入/离开)
socket.on('user_status', function(data) {
addMessage(`<strong>${data.data}</strong>`);
});
// 3. 监听在线用户列表
socket.on('online_users', function(data) {
addMessage(`当前在线用户:${data.users.join(', ')}`);
});
// 4. 监听新的公共消息
socket.on('new_public_message', function(data) {
addMessage(`[${data.time}] ${data.user_id}:${data.message}`);
});
// 5. 监听房间通知(加入/系统消息)
socket.on('room_notice', function(data) {
addMessage(`<em>房间通知:${data.data}</em>`);
});
// 6. 监听房间消息
socket.on('new_room_message', function(data) {
addMessage(`[房间 ${data.room}] ${data.user_id}:${data.message}`);
});
// 辅助函数:添加消息到页面
function addMessage(text) {
const messagesDiv = document.getElementById('messages');
const p = document.createElement('p');
p.innerHTML = text;
messagesDiv.appendChild(p);
// 滚动到底部
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
// 登录函数
function login() {
const user_id = document.getElementById('user_id').value.trim();
if (user_id) {
currentUser = user_id;
socket.emit('user_login', user_id); // 发送登录事件到服务器
addMessage(`您已以 ${user_id} 身份登录`);
}
}
// 发送公共消息
function sendPublicMessage() {
if (!currentUser) {
alert('请先登录');
return;
}
const message = document.getElementById('public_msg').value.trim();
if (message) {
socket.emit('public_message', { // 发送公共消息事件
user_id: currentUser,
message: message
});
document.getElementById('public_msg').value = ''; // 清空输入
}
}
// 加入房间
function joinRoom() {
if (!currentUser) {
alert('请先登录');
return;
}
const room = document.getElementById('room_id').value.trim();
if (room) {
currentRoom = room;
socket.emit('join_room', { // 发送加入房间事件
user_id: currentUser,
room: room
});
}
}
// 发送房间消息
function sendRoomMessage() {
if (!currentUser || !currentRoom) {
alert('请先登录并加入房间');
return;
}
const message = document.getElementById('room_msg').value.trim();
if (message) {
socket.emit('room_message', { // 发送房间消息事件
user_id: currentUser,
room: currentRoom,
message: message
});
document.getElementById('room_msg').value = ''; // 清空输入
}
}
</script>
</body>
</html>
四、核心概念解析
4.1 事件驱动模型
Flask-SocketIO 基于“事件”通信:
- 客户端通过
socket.emit('事件名', 数据)发送事件; - 服务器通过
@socketio.on('事件名')装饰器绑定处理函数; - 内置事件:
connect(连接成功)、disconnect(连接断开)等,可直接监听; - 自定义事件:如示例中的
public_message、join_room等,需前后端事件名一致。
4.2 房间(Rooms)机制
用于将客户端分组,实现“定向消息推送”:
join_room(room_name):将当前客户端加入指定房间;leave_room(room_name):离开房间;- 发送消息时指定
room=room_name,仅该房间内的客户端能收到; - 示例:聊天室分组、多用户协作场景(如“项目A”房间的消息仅项目成员可见)。
4.3 广播(Broadcast)
- 发送消息时设置
broadcast=True,消息会被发送给所有连接的客户端(除发送者自己); - 若需包含发送者,可手动再向发送者单独发送一次。
五、注意事项
-
版本兼容性:
- 后端
flask-socketio与前端socket.io.js版本需兼容(如后端 5.x 对应前端 4.x); - 查看兼容表:Flask-SocketIO 文档。
- 后端
-
跨域问题:
- 开发环境可设置
cors_allowed_origins="*"允许所有域; - 生产环境需指定具体域名(如
cors_allowed_origins=["https://yourdomain.com"])。
- 开发环境可设置
-
生产环境部署:
- 不能使用
socketio.run(app, debug=True),需配合 WSGI 服务器(如 Gunicorn)+ 异步引擎(Eventlet/GeVent); - 示例命令:
gunicorn -k eventlet -w 1 app:app(单工作进程,Eventlet worker)。
- 不能使用
-
会话与认证:
- Flask-SocketIO 可访问 Flask 的
session对象(需配置SECRET_KEY); - 生产环境建议通过令牌(如 JWT)验证客户端身份,在
connect事件中检查。
- Flask-SocketIO 可访问 Flask 的
六、总结
Flask-SocketIO 极大简化了 Flask 应用的实时通信开发,通过事件驱动和房间机制,可快速实现聊天、实时数据监控、多人协作等功能。核心步骤是:
- 初始化 Flask 与 SocketIO 实例;
- 后端用
@socketio.on定义事件处理函数; - 前端引入客户端库,通过
socket.emit发送事件、socket.on监听事件; - 利用房间机制实现精准消息推送。
通过这种方式,开发者可专注于业务逻辑,无需深入处理 WebSocket 协议细节。
1379

被折叠的 条评论
为什么被折叠?



