使用flask_socketio实现服务端向客户端定时推送
2019-12-03

  websocket连接是客户端与服务器之间永久的双向通信通道,直到某方断开连接。

  双向通道意味着在连接时,服务端随时可以发送消息给客户端,反之亦然,这在一些需要即时通讯的场景比如多人聊天室非常重要。

  flask_socketio实现了对websocket的封装,它可以让运行flask应用的服务端和客户端建立全双工通道。

  flask_socketio是一个python库,是flask框架的扩展。

一、安装

pip install flask-socketio

 

二、实现对flask的封装

from flask import Flask, render_templatefrom flask_socketio import SocketIO,emitapp = Flask(__name__)app.config["SECRET_KEY"] = "secret!"socketio = SocketIO(app)if __name__ == "__main__": socketio.run(app, debug=True)

  socketio.run()函数封装了flask的web服务器的启动

 

三、服务端向客户端推送

  socketio的两个函数send()和emit()都可以实现消息发送,前者用于无名事件,后者用于命名的事件。

  事件是消息的名称。如果把消息比做信件,事件就是贴在信封上的标识,这个标识规定了信件送往客户端或服务端的某个函数。

from flask import Flask, render_templatefrom flask_socketio import SocketIO,emitapp = Flask(__name__)app.config["SECRET_KEY"] = "secret!"socketio = SocketIO(app)@socketio.on("connect", namespace="/test_conn")def test_connect(): socketio.emit("server_response", {"data": ‘connected’},namespace="/test_conn")if __name__ == "__main__": socketio.run(app, debug=True)

  比如上面socketio.on("connect",namespace="/test_conn")中的connect就是soketio的内置事件,当客户端与服务端连接之后,一个名为‘connect’的事件产生,服务端接到这个事件就会执行test_connect函数中的内容了。

  再说namespace,namespace可以标志多个事件,在官方文档的解释是“Namespaces allow a client to open multiple connections to the server that are multiplexed on a single socket.”。我的理解是一个namespace就定义了一个websocket连接,我们可以将多个事件放到一个连接中处理,否则就会有多个物理信道在使用。

  再看soketio.emit,第一个参数"server_response"是服务端发送这个消息的事件名,在客户端要建立一个接受这个事件的函数处理,后面的字典就是消息内容,namespace="/test_conn"表示这个消息还是发送到同一个信道(test_conn)中。

 

四、定时推送

  实验的目的是服务端定时发送一个随机数到客户端,并且客户端可以及时显示。

  一开始,我在socketio装饰的函数中写了一个while循环

from flask import Flask, render_templatefrom flask_socketio import SocketIO,emitimport randomasync_mode = Noneapp = Flask(__name__)app.config["SECRET_KEY"] = "secret!"socketio = SocketIO(app)@app.route("/")def index(): return render_template("index.html")@socketio.on("connect", namespace="/test_conn")def test_connect(): while True: socketio.sleep(5) t = random.randint(1, 100) socketio.emit("server_response", {"data": t},namespace="/test_conn")if __name__ == "__main__": socketio.run(app, debug=True)

  事实证明这样是行不通的,虽然看上去,虽然服务端陷入while的死循环中,但是emit函数每次都会执行,所以理论上客户端应该可以定时收到服务端的随机数。但是结果是客户端根本接收不到,连soketio.on函数都没有触发运行。

  原因应该是当服务端陷入死循环,会影响与客户端之间的websocket连接,总之写while true需谨慎

  在flask_socketio的示例程序中,我找到了用后台线程进行while循环以解决这个问题的方法。

from flask import Flask, render_templatefrom flask_socketio import SocketIO,emitfrom threading import Lockimport randomasync_mode = Noneapp = Flask(__name__)app.config["SECRET_KEY"] = "secret!"socketio = SocketIO(app)thread = Nonethread_lock = Lock()@app.route("/")def index(): return render_template("index.html")@socketio.on("connect", namespace="/test_conn")def test_connect(): global thread with thread_lock: if thread is None: thread = socketio.start_background_task(target=background_thread)def background_thread(): while True: socketio.sleep(5) t = random.randint(1, 100) socketio.emit("server_response", {"data": t},namespace="/test_conn")if __name__ == "__main__": socketio.run(app, debug=True)

 

五、客户端

  index.html的内容如下

<!DOCTYPE html><html><head> <meta charset="UTF-8"> <title></title> <script type="text/javascript" src="//code.jquery.com/jquery-1.4.2.min.js"></script> <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.5/socket.io.min.js"></script></head><body><h1 id="t"></h1><script type="text/javascript"> $(document).ready(function() { namespace = "/test_conn"; var socket = io.connect(location.protocol + "//" + document.domain + ":" + location.port + namespace); socket.on("server_response", function(res) { console.log(res.data); $("#t").text(res.data); }); });</script></body></html>

  注意客户端也要导入socketio的库,然后用io.connect建立命名域的socket连接。

  最后在浏览器输入http://127.0.0.1:5000就可以了

 

六、结果

 

  开发者工具的console可以查看日志