草庐IT

Python基于Flask框架实现Websocket通信(待更新)

唤醒手腕 2023-07-04 原文

Websocket 和 Http

WebSocket 是 HTML5 出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算)

首先HTTP有 1.1 和 1.0 之说,也就是所谓的 keep-alive ,把多个HTTP请求合并为一个,但是 Websocket 其实是一个新协议,跟HTTP协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,也就是说它是HTTP协议上的一种补充可以通过这样一张图理解:

有交集,但是并不是全部

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

服务器端 flask_sockets

flask实现 websocket 的两种方式

第一种是 flask_sockets 方式,该方式是flask对websocket的最原始封装,功能较为单一。

第二种方式 Flask-SocketIO 对 websocket 的封装,该方式所能提供功能较多,不但实现了socket的基本通信功能,也可以结合flask相关接口,使其更加完备,因此网上对该api介绍也较多。

flask_sockets 实现介绍

@sockets.route('/echo')
def echo_socket(ws):
    print("connection start")
    while not ws.closed:
        msg = ws.receive() # 同步阻塞
        print(msg)
        now = datetime.datetime.now().isoformat()
        ws.send(now)  # 发送数据
        time.sleep(1)

客户端发送数据实现

<button onclick="send()">发送</button>


var ws = new WebSocket("ws://127.0.0.1:8080/echo")
ws.onmessage = function (event) {
    console.log(event.data)
};

function send(){
    ws.send("hello world")
}

服务器端完整代码展示

from flask import Flask
from flask_sockets import Sockets
import datetime

app = Flask(__name__)
sockets = Sockets(app)

from flask_cors import *
CORS(app, supports_credentials=True)

@sockets.route('/echo')
def echo_socket(ws):
    print("hello")
    while not ws.closed:
        msg = ws.receive()
        print(msg)
        now = datetime.datetime.now().isoformat()
        ws.send(now)  #发送数据

@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == "__main__":
    from gevent import pywsgi
    from geventwebsocket.handler import WebSocketHandler
    server = pywsgi.WSGIServer(('0.0.0.0', 8080), app, handler_class=WebSocketHandler)
    print('server start')
    server.serve_forever()
var ws = new WebSocket("ws://127.0.0.1:8080/echo")
ws.onmessage = function (event) {
    console.log(event.data)
}
ws.onopen = function() {
    console.log("start connection")
}
ws.onclose = function(e){
    console.log("connection closed (" + e.code + ")");
}

服务器端 Flask-SocketIO

debug的好处:已经run过的程序,Ctrl+S,保存一下,自动重启,(节省时间,提高效率)

可以分为开发模式,和生产模式;在程序员自己写代码的时候,即 app.run(debug=True),写完程序,递交项目最终成果时,要将其关闭,即 app.run()

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 = '/echo'

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

@app.route('/push')
def push_once():
    event_name = 'echo'
    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)

客户端的实现:

<div id="data_show"></div>

<script>
$(document).ready(function () {
    namespace = '/echo';
    var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace);
    socket.on('echo', function (res) {
        var data_show = res.data;
        if (data_show) {
            $("#data_show").append(data_show).append('<br/>');
        }
    });
});
</script>

socketio 中的 namespace 的概念

如果希望服务端发送的信息在所有客户端都能收到,那么使用默认的namespace / 就好了。但是如果想把发送信息的服务器作为第三方应用给不同客户端使用,就需要为每一个客户端定义一个namespace.

跨域问题的解决方案:

采用 flask_cors 进行解决跨域问题

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

版本不兼容问题:

pip list 查看 服务器端 Flask-SocketIO 版本

查看 vue 客户端的 socketIO 版本

Vue 使用 socket.io

通过最近一系列的踩坑之后,总结出了这篇相对较为完善的关于vue-socket.io的使用文章,包含大家困扰的(socket连接、断开重连,以及当连接的地址是动态的情况下,我们应该注意的事项。

如何安装 socket.io

npm install vue-socket.io --save

第一种:引用 socket.io,在组件中直接引入

import io from 'socket.io-client'

第二种:引用 socket.io,在 main.js 中直接这样写

import VueSocketIO from 'vue-socket.io'

Vue.use(new VueSocketIO({
    debug: true,
    connection: 'ws://localhost:5000/echo',  //
}))

这里要提示的一点就是,当连接的地址是动态的,代码的执行顺序就很重要了,即 new VueSocket 在main.js中的位置

如何进行建立 socket.io 连接

在 html 中直接实现:

 <html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.bootcdn.net/ajax/libs/socket.io/4.4.1/socket.io.min.js"></script>
    <title>socket.io</title>
</head>
    <button onclick="send()">发送信息</button>
<body>
    <script>
        var socket = null
        function initWebSocket() {
            socket = io.connect('ws://localhost:5000/echo', {
                timeout: 300000,
                reconnectionDelayMax: 1000,
                reconnectionDelay: 500
            })
            socket.on('connect', () => {
                console.log('建立链接')
                socket.emit('message', { 'data': 'I\'m connected!' })
            })
            socket.on('disconnect', () => {
                console.log('连接断开')
                socket.emit('message', { 'data': 'I\'m disconnected!' });
            })
            socket.on('card message', msg => {
                // 接受数据
            })
            socket.on('error message', msg => {
                console.log('error:' + msg)
                
            })
        }
        function send(){
            socket.emit('message', { 'data': 'I\'m disconnected!' })
        }

        initWebSocket()
    </script>
</body>

</html>

在 Vue 组件 中直接实现:

initWebSocket () {
	this.socket = null
	this.socket = io.connect('ws://localhost:5000/namespace', {
		timeout: 300000,
		reconnectionDelayMax: 1000,
		reconnectionDelay: 500
	})
	this.socket.on('connect', () => {
		console.log('建立链接')
		this.socket.emit(传参)
	})
	this.socket.on('disconnect', () => {
		console.log('连接断开')
	})
	this.socket.on('card message', msg => {
	 	// 接受数据
	})
	this.socket.on('error message', msg => {
		console.log('error:' + msg)
	})
}

网页客户端实现

首先看下四个方法

onclose: ((this: WebSocket, ev: CloseEvent) => any) | null;

onerror: ((this: WebSocket, ev: Event) => any) | null;

onmessage: ((this: WebSocket, ev: MessageEvent) => any) | null;

onopen: ((this: WebSocket, ev: Event) => any) | null;

建立 ws 对象,绑定 onmessage 方法进行接受服务器端传来的数据

var ws = new WebSocket("ws://127.0.0.1:8080/echo")

ws.onmessage = function (event) {
    console.log(event.data)
};

ws.onopen = function() {
   console.log("start connection")
};

ws.onclose = function(e){
   console.log("connection closed (" + e.code + ")");
};

BootCDN 稳定、快速、免费的前端开源项目 CDN 加速服务

https://cdn.bootcdn.net/ajax/libs/socket.io/4.4.1/socket.io.esm.min.js

<script src="https://cdn.bootcdn.net/ajax/libs/socket.io/4.4.1/socket.io.esm.min.js"></script>

常见问题报错解决方案

ImportError 无法导入 ContextVar 包

ImportError: cannot import name ‘ContextVar’

pip uninstall flask   
# 卸载  你也可以省略这步,直接pip install flask==1.0
pip install flask==1.0   
# 我这里改成1.0就行了,改成其他低版本应该也没问题,有兴趣自己试下

关于 Werkzeug 缺失,或者版本过低问题

werkzeug.routing.WebsocketMismatch: 400 Bad Request: The browser (or proxy) sent a request that this server could not understand.

2022-04-27T12:31:24Z { REMOTE_ADDR : 127.0.0.1 , REMOTE_PORT : 63825 , HTTP_HOST : 127.0.0.1:8080 , (hidden keys: 30)} failed with WebsocketMismatch

pip install Werkzeug==1.0.1 -i https://mirrors.aliyun.com/pypi/simple/

flask 缺少 Werkzeug,或者 Werkzeug 版本不兼容 flask 等问题

flask 2.0.2 requires Werkzeug>=2.0, but you have werkzeug 1.0.1 which is incompatible.

关于客户端的 socket 和 flask 端的 socket 版本不兼容

The client is using an unsupported version of the Socket.IO or Engine.IO protocols (further occurrences of this error will be logged with level INFO)

有关Python基于Flask框架实现Websocket通信(待更新)的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  3. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  4. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  5. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  6. Python 相当于 Perl/Ruby ||= - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。

  7. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  8. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  9. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  10. python - 如何读取 MIDI 文件、更改其乐器并将其写回? - 2

    我想解析一个已经存在的.mid文件,改变它的乐器,例如从“acousticgrandpiano”到“violin”,然后将它保存回去或作为另一个.mid文件。根据我在文档中看到的内容,该乐器通过program_change或patch_change指令进行了更改,但我找不到任何在已经存在的MIDI文件中执行此操作的库.他们似乎都只支持从头开始创建的MIDI文件。 最佳答案 MIDIpackage会为您完成此操作,但具体方法取决于midi文件的原始内容。一个MIDI文件由一个或多个音轨组成,每个音轨是十六个channel中任何一个上的

随机推荐