轮询、长轮询、websocket
轮询、长轮询、websocket
一、轮询
在一些需要进行实时查询的场景下应用
比如投票系统:
大家一起在一个页面上投票
在不刷新页面的情况下,实时查看投票结果
1、后端代码
from flask import Flask, render_template, request, jsonify
app = Flask(__name__)
USERS = {
1: {'name': '小米', 'count': 300},
2: {'name': '小康', 'count': 200},
3: {'name': '小明', '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>最帅男人投票</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': '小明', '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 = ''
ret = q.get()
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>最晒男人投票</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出的新协议 浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输
解决轮询问题
特点:
1. 握手 基于HTTP进行握手(因此websocket与Http有一定的交集,但不是同一个东西)
2. 发送数据加密
3. 保持连接不断开
2、在Flask中使用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
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': '小明', '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()
<!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>最晒男人投票</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>