Flask-SocketIO实现WebSocket双向通信教程
本文详解如何利用Flask-SocketIO在Python后端与前端JavaScript之间建立WebSocket双向通信。涵盖HTML5 WebSocket原理、socket.io.js库的使用、前后端事件监听与消息收发机制,以及flask-socketIO的装饰器配置。同时探讨了Manager启动方式可能导致的消息缓存问题,建议直接使用socketio.run以保证实时性。
【flask-socketio】
此前曾探讨过从Web后端向前端推送消息的技术。反向Ajax(Reverse Ajax)是其中一种模式,其核心在于只要客户端与服务器保持交互,服务器就必须维持该客户端的连接请求,并在适当时机返回数据。然而,当客户端数量庞大时,这种维持长连接的方式会消耗巨大的服务器资源,成本较高。
除了反向Ajax,还有隐藏iframe、Comet、长轮询等其他后端推前端的技術。这些技术各有优劣,具体选择需视场景而定,但均未深入展开讨论。
在近期的开发中,遇到一个典型场景:后台执行耗时任务,需实时向前端推送执行进度。若使用反向Ajax,由于消息频率较高,会消耗大量网络资源;且Ajax请求与后台工作进程并行,获取进度可能涉及复杂的进程间通信。因此,理想的方案是后台能随时推送数据,前端实时展示,且后台程序无需阻塞即可继续运行。
为此,可以寻找HTML5引入的WebSocket技术。此外,发现flask-socketio这一扩展模块为Flask框架提供了对WebSocket的支持,恰好满足上述需求。
概述
WebSocket是HTML5定义的一种新协议,支持服务端与客户端进行双向文本或二进制数据通信。它独立于HTTP协议,本质上与HTTP无关,但在形式上仍有相似之处。WebSocket的连接地址格式为ws://localhost:8080,其中协议修饰符为ws而非http。
WebSocket在连接建立阶段通过HTTP握手进行,这是为了兼容浏览器或利用现有功能而采取的折中方案。一旦连接建立,客户端与服务端之间的通信将完全由WebSocket接管,不再使用HTTP协议进行交互。
从资源占用角度看,WebSocket可能比Ajax占用更多资源,但其实现的全双工通信特性使得前后端交互程序的编写更加便捷。由于采用了新协议,需要对前后端代码进行相应改造。
前端WebSocket编写及socket.io.js
由于WebSocket是较新的技术,并非所有浏览器都支持。因此,可以使用以下代码判断浏览器是否支持WebSocket:
if ('WebSocket' in window){
websocket = new WebSocket('ws://localhost:8080');
}
仅当浏览器支持WebSocket时,才启动相应的处理逻辑。
实现双向通信本身并非最终目的,关键在于通信过程中前后端能执行特定动作,这需要添加事件监听。在前端,可以通过为WebSocket对象绑定监听回调接口,规定在不同场景下前端执行的操作。例如:
wesocket.onopen = function(){
alert('建立websocket连接');
}
websocket.onerror = function(){
alert('WebSocket连接发生错误');
}
/****
等等,由于有封装程度更高的js模块,就不扩展写从较底层构建websocket的方法了
****/
socket.io.js
若觉得底层实现繁琐,可以使用封装好的WebSocket JS库,如socket.io.js。该库虽主要为Node.js设计(网上相关教程多结合Node.js),但单独使用亦无不可。若通过CDN引用,可编写如下代码:
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.6/socket.io.min.js"></script>
应用socket.io.js创建简单Socket对象的代码如下:
var websocket_url = 'http"//' + document.domain + ':' + location.port + '/testnamespace'; //没错是用http开头的url了,因为这个库会自动解析并帮我们创建websocket对象的 //最后的namespace是websocket中的命名空间,后面再讲 var socket = io.connect(wesocket_url);
获得Socket对象后,即可进行消息收发。简单的消息收发示例如下:
//发送消息
socket.emit('request_for_response',{'param':'value'});
//监听回复的消息
socket.on('response',function(data){
if (data.code == '200'){
alert(data.msg);
}
else{
alert('ERROR:' + data.msg);
}
});
其中'request_for_response'和'reponse'为自定义的事件名,需与后端对应名称保持一致以确保通信成功。此外,涉及命名空间(namespace)的概念,消息收发均在'testnamespace'这一特定空间内进行,因此后端也必须配置相同的命名空间。
后端Socket编写(flask-socketio)
此处以Python后端为例。Python中有socketio模块,但直接编写底层代码较为僵硬。鉴于各类Web框架对WebSocket的支持日益完善,这里选用Flask框架的flask-socketio扩展。
flask-socketIO的创建与运行方式如下:
from flask import Flask from flask_socketio import SocketIO,emit app = Flask(__name__) socketio = SocketIO() socketio.init_app(app) """ 对app进行一些路由设置 """ """ 对socketio进行一些监听设置 """ if __name__ == '__main__': socketio.run(app,debug=True,host='0.0.0.0',port=5000) #这里就不再用app.run而用socketio.run了。socketio.run的参数和app.run也都差不多
上述代码中,App的路由设置不再赘述,重点在于SocketIO的监听设置,这直接关乎前后端WebSocket通信。结合前端代码,SocketIO的监听设置可如下配置:
@socketio.on('request_for_response',namespace='/testnamespace')
def give_response(data):
value = data.get('param')
#进行一些对value的处理或者其他操作,在此期间可以随时会调用emit方法向前台发送消息
emit('response',{'code':'200','msg':'start to process...'})
time.sleep(5)
emit('response',{'code':'200','msg':'processed'})
SocketIO同样采用类似app.route的装饰器形式进行监听设置,主要参数包含namespace,用于指定监听范围。在前端,只有注册在testnamespace上的Socket,向'request_for_response'发送的消息才会被此函数接收处理。处理函数自带一个参数,用于接收前端emit消息中的对象,并在函数内解析处理。随后,后端通过emit方法向前端发送'start to process'消息,并指定监听事件为'response'。这意味着前端在'response'上的监听处理函数将处理该消息(同样在testnamespace框架内)。发出消息后,后端不会被阻塞,而是继续向下执行。在处理5秒后,后端发出结束处理的消息,前端在等待5秒后接收该消息。
Socket监听响应函数本身无需返回值,只需在处理过程中的适当位置调用emit发送消息即可。
网上部分教程建议使用send方法替代emit(无论前端还是后端)。实际上,send方法是将'request_for_response'和'response'这两个标识默认设为'message'。这样编写时无需指定事件名,直接传递参数即可。反之,使用emit方法相当于执行自定义事件,灵活性更高。
略大项目中Manager的支持
通常,上述socketio.run方式给人的感觉仅适用于小型项目。若开发大型项目,需采用更高级的启动方式,并配置独立的config.py,通过app.config.from_object方法填充配置。
然而,经实验发现,虽然使用manager.run可使socketio工作,但似乎存在类似缓存的机制。即后台emit发出的信息并非直接发送到前端,而是在整个响应函数执行结束后一次性发送。这可能是后端发送时的缓存,也可能是前端接收时的缓存所致。
因此,若使用WebSocket,建议尽量采用socketio.run这种方式启动。配置可通过在socketio.run中添加类似debug=current_app.config['DEBUG'], host=current_app.config['HOST']的参数来实现。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









