Websocket 和 Http
WebSocket 是 出的东西(协议),也就是说HTTPandroid系统定制开发协议没有变化,android系统定制开发或者说没关系,但HTTPandroid系统定制开发是不支持持久连接的(长连接,android系统定制开发循环连接的不算)
首先HTTP有 1.1 和 1.0 之说,也就是所谓的 keep-alive ,把多个HTTP请求合并为一个,但是 Websocket 其实是一个新协议,跟HTTP协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,也就是说它是HTTP协议上的一种补充可以通过这样一张图理解:
有交集,但是并不是全部
GET /chat HTTP/1.1Host: server.example.comUpgrade: websocketConnection: UpgradeSec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==Sec-WebSocket-Protocol: chat, superchatSec-WebSocket-Version: 13Origin: http://example.com
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
服务器端 flask_sockets
实现 websocket 的两种方式
第一种是 flask_sockets 方式,该方式是flask对websocket的最原始封装,功能较为单一。
第二种方式 Flask-SocketIO 对 websocket 的封装,该方式所能提供功能较多,不但实现了的基本通信功能,也可以结合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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
客户端发送数据实现
<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")}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
服务器端完整代码展示
from flask import Flaskfrom flask_sockets import Socketsimport datetimeapp = 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()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
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 + ")");}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
服务器端 Flask-SocketIO
debug的好处:已经run过的程序,Ctrl+S,保存一下,自动重启,(节省时间,提高效率)
可以分为开发模式,和生产模式;在程序员自己写代码的时候,即 app.run(debug=True),写完程序,递交项目最终成果时,要将其关闭,即 app.run()
from flask import Flask, render_templatefrom flask_socketio import SocketIO, emitapp = 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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
客户端的实现:
<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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
socketio 中的 namespace 的概念
如果希望服务端发送的信息在所有客户端都能收到,那么使用默认的namespace / 就好了。但是如果想把发送信息的服务器作为第三方应用给不同客户端使用,就需要为每一个客户端定义一个namespace.
跨域问题的解决方案:
采用 flask_cors 进行解决跨域问题
socketio = SocketIO(app, cors_allowed_origins='*')
- 1
版本不兼容问题:
pip list 查看 服务器端 Flask-SocketIO 版本
查看 vue 客户端的 socketIO 版本
Vue 使用 socket.io
通过最近一系列的踩坑之后,总结出了这篇相对较为完善的关于vue-socket.io的使用文章,包含大家困扰的(socket连接、断开重连,以及当连接的地址是动态的情况下,我们应该注意的事项。
如何安装 socket.io
npm install vue-socket.io --save
- 1
第一种:引用 socket.io,在组件中直接引入
import io from 'socket.io-client'
- 1
第二种:引用 socket.io,在 main.js 中直接这样写
import VueSocketIO from 'vue-socket.io'Vue.use(new VueSocketIO({ debug: true, connection: 'ws://localhost:5000/echo', //}))
- 1
- 2
- 3
- 4
- 5
- 6
这里要提示的一点就是,当连接的地址是动态的,代码的执行顺序就很重要了,即 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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
在 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) })}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
网页客户端实现
首先看下四个方法
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;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
建立 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 + ")");};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
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>
- 1
- 2
- 3
常见问题报错解决方案
ImportError 无法导入 ContextVar 包
ImportError: cannot import name ‘ContextVar’
pip uninstall flask # 卸载 你也可以省略这步,直接pip install flask==1.0pip install flask==1.0 # 我这里改成1.0就行了,改成其他低版本应该也没问题,有兴趣自己试下
- 1
- 2
- 3
- 4
关于 Werkzeug 缺失,或者版本过低问题
werkzeug.routing.WebsocketMismatch: 400 Bad Request: The browser (or proxy) sent a request that this server could not understand.
- 1
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/
- 1
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)