【计算机网络基础——系列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通信
之前使用flask与前端进行通信一直是使用的
http1.0
,通过Ajax
轮询的方式实现通信,通信的进行是不断的通过客户端发送请求-服务端响应
来进行的,这样客户端需要不断的去进行请求,浪费资源和性能。
以前做项目时也因为使用AJAX
轮询,而数据请求过于频繁出现过相关问题,所以这次的项目我打算使用websocket
进行
HTML5开始提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道。他在HTTP1.1实现长连接的基础上还能够保有超时重连和心跳机制。
相比较传统的http与websocket可用一幅图表示:
从图中可以知道,websocket只需要发送一次请求就可以建立起长期有效的连接。
可以看出,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==
HTTP/1.1 101 Switching Protocols
Connection:Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PTU=
Sec-WebSocket-Protocol:chat
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 ... |
+---------------------------------------------------------------+
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);
},
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() | 使用连接发送数据 |
其原理是在发送某一个数据以后就开启一个计时器,在一定时间内如果没有得到发送的数据报的确认报文,那么就重新发送数据,直到发送成功为止。这里是以
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);
},
所谓心跳机制是指定时发送一个自定义的结构体 (心跳包,在本文中
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一次
},
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);
}
<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>
本来是想用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通信网站
这是链接指向的网页不存在,即原始网页的URL失效,无法在所请求的端口上访问Web站点,通过修改服务端的启动部分解决:
if __name__ == "__main__":
server = pywsgi.WSGIServer(('127.0.0.1', 5000), app, handler_class=WebSocketHandler)
print('server start')
server.serve_forever()
ModuleNotFoundError: No module named 'flask_sockets'
我已经使用了pip install flask_socket
,但是启动后仍然报错,于是我查到可能因为flask的版本问题,于是我就重新pip install flask
,然后再下载flask_socket,于是成功
pip install flask
pip install flask_socket
这个错误因为强制转换了类型导致失败 如下:
const redata = JSON.parse(e.data);
console.log('接收',redata);
// this.aa = [...this.aa, redata.type];
this.reset();
于是我把这个隐去了this.aa = [...this.aa, redata.type];
就成功了。
sockjs-node是一个低延时全双工的API,我在这里没有用到,于是找到它,隐去它,成功!
在node_moudules中找到它:
try {
// self.xhr.send(payload); 把这里注掉
} catch (e) {
self.emit('finish', 0, '');
self._cleanup(false);
}
在这个方法中,设置地址要用ws代替http;wss代替https。
const wsuri = "ws://127.0.0.1:5000/test";
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
这个方法没有额外引入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';
码字不易~, 各位看官要是看爽了,可不可以三连走一波,点赞皆有好运!,不点赞也有哈哈哈~~~