轮询、长轮询和websocket
一、轮询
在一些需要进行实时查询的场景下应用
比如投票系统:
大家一起在一个页面上投票
在不刷新页面的情况下,实时查看投票结果
1、后端代码
from flask import Flask, render_template, request, jsonify app = Flask(__name__) USERS = {
1: {'name': '明凯', 'count': 300},
2: {'name': '厂长', 'count': 200},
3: {'name': '7酱', 'count': 600},
} @app.route('/')
def index():
return render_template('Poll.html', users=USERS) @app.route('/vote', methods=['POST'])
def vote():
# 接收uid,通过uid给打野票数 +1
# 用户提交Json数据过来,用request.json获取
uid = request.json.get('uid')
USERS[uid]['count'] += 1
return "投票成功" @app.route('/get_vote')
def get_vote():
# 返回users数据
# jsonify 是flask自带的序列化器
return jsonify(USERS) if __name__ == '__main__':
app.run()
2、前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>投票系统</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css">
<script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.js"></script>
</head>
<style>
.my-li {
list-style: none;
margin-bottom: 20px;
font-size: 18px;
}
</style>
<body>
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<h1>LPL第一打野投票</h1>
{% for (uid, user) in users.items() %}
<button class="btn btn-success" onclick="vote({{ uid }})">投票</button>
<li class="list-group-item-info my-li" id="{{ uid }}">{{ user.name }}目前的票数是: {{ user.count }}</li>
{% endfor %}
</div>
</div>
</div> <script>
// 投票
function vote(uid) {
// 向后端发送投票请求
axios.request({
url: '/vote',
method: 'post',
data: {
uid: uid
}
}).then(function (response) {
console.log(response.data);
})
} // 获取最新的投票结果
function get_vote() {
axios.request({
url: '/get_vote',
method: 'get'
}).then(function (response) {
// 获取后端传过来的新数据
// 重新渲染页面
let users = response.data;
for (let uid in users) {
//根据uid获取li标签 改变innerText
let liEle = document.getElementById(uid);
liEle.innerText = `${users[uid]['name']}目前的票数是: ${users[uid]['count']}`
}
});
} // 页面加载完后,立刻获取数据
window.onload = function () {
setInterval(get_vote, 2000)
} </script> </body>
</html>
3、轮询
特点:每隔一段时间不断向后端发送请求
缺点:消耗大 有延迟
二、长轮询
由于上面的轮询是不能实时查看到投票情况的,存在一定的延迟性
长轮询可以实现实时查看投票情况
1、后端代码
from flask import Flask, render_template, request, jsonify, session
import uuid
import queue app = Flask(__name__)
app.secret_key = '切克闹' USERS = {
1: {'name': '明凯', 'count': 300},
2: {'name': '厂长', 'count': 200},
3: {'name': '7酱', 'count': 600},
} Q_DICT = {
# uid: q对象
} @app.route('/')
def index():
# 模拟用户登录
# 模拟用户登录后的唯一id
user_id = str(uuid.uuid4())
# 每个用户都有自己的Q对象
Q_DICT[user_id] = queue.Queue()
# 把用户的id存到session
session['user_id'] = user_id
# 页面展示投票的人的信息
return render_template('longPoll.html', users=USERS) @app.route('/vote', methods=['POST'])
def vote():
# 处理投票,给打野的票数 +1
# 用户提交Json数据过来,用request.json获取
uid = request.json.get('uid')
USERS[uid]['count'] += 1
# 投票成功后,给每个用户的Q对象put最新的值进去
for q in Q_DICT.values():
q.put(USERS)
return "投票成功" @app.route('/get_vote')
def get_vote():
# 请求进来,从session获取用户的id
user_id = session.get('user_id')
# 根据用户的id 获取用户的Q对象
q = Q_DICT.get(user_id)
try:
ret = q.get(timeout=30)
except queue.Empty:
ret = ''
except Exception:
ret = ''
return jsonify(ret) if __name__ == '__main__':
app.run()
2、前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>投票系统</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css">
<script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.js"></script>
</head>
<style>
.my-li {
list-style: none;
margin-bottom: 20px;
font-size: 18px;
}
</style>
<body>
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<h1>LPL第一打野投票</h1>
{% for (uid, user) in users.items() %}
<button class="btn btn-success" onclick="vote({{ uid }})">投票</button>
<li class="list-group-item-info my-li" id="{{ uid }}">{{ user.name }}目前的票数是: {{ user.count }}</li>
{% endfor %}
</div>
</div>
</div> <script>
// 投票
function vote(uid) {
// 向后端发送投票请求
axios.request({
url: '/vote',
method: 'post',
data: {
uid: uid
}
}).then(function (response) {
console.log(response.data);
})
} // 获取最新的投票结果
function get_vote() {
axios.request({
url: '/get_vote',
method: 'get'
}).then(function (response) {
// 判断后端的数据是否为空
if (response.data != '') {
// 获取到最新的数据
let users = response.data;
for (uid in users) {
// 根据uid找到每个li标签
let liEle = document.getElementById(uid);
// 给每个li标签设置最新的数据
liEle.innerText = `${users[uid]['name']}目前的票数是: ${users[uid]['count']}`
}
}
// 获取完数据后,再发送请求,看还有没有人投票,有的话再去获取最新的数据
get_vote()
});
} // 页面加载完后,立刻获取数据
window.onload = function () {
get_vote()
} </script> </body>
</html>
3、长轮询
特点:满足实时更新
缺点:消耗大
实现:
利用queue对象实现请求夯住
每个请求进来都要生成一个q对象
如果有人投票 给所有的q对象put数据
拿数据请求从自己的q对象get数据
三、websocket介绍
1.对比
http协议
短连接 无状态 基于TCP/UDP协议进行传输数据(TCP/UDP: 传输协议)
socket
socket不是传输协议 跟websocket是两个完全不一样的东西 socket是套接字 API接口
websocket
H5出的新协议 浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输
解决轮询问题
特点:
0. 本质上是基于TCP的协议
1. 但在握手阶段是基于HTTP进行握手(因此websocket与Http有一定的交集,但不是同一个东西)
2. 发送数据加密
3. 保持连接不断开
2.不同
以前客户端想知道服务端的处理进度,要不停地使用 Ajax 进行轮询,让浏览器隔个几秒就向服务器发一次请求,这对服务器压力较大。另外一种轮询就是采用 long poll 的方式,这就跟打电话差不多,没收到消息就一直不挂电话,也就是说,客户端发起连接后,如果没消息,服务端就一直不返回 Response 给客户端,连接阶段一直是阻塞的。
websocket 是服务器推送技术的一种,最大的特点是服务器可以主动向客户端推送消息,客户端也可以主动向服务器发送消息。解决了轮询造成的同步延迟问题。由于 WebSocket 只需要一次 HTTP 握手,服务端就能一直与客户端保持通信,直到关闭连接,这样就解决了服务器需要反复解析 HTTP 协议,减少了资源的开销。
只要不是太老的浏览器,一般都支持websocket。
3.特点
- 在单个 TCP 连接上进行全双工通讯的协议。
- 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
- 数据格式比较轻量,性能开销小,通信高效。
- 可以发送文本,也可以发送二进制数据。
- 没有同源限制,客户端可以与任意服务器通信。
- 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
四、Web Socket客户端的实现
1、WebSocket 构造函数
WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例
var ws = new WebSocket('ws://localhost:8000'); // 建立连接发送的是GET请求,如果是使用Flask的CBV编程的时候注意咯,要定义get方法
2、WebSocket.readyState 属性
属性 | 描述 |
---|---|
WebSocket.readyState |
只读属性 readyState 表示这个连接的状态,可以是以下值:
|
WebSocket.bufferedAmount |
只读属性 bufferedAmount 表示还没有发送出去的 UTF-8 文本字节数。 |
示例1:
switch (ws.readyState) {
case ws.CONNECTING:
// do something
break;
case ws.OPEN:
// do something
break;
case ws.CLOSING:
// do something
break;
case ws.CLOSED:
// do something
break;
default:
// this never happens
break;
}
示例2:
// webSocket.bufferedAmount
实例对象的bufferedAmount属性,表示还有多少字节的二进制数据没有发送出去。它可以用来判断发送是否结束。
如果为 0 代表全部发送完毕,如果不为 0 代表还有多少字节没有发送出去。 var data = new ArrayBuffer(10000000);
ws.send(data); if (ws.bufferedAmount === 0) {
// 发送完毕
} else {
// 发送还没结束
}
3、WebSocket事件
事件 | 事件处理程序 | 描述 |
---|---|---|
open | WebSocket.onopen | 连接建立时触发 |
message | WebSocket.onmessage | 客户端接收服务端数据时触发 |
error | WebSocket.onerror | 通信发生错误时触发 |
close | WebSocket.onclose | 连接关闭时触发 |
3.1 webSocket.onopen
实例对象的onopen
属性,用于指定连接成功后的回调函数。
ws.onopen = function () {
ws.send('Hello Server!');
}
如果要指定多个回调函数,可以使用addEventListener
方法
ws.addEventListener('open', function (event) {
ws.send('Hello Server!');
});
3.2 webSocket.onclose
实例对象的onclose
属性,用于指定连接关闭后的回调函数。
ws.onclose = function(event) {
var code = event.code;
var reason = event.reason;
var wasClean = event.wasClean;
// handle close event
}; ws.addEventListener("close", function(event) {
var code = event.code;
var reason = event.reason;
var wasClean = event.wasClean;
// handle close event
});
3.3 webSocket.onmessage
实例对象的onmessage
属性,用于指定收到服务器数据后的回调函数。
ws.onmessage = function(event) {
var data = event.data;
// 处理数据
}; ws.addEventListener("message", function(event) {
var data = event.data;
// 处理数据
});
注意,服务器数据可能是文本,也可能是二进制数据(blob
对象或Arraybuffer
对象)。
ws.onmessage = function(event){
if(typeof event.data === String) {
console.log("Received data string");
} if(event.data instanceof ArrayBuffer){
var buffer = event.data;
console.log("Received arraybuffer");
}
}
除了动态判断收到的数据类型,也可以使用binaryType
属性,显式指定收到的二进制数据类型。
// 收到的是 blob 数据
ws.binaryType = "blob";
ws.onmessage = function(e) {
console.log(e.data.size);
}; // 收到的是 ArrayBuffer 数据
ws.binaryType = "arraybuffer";
ws.onmessage = function(e) {
console.log(e.data.byteLength);
};
3.4 webSocket.onerror
实例对象的onerror
属性,用于指定报错时的回调函数。
socket.onerror = function(event) {
// handle error event
}; socket.addEventListener("error", function(event) {
// handle error event
});
4、WebSocket 方法
方法 | 描述 |
---|---|
WebSocket.send() |
使用连接发送数据 |
WebSocket.close() |
关闭连接 |
4.1 webSocket.send()
实例对象的send()
方法用于向服务器发送数据。
发送文本的例子
ws.send('your message');
发送 Blob 对象的例子。
var file = document
.querySelector('input[type="file"]')
.files[0];
ws.send(file);
发送 ArrayBuffer 对象的例子。
// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
binary[i] = img.data[i];
}
ws.send(binary.buffer);
4.2 webSocket.close()
断开连接
ws.close();
五、WebSocket服务端的实现
1、flask服务端实现的示例
from flask import Flask, request, render_template, abort
from geventwebsocket.handler import WebSocketHandler
from gevent.pywsgi import WSGIServer app = Flask(__name__) @app.route('/')
def index():
return render_template('index.html') @app.route('/foobar')
def echo():
if request.environ.get('wsgi.websocket'):
ws = request.environ['wsgi.websocket']
if ws is None:
abort(404)
else:
while True:
if not ws.closed:
message = ws.receive()
ws.send(message)
if __name__ == '__main__':
http_server = WSGIServer(('127.0.0.1',5000), app, handler_class=WebSocketHandler)
http_server.serve_forever()
2、前后端 websocket 实现的对比
1. Flask没有websocket,需要安装包 pip install gevent-websocket 2. 后端怎样建立一个支持websocket协议连接
from geventwebsocket.handler import WebSocketHandler
from gevent.pywsgi import WSGIServer # 拿到websocket对象
ws = request.environ.get("wsgi.websocket")
# 后端发送数据
ws.send(xxx)
# 后端接收数据
ws.receive() if __name__ == '__main__':
# app.run()
# 即支持HTTP 也支持websocket
http_server = WSGIServer(('0.0.0.0', 5000), app, handler_class=WebSocketHandler)
http_server.serve_forever() 3. 前端怎么发起websocket连接
let ws = new WebSocket("ws://127.0.0.1:5000")
# 前端发送数据
ws.send("xxx")
# 前端接收数据
ws.onmessage = function(event){
# 注意数据数据类型的转换
let data = event.data
} 4. 收发消息
1, 前端发送数据给后端
前端发送:ws.send('数据')
后端接收:ws.receive() 2, 后端发送数据给前端
后端发送:ws.send('数据')
前端接收:ws.onmessage = function(event){
let data = event.data
}
3、Demo
1.后端代码
from flask import Flask, render_template, request
from geventwebsocket.handler import WebSocketHandler
from gevent.pywsgi import WSGIServer
import json app = Flask(__name__) USERS = {
1: {'name': '明凯', 'count': 300},
2: {'name': '厂长', 'count': 200},
3: {'name': '7酱', 'count': 600},
} WEBSOCKET_LIST = [] @app.route('/')
def index():
return render_template('websocket.html', users=USERS) @app.route('/vote')
def vote():
# 处理websocket
# 判断是什么类型的请求,HTTP还是websocket
# 看能否获取得到websocket的对象
ws = request.environ.get("wsgi.websocket")
if not ws:
return "这是HTTP协议的请求"
# 把所有用户的ws对象存到一个列表
WEBSOCKET_LIST.append(ws)
while True:
# 获取前端传过来的uid,给打野票数 +1
uid = ws.receive()
# 如果前端主动断开连接
# 那么后端也关闭与前端的连接
if not uid:
WEBSOCKET_LIST.remove(ws)
ws.close()
break
uid = int(uid)
USERS[uid]["count"] += 1
data = {
"uid": uid,
"name": USERS[uid]["name"],
"count": USERS[uid]["count"]
}
for ws in WEBSOCKET_LIST:
# 给前端发送新的数据
ws.send(json.dumps(data)) if __name__ == '__main__':
# app.run()
# 这样启服务的意思是:即支持HTTP协议,也支持websocket协议
http_server = WSGIServer(('127.0.0.1', 5000), app, handler_class=WebSocketHandler)
http_server.serve_forever()
2.前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>投票系统</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css">
<script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.js"></script>
</head>
<style>
.my-li {
list-style: none;
margin-bottom: 20px;
font-size: 18px;
}
</style>
<body>
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<h1>LPL第一打野投票</h1>
{% for (uid, user) in users.items() %}
<button class="btn btn-success" onclick="vote({{ uid }})">投票</button>
<li class="list-group-item-info my-li" id="{{ uid }}">{{ user.name }}目前的票数是: {{ user.count }}</li>
{% endfor %}
</div>
</div>
</div> <script>
// 向后端发送一个websocket连接请求
let ws = new WebSocket('ws://127.0.0.1:5000/vote');
function vote(uid) {
// 向后端发数据
ws.send(uid)
}
ws.onmessage = function (event) {
let data = JSON.parse(event.data);
let liEle = document.getElementById(data.uid);
liEle.innerText = `${data.name}目前的票数是: ${data.count}`
}
</script> </body>
</html>
轮询、长轮询和websocket的更多相关文章
- 使用轮询&长轮询实现网页聊天室
前言 如果有一个需求,让你构建一个网络的聊天室,你会怎么解决? 首先,对于HTTP请求来说,Server端总是处于被动的一方,即只能由Browser发送请求,Server才能够被动回应. 也就是说,如 ...
- 了解轮询、长轮询、长连接、websocket
业务开发中我们往往会有一些需要即时通信的场景,比如微信扫码登录.聊天功能. 下面这四种方式都可以实现即时通信. 轮询: 浏览器通过定时器每隔一段时间向服务器端发送请求,服务器端收到请求并响应请求.没有 ...
- Tornado长轮询和WebSocket
Http协议是一种请求响应式协议, 不允许服务端主动向客户端发送信息. 短轮询是一种简单的实现服务端推送消息的解决方案, 客户端以一定间隔自动向服务端发送刷新请求, 服务端返回要推送的消息作为响应. ...
- 轮询、长轮询、长连接、websocket
Web端即时通讯技术:即时通讯技术简单的说就是实现这样一种功能:服务器端可以即时地将数据的更新或变化反应到客户端,例如消息即时推送等功能都是通过这种技术实现的.但是在Web中,由于浏览器的限制,实现即 ...
- 长连接、短连接、长轮询和WebSocket
//转发,格式待整理 2017-08-0519784View0 对这四个概念不太清楚,今天专门搜索了解一下,总结一下: 长连接:在HTTP 1.1,客户端发出请求,服务端接收请求,双方建立连接,在服务 ...
- Apollo 3 定时/长轮询拉取配置的设计
前言 如上图所示,Apollo portal 更新配置后,进行轮询的客户端获取更新通知,然后再调用接口获取最新配置.不仅仅只有轮询,还有定时更新(默认 5 分钟一次).目的就是让客户端能够稳定的获取到 ...
- es6- Generator函数实现长轮询
1.Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同. 语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态.形式上,Gene ...
- http轮询,长轮询
轮询,长轮询 轮询 轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接. 优点:后端程序编写比较容易. 缺点:请求中有大半是无用,浪费带宽和服务器资源. 实例:适于小 ...
- python之轮询、长轮询、websocket
轮询 ajax轮询 ,ajax轮询 的原理非常简单,让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息. 1.后端代码 from flask import Flask,render_templat ...
随机推荐
- 网络最大流算法—EK算法
前言 EK算法是求网络最大流的最基础的算法,也是比较好理解的一种算法,利用它可以解决绝大多数最大流问题. 但是受到时间复杂度的限制,这种算法常常有TLE的风险 思想 还记得我们在介绍最大流的时候提到的 ...
- 使用addviewController()实现无业务逻辑跳转
需要实现WebMvcConfigurer类,重写addViewControllers方法. 添加@Configuration,等价于xml配置. package dbzx.config; import ...
- 使用 empApi 组件实现 Change Data Capture 功能
Change Data Capture 功能是从 Winter '19 版本开始正式启用的功能. 它是基于"发布-订阅"模式设计,可以将 Salesforce 中记录的改变自动推送 ...
- MongoDB基础学习
*:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...
- c/c++ 网络编程 UDP up/down 网卡
网络编程 UDP up/down 网卡 在程序里动态改变网卡的状态.注意:程序运行需要root权限. 程序运行的方法: sudo ./a.out 1,关闭网卡 #include <stdio.h ...
- Java中console类的简单用法
Java.io.Console 只能用在标准输入.输出流未被重定向的原始控制台中使用,在 Eclipse 或者其他 IDE 的控制台是用不了的. import java.io.Console; pub ...
- Django学习开发--笔记一(从零开始)
创建django项目注: 首先需在python中下载django 命令:pip install django1.任意文件中创建django项目 diango-admin startproject my ...
- (转载)Python之道1-环境搭建与pycharm的配置django安装及MySQL数据库配置
近期做那个python的开发,今天就来简单的写一下开发路线的安装及配置, 开发路线 Python3.6.1+Pycharm5.0.6+Django1.11+MySQL5.7.18 1-安装Python ...
- 解决IntelliJ IDEA 创建Maven项目速度慢问题
IntelliJ IDEA 创建maven项目速度很慢,甚至卡住不动了. 原因 IDEA根据maven archetype的本质,其实是执行mvn archetype:generate命令,该命令执行 ...
- docker面试整理
为什么要使用docker https://www.cnblogs.com/AshOfTime/p/10755479.html docker的使用场景 docker和虚拟机比较的优势 https: ...