flask-socketio使用遇到的坑,版本兼容问题和while True堵塞信息问题

本文介绍了使用Flask-SocketIO时遇到的版本兼容问题和在服务端使用while True堵塞信息的问题。强调了WebSocket的永久双向通信特性,以及flask_socketio如何封装WebSocket。在服务端向客户端推送消息时,讨论了send()和emit()的区别,同时指出在while True循环中使用emit可能导致的问题,并推荐使用后台线程解决。客户端通过socketio库与服务器建立命名域的WebSocket连接,确保实时通信。
  特别说明,下面解决方法是在按网上最容易查到的一个案例参考使用时,遇到了问题,反复使用仍没有解决情况下,找到了下面文章,发现是后台的while true问题,特别编辑理发一下,同时感谢原作者,引用地址如下,避免大家遇到同样问题。
  [https://www.cnblogs.com/luozx207/p/9714487.html](https://www.cnblogs.com/luozx207/p/9714487.html)

特别请大家注意,版本兼容问题:
在这里插入图片描述

websocket连接是客户端与服务器之间永久的双向通信通道,直到某方断开连接。
  双向通道意味着在连接时,服务端随时可以发送消息给客户端,反之亦然,这在一些需要即时通讯的场景比如多人聊天室非常重要。
  flask_socketio实现了对websocket的封装,它可以让运行flask应用的服务端和客户端建立全双工通道。
  flask_socketio是一个python库,是flask框架的扩展。

一、安装

pip install flask-socketio

二、实现对flask的封装

from flask import Flask, render_template
from flask_socketio import SocketIO,emit
app = 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_template
from flask_socketio import SocketIO,emit
app = 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.”。当一个客户端连接服务器的不同命名域的时候,可以在同一个socket连接里完成。我的理解是一个namespace就定义了一个后端websocket连接的接口,客户端与服务器通过三次握手建立socket连接后,连接不同的服务器接口,socket的连接并不会断开。这可以类比于http的路由(但是完全不同哦,因为传输协议完全不一样),在http连接范畴,当用户登录后,访问服务器不同的路由并不会改变它的登录状态。一个后端接口可以接受多个客户端的socket连接,如果在后端的emit中定义‘broadcast=True’,那么所有连接到这个命名域的客户端都会收到这个消息,命名域之间也可以通过发送消息指定命名域的方式来相互通信。
  再看soketio.emit,第一个参数’server_response’是服务端发送这个消息的事件名,在客户端要建立一个接受这个事件的函数处理,后面的字典就是消息内容,namespace=’/test_conn’表示这个消息还是发送到同一个信道(test_conn)中。emit发送信息只能从前端发到后端或者从后端发向前端,如果在在前端emit(‘event’,{data})再写socket.on(‘event’, {data})是收不到的。

四、定时推送

实验的目的是服务端定时发送一个随机数到客户端,并且客户端可以及时显示。
  一开始,我在socketio装饰的函数中写了一个while循环

from flask import Flask, render_template
from flask_socketio import SocketIO,emit
import random
async_mode = None
app = 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_template
from flask_socketio import SocketIO,emit
from threading import Lock
import random
async_mode = None
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
thread = None
thread_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, {transports: ['websocket']});
        socket.on('server_response', function(res) {
            console.log(res.data);
            $('#t').text(res.data);
        });
    });
</script>
</body>
</html>

注意客户端也要导入socketio的库,然后用io.connect建立命名域的socket连接。
  如果不加{transports: [‘websocket’]},实际上建立的是长轮询。长轮询或websocket都是由客户端发起的

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

评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值