轮询、长轮询和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
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': '小明', '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>最晒男人投票</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>