B站: 迷途小书童的Note;微信公众号: Dev_Club;个人微信:xituxiaoshutong100

Flask教程(十九)SocketIO

Flask Web 迷途小书童 0评论

软硬件环境

视频看这里

此处是youtube的播放链接,需要科学上网。喜欢我的视频,请记得订阅我的频道,打开旁边的小铃铛,点赞并分享,感谢您的支持。

什么是websocket

WebSocket是一种网络通讯协议,与HTTP不同的是,WebSocket提供全双工通信。也就是说,传统的方式,只有当客户端发起请求后,服务器端才会发送数据,而WebSocket可以让服务器主动发送数据给客户端,它是服务器推送技术的一种。

下图是WebSocketHTTP的区别

websocket

websocketd

websocketd是一款非常不错的WebSocket服务器,我们首先利用它来了解下标准WebSocket的交互方式。先来下载windows版本的websocketd,解压之后进入目录

下面开始编写测试脚本,使用python语言,文件名是test.py,脚本的作用是周期性的向标准输出stdout打印一个数值

from sys import stdout
from time import sleep

for count in range(0, 10):
  print(count + 1)
  stdout.flush()
  sleep(0.5)

开启服务

websocketd --port 8080 python test.py

现在需要一个WebSocket客户端,我们来到chrome商店,安装插件 https://chrome.google.com/webstore/detail/websocket-test-client/fgponpodhbmadfljofbimhhlengambbn , 然后打开插件,输入WebSocketurl就可以看到服务器端的输出了

websocketd

websocketd

websocketd

flask-socketio

Flask-SocketIO使Flask应用程序能够访问客户端和服务器之间的低延迟双向通信。客户端应用程序可以使用JavascriptC++JavaSwift中的任何SocketIO官方客户端库或任何兼容的客户端来建立与服务器的永久连接。

异步模式

flask-socketio需要底层异步服务的支持,比较灵活的是,它会自己检测当前环境中存在的异步服务,且使用顺序为 eventlet –> gevent –> werkzeug

  • eventlet 性能最佳,支持长轮询和WebSocket传输
  • gevent 在许多不同的配置中得到支持。gevent完全支持长轮询传输,但与eventlet不同,gevent没有本机WebSocket支持。要添加对WebSocket的支持,目前有两种选择:安装gevent-websocket包为gevent增加WebSocket支持,或者可以使用带有WebSocket功能的uWSGI Web服务器。gevent的使用也是一种高性能选项,但略低于eventlet
  • werkzeug 也可以使用基于werkzeugFlask开发服务器,但需要注意的是,它缺乏其他两个选项的性能,因此只应用于简单的开发环境,而且它也仅支持长轮询传输

安装

直接使用pip来安装

pip install eventlet
pip install flask-socketio

示例代码

先看服务端代码

文件run.py

from flask import Flask, render_template
from flask_socketio import SocketIO, emit


app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret_key'

socketio = SocketIO()
socketio.init_app(app, cors_allowed_origins='*')

name_space = '/dcenter'


@app.route('/')
def index():
    return render_template('index.html')


@app.route('/push')
def push_once():
    event_name = 'dcenter'
    broadcasted_data = {'data': "test message!"}
    socketio.emit(event_name, broadcasted_data, broadcast=False, namespace=name_space)
    return 'done!'


@socketio.on('connect', namespace=name_space)
def connected_msg():
    print('client connected.')


@socketio.on('disconnect', namespace=name_space)
def disconnect_msg():
    print('client disconnected.')


@socketio.on('my_event', namespace=name_space)
def mtest_message(message):
    print(message)
    emit('my_response',
         {'data': message['data'], 'count': 1})


if __name__ == '__main__':

    socketio.run(app, host='0.0.0.0', port=5000, debug=True)

代码中,我们设定了命名空间namespace,因为客户端在进行连接的时候需要用到的url就包含namespace,另外还有个概念,就是eventconnectdisconnect还有自定义的my_event都是

文件index.html的代码如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>SocketIO Demo</title>
    <script type="text/javascript" src="//cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
    <script type="text/javascript" src="//cdn.bootcss.com/socket.io/1.5.1/socket.io.min.js"></script>
</head>
<body>

<h2>Demo of SocketIO</h2>
<div id="t"></div>
<script>
$(document).ready(function () {
    namespace = '/dcenter';
    var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace);
    socket.on('dcenter', function (res) {
        var t = res.data;
        if (t) {
            $("#t").append(t).append('<br/>');
        }

    });
});
</script>
</body>
</html>

启动服务后,我们访问 http://127.0.0.1:5000, 并保持这个页面不动,因为,后面接收到的消息会显示在这个页面上

flask-socketio

这时候,使用浏览器访问 http://127.0.0.1:5000/push

flask-socketio

我们使用这种方式来触发服务器发送消息给客户端,再次回到之前的页面,可以看到已经把服务器端发送过来的消息显示在了页面上

flask-socketio

注意到,上面javascript中,url中使用的协议是http,而不是ws,这一点跟原生的WebSocket有点区别。

cors跨域错误

如果是前后端分离的系统中,会出现如下的错误

Traceback (most recent call last):
  File "src\\gevent\\greenlet.py", line 854, in gevent._gevent_cgreenlet.Greenlet.run
  File "C:\ProgramData\Anaconda3\envs\FlaskTutorial\lib\site-packages\gevent\baseserver.py", line 34, in _handle_and_close_when_done
    return handle(*args_tuple)
  File "C:\ProgramData\Anaconda3\envs\FlaskTutorial\lib\site-packages\gevent\server.py", line 233, in wrap_socket_and_handle
    with _closing_socket(self.wrap_socket(client_socket, **self.ssl_args)) as ssl_socket:
TypeError: wrap_socket() got an unexpected keyword argument 'cors_allowed_origins'
2020-09-09T08:16:07Z <Greenlet at 0x1812f1e8bf8: _handle_and_close_when_done(<bound method StreamServer.wrap_socket_and_handle , <bound method StreamServer.do_close
of <WSGIServer, (<gevent._socket3.socket [closed] at 0x1812fc41cc8)> failed with TypeError

这是跨域的问题,经测试发现,使用之前介绍过的flask-cors来处理,其实并不起作用,我们需要在socketio初始化的时候加入必要的参数

socketio = SocketIO()
socketio.init_app(app, cors_allowed_origins='*')

详细的信息可以参考这个链接, https://github.com/eventlet/eventlet/issues/526

源码下载

https://github.com/xugaoxiang/FlaskTutorial

参考资料

喜欢 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址