【计算机网络基础——系列12】flask作为服务器与vue实现websocket通信
系列文章目录提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加例如:第一章 Python 机器学习入门之pandas的使用提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录系列文章目录一、为什么选择websocket1.1 websocket是什么?1.2 websocket的优点1.3 websocket报文1.3.1 客户端请求报文:1.3.2 服务端响应报
系列文章目录
【计算机网络基础——系列1】-matlab与python使用socket udp进行进程间通信
【计算机网络基础——系列2】-matlab与python使用socket tcp进行进程间通信
【计算机网络基础——系列3】输入url后页面会遇到的问题
【计算机网络基础——系列4】关于HTTP请求的相关内容
【计算机网络基础——系列5】前端遇到的三种网络攻击
【计算机网络基础——系列6】浏览器缓存之cookie、session、localstorage
【计算机网络基础——系列7】浏览器缓存之—http缓存
【计算机网络基础——系列8】前端优化总结
【计算机网络基础——系列9】restful规范;dns劫持
【计算机网络基础——系列10】osi网络结构;tcp协议保持传输的可靠性;SSL
【计算机网络基础——系列11】实现python作为服务端与qt进行udp通信
【计算机网络基础——系列12】flask作为服务器与vue实现websocket通信
文章目录
一、为什么选择websocket
之前使用flask与前端进行通信一直是使用的
http1.0
,通过Ajax
轮询的方式实现通信,通信的进行是不断的通过客户端发送请求-服务端响应
来进行的,这样客户端需要不断的去进行请求,浪费资源和性能。
以前做项目时也因为使用AJAX
轮询,而数据请求过于频繁出现过相关问题,所以这次的项目我打算使用websocket
进行
1.1 websocket是什么?
HTML5开始提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道。他在HTTP1.1实现长连接的基础上还能够保有超时重连和心跳机制。
1.2 websocket的优点
- 支持双向通信,实时性更强,有超时重连和心跳机制,数据传输更加稳定可靠。
- 更好的二进制支持。
- 较少的控制开销。连接创建后,ws客户端、服务端进行数据交换时,协议控制的数据包头部较小。在不包含头部的情况下,服务端到客户端的包头只有2~10字节(取决于数据包长度),客户端到服务端的的话,需要加上额外的4字节的掩码。而HTTP协议每次通信都需要携带完整的头部。
- 支持扩展。ws协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议。(比如支持自定义压缩算法等)
相比较传统的http与websocket可用一幅图表示:
从图中可以知道,websocket只需要发送一次请求就可以建立起长期有效的连接。
1.3 websocket报文
1.3.1 客户端请求报文:
可以看出,websocket还是基于http1.1的使用get请求的连接
GET / HTTP/1.1
Host: localhost:8080
Origin: http://127.0.0.1:3000
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw==
- Connection: Upgrade:表示要升级协议
- Upgrade: websocket:表示要升级到websocket协议。
- Sec-WebSocket-Version:13:表示websocket的版本。如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。(这个地方在前两天的尝试中就吃过亏,客户端和服务端的版本号不一致导致连接失败——后来全部升级到了最新版本)
- Sec-WebSocket-Key:与后面服务端响应首部的Sec-WebSocket-Accept是配套的,提供基本的防护,比如恶意的连接,或者无意的连接。
1.3.2 服务端响应报文:
HTTP/1.1 101 Switching Protocols
Connection:Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PTU=
Sec-WebSocket-Protocol:chat
- 101 状态码表示服务器已经理解了客户端的请求,并将通过 Upgrade 消息头通知客户端采用不同的协议来完成这个请求;
- Sec-WebSocket-Accept 这个则是经过服务器确认,并且加密过后的 Sec-WebSocket-Key,主要作用在于提供基础的防护,减少恶意连接、意外连接。
- Sec-WebSocket-Protocol 则是表示最终使用的协议,将 Sec-WebSocket-Key 跟258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接,通过 SHA1 计算出摘要,并转成 base64字符串。
1.3.3 数据帧预览格式:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
二、客户端(vue)使用websocket
1.数据声明
data() {
return {
websock: null, //建立的连接
lockReconnect: false, //是否真正建立连接
timeout: 28 * 1000, //30秒一次心跳
timeoutObj: null, //外层心跳倒计时
serverTimeoutObj: null, //内层心跳检测
timeoutnum: null //断开 重连倒计时
};
},
2.生命周期启动关闭连接
这里删除的定时器是后文进行心跳机制使用到的。
mounted:function(){
this.initWebSocket();
},
beforeDestroy: function () {
// 页面离开时断开连接,清除定时器
this.disconnect();
clearInterval(this.timer);
},
3.methods中进行websocket各个函数设置
3.1 初始化websocket
initWebSocket() {
//初始化weosocket
const wsuri = "ws://127.0.0.1:5000/test";
this.websock = new WebSocket(wsuri);
this.websock.onopen = this.websocketonopen;
this.websock.onmessage = this.websocketonmessage;
this.websock.onerror = this.websocketonerror;
this.websock.onclose = this.websocketclose;
this.websock.onsend=this.websocketsend;
},
事件 | 事件处理程序 | 描述 |
---|---|---|
open | Socket.onopen | 连接建立时触发 |
message | Socket.onmessage | 客户端接收服务端数据时触发 |
error | Socket.onerror | 通信发生错误时触发 |
close | Socket.onclose | 连接关闭时触发 |
send | Socket.send() | 使用连接发送数据 |
3.2 websocket的超时重连机制
其原理是在发送某一个数据以后就开启一个计时器,在一定时间内如果没有得到发送的数据报的确认报文,那么就重新发送数据,直到发送成功为止。这里是以
that.lockReconnect
为判断依据
reconnect() {
//重新连接
var that = this;
if (that.lockReconnect) {
// 是否真正建立连接
return;
}
that.lockReconnect = true;
//没连接上会一直重连,设置延迟避免请求过多
that.timeoutnum && clearTimeout(that.timeoutnum);
// 如果到了这里断开重连的倒计时还有值的话就清除掉
that.timeoutnum = setTimeout(function() {
//然后新连接
that.initWebSocket();
that.lockReconnect = false;
}, 5000);
},
3.3 websocket的心跳机制
所谓心跳机制是指定时发送一个自定义的结构体 (心跳包,在本文中
self.websock.send("heartCheck")
),让对方知道自己还活着,以确保连接的有效性的机制。判断依据是self.websock.readyState
。
websock.readyState值 | 代表状态 |
---|---|
0 | 正在链接中 |
1 | 已经链接并且可以通讯 |
2 | 连接正在关闭 |
3 | 连接已关闭或者没有链接成功 |
reset() {
//重置心跳
var that = this;
//清除时间(清除内外两个心跳计时)
clearTimeout(that.timeoutObj);
clearTimeout(that.serverTimeoutObj);
//重启心跳
that.start();
},
start() {
//开启心跳
var self = this;
self.timeoutObj && clearTimeout(self.timeoutObj);
// 如果外层心跳倒计时存在的话,清除掉
self.serverTimeoutObj && clearTimeout(self.serverTimeoutObj);
// 如果内层心跳检测倒计时存在的话,清除掉
self.timeoutObj = setTimeout(function() {
// 重新赋值重新发送 进行心跳检测
//这里发送一个心跳,后端收到后,返回一个心跳消息,
if (self.websock.readyState === 1) {
//如果连接正常
self.websock.send("heartCheck");
}
else {
//否则重连
self.reconnect();
}
self.serverTimeoutObj = setTimeout(function() {
// 在三秒一次的心跳检测中如果某个值3秒没响应就关掉这次连接
//超时关闭
self.websock.close();
}, self.timeout);
}, self.timeout);
// 3s一次
},
3.4 执行函数
websocketonopen(e) {
//连接建立之后执行send方法发送数据
console.log("成功",e);
let actions = 123;
this.websocketsend(JSON.stringify(actions));
},
websocketonerror() {
//连接建立失败重连
console.log("失败");
this.initWebSocket();
},
websocketonmessage(e) {
//数据接收
const redata = JSON.parse(e.data);
console.log('接收',redata);
// this.aa = [...this.aa, redata.type];
this.reset();
},
websocketsend(Data) {
//数据发送
console.log('发送',Data);
this.websock.send(Data);
},
websocketclose(e) {
//关闭
console.log("断开连接", e);
}
3.5 源码
<template>
<div>
</div>
</template>
<script>
export default {
data() {
return {
websock: null, //建立的连接
lockReconnect: false, //是否真正建立连接
timeout: 28 * 1000, //30秒一次心跳
timeoutObj: null, //外层心跳倒计时
serverTimeoutObj: null, //内层心跳检测
timeoutnum: null //断开 重连倒计时
};
},
mounted:function(){
this.initWebSocket();
},
beforeDestroy: function () {
// 页面离开时断开连接,清除定时器
this.disconnect();
clearInterval(this.timer);
},
methods: {
initWebSocket() {
//初始化weosocket
const wsuri = "ws://127.0.0.1:5000/test";
this.websock = new WebSocket(wsuri);
this.websock.onopen = this.websocketonopen;
this.websock.onmessage = this.websocketonmessage;
this.websock.onerror = this.websocketonerror;
this.websock.onclose = this.websocketclose;
this.websock.onsend=this.websocketsend;
},
reconnect() {
//重新连接
var that = this;
if (that.lockReconnect) {
// 是否真正建立连接
return;
}
that.lockReconnect = true;
//没连接上会一直重连,设置延迟避免请求过多
that.timeoutnum && clearTimeout(that.timeoutnum);
// 如果到了这里断开重连的倒计时还有值的话就清除掉
that.timeoutnum = setTimeout(function() {
//然后新连接
that.initWebSocket();
that.lockReconnect = false;
}, 5000);
},
reset() {
//重置心跳
var that = this;
//清除时间(清除内外两个心跳计时)
clearTimeout(that.timeoutObj);
clearTimeout(that.serverTimeoutObj);
//重启心跳
that.start();
},
start() {
//开启心跳
var self = this;
self.timeoutObj && clearTimeout(self.timeoutObj);
// 如果外层心跳倒计时存在的话,清除掉
self.serverTimeoutObj && clearTimeout(self.serverTimeoutObj);
// 如果内层心跳检测倒计时存在的话,清除掉
self.timeoutObj = setTimeout(function() {
// 重新赋值重新发送 进行心跳检测
//这里发送一个心跳,后端收到后,返回一个心跳消息,
if (self.websock.readyState === 1) {
//如果连接正常
self.websock.send("heartCheck");
}
else {
//否则重连
self.reconnect();
}
self.serverTimeoutObj = setTimeout(function() {
// 在三秒一次的心跳检测中如果某个值3秒没响应就关掉这次连接
//超时关闭
self.websock.close();
}, self.timeout);
}, self.timeout);
// 3s一次
},
websocketonopen(e) {
//连接建立之后执行send方法发送数据
console.log("成功",e);
let actions = 123;
this.websocketsend(JSON.stringify(actions));
},
websocketonerror() {
//连接建立失败重连
console.log("失败");
this.initWebSocket();
},
websocketonmessage(e) {
//数据接收
const redata = JSON.parse(e.data);
console.log('接收',redata);
// this.aa = [...this.aa, redata.type];
this.reset();
},
websocketsend(Data) {
//数据发送
console.log('发送',Data);
this.websock.send(Data);
},
websocketclose(e) {
//关闭
console.log("断开连接", e);
}
}
};
</script>
三、服务端(python flask)使用websocket
本来是想用nodejs作为服务端的,但是算法部分使用python做的,如果用nodejs做又需要进程通信使用到socket udp,就还是使用flask了。
-
端口设置了5000,ip地址就是127.0.0.1,确保心跳机制的执行,服务端必须接收到客户端发的消息才能保持该服务运行,如果ws.receive()没有接收到客户端发送的消息,那么它会关闭与客户端建立的链接。
-
底层解释:Read and return a message from the stream. If
None
is
returned, then
the socket is considered closed/errored. -
所以客户端只建立连接,不与服务端交互通信,则无法实现自由通信状态,之后在客户端代码处会有详细内容。
from flask import Flask, jsonify, request # 引入核心处理模块
import json
from flask_sockets import Sockets
import time
import sys
import os
from gevent import monkey
from gevent import pywsgi
from geventwebsocket.handler import WebSocketHandler
print('已进入')
sys.path.append(os.path.abspath(os.path.dirname(__file__) + '/' + '..'))
sys.path.append("..")
monkey.patch_all()
app = Flask(__name__)
sockets = Sockets(app)
now = time.strftime('%Y-%m-%d-%H-%M-%S', time.localtime(time.time()))
@sockets.route('/test') # 指定路由
def echo_socket(ws):
while not ws.closed:
ws.send(str(111111111)) # 回传给clicent
""" 服务端必须接收到客户端发的消息才能保持该服务运行,如果ws.receive()没有接收到客户端发送的
消息,那么它会关闭与客户端建立的链接
底层解释:Read and return a message from the stream. If `None` is returned, then
the socket is considered closed/errored.
所以客户端只建立连接,不与服务端交互通信,则无法实现自由通信状态,之后在客户端代码处会有详细内容。
"""
message = ws.receive() # 接收到消息
if message is not None:
print("%s receive msg==> " % now, str(json.dumps(message)))
""" 如果客户端未发送消息给服务端,就调用接收消息方法,则会导致receive()接收消息为空,关闭此次连接 """
ws.send(str(json.dumps(message))) # 回传给clicent
else:
print(now, "no receive")
@app.route('/')
def hello():
return 'Hello World! server start!'
if __name__ == "__main__":
server = pywsgi.WSGIServer(('127.0.0.1', 5000), app, handler_class=WebSocketHandler)
print('server start')
server.serve_forever()
四、通信测试
这里推荐一个好网站,既可以测试服务端又可以测试客户端的网站(工具)
- 这个网站自己内置了一个websocket客户端的js,在输入行内输入服务端的入口地址即可以连接上客户端,然后实现数据通信。也可以知晓服务端的问题所在
- 同时,这个网站还提供了一个示例服务器,可以返回客户端发送的数据,这样就可以测试客户端的问题所在,前两天就是通过这个网站找到的服务端的问题的。
这是网站地址:测试websocket通信网站
五、报错集锦
5.1 404出错
这是链接指向的网页不存在,即原始网页的URL失效,无法在所请求的端口上访问Web站点,通过修改服务端的启动部分解决:
if __name__ == "__main__":
server = pywsgi.WSGIServer(('127.0.0.1', 5000), app, handler_class=WebSocketHandler)
print('server start')
server.serve_forever()
5.2 python的flask_socket模块安装失败
ModuleNotFoundError: No module named 'flask_sockets'
我已经使用了pip install flask_socket
,但是启动后仍然报错,于是我查到可能因为flask的版本问题,于是我就重新pip install flask
,然后再下载flask_socket,于是成功
pip install flask
pip install flask_socket
5.3 object null is not iterable (cannot read property Symbol(Symbol.iterator))
这个错误因为强制转换了类型导致失败 如下:
const redata = JSON.parse(e.data);
console.log('接收',redata);
// this.aa = [...this.aa, redata.type];
this.reset();
于是我把这个隐去了this.aa = [...this.aa, redata.type];
就成功了。
5.4 sockjs-node/info?t=报错
sockjs-node是一个低延时全双工的API,我在这里没有用到,于是找到它,隐去它,成功!
在node_moudules中找到它:
try {
// self.xhr.send(payload); 把这里注掉
} catch (e) {
self.emit('finish', 0, '');
self._cleanup(false);
}
5.5 ws与http的报错
在这个方法中,设置地址要用ws代替http;wss代替https。
const wsuri = "ws://127.0.0.1:5000/test";
六、相关环境配置
6.1 python flask服务端
pip install flask
pip install json
pip install flask_sockets
pip install time
pip install sys
pip install os
pip install gevent
pip install geventwebsocket.handler
6.2 vue客户端
这个方法没有额外引入js文件,但如果使用socketio方法的话,需要在main.js中加入:
import VueSocketIOExt from 'vue-socket.io-extended';
import socketio from 'socket.io-client';
Vue.use(VueSocketIOExt, socketio('http:127.0.0.1:8888'));
Vue.prototype.$socketio = socketio;
或者是在当前vue文件中引入:
import SockJS from 'sockjs-client';
import Stomp from 'stompjs';
码字不易~, 各位看官要是看爽了,可不可以三连走一波,点赞皆有好运!,不点赞也有哈哈哈~~~
更多推荐
所有评论(0)