轮询、长轮询和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 ...
随机推荐
- 学习前端笔记1(HTML)
(注:此文是在看过许多学习资料和视频之后,加上自身理解拼凑而成,仅作学习之用.若有版权问题,麻烦及时联系) 标准页面结构: HTML发展历史: 注:每一种HTML需要有对应的doctype声明. H ...
- 用webpack打包加密静态配置文件
webpack处理静态文件,如json.xml等配置文件,可以采用 copy-webpack-plugin 插件直接复制到打包后的文件夹下,但如果想采用一些手段隐藏一下这些配置文件的内容怎么办呢? 虽 ...
- 从零学习Flutter(一):初识Dart
Fluter是Google推出的跨平台开发App的一套框架,很多人都说,Google出品比属于金品,故,我也来凑凑热闹,Fluter是用Dart写的,所以在用Fluter之前,我们还是有必要简单了解一 ...
- Windows WMIC命令使用详解(附实例)
第一次执行WMIC命令时,Windows首先要安装WMIC,然后显示出WMIC的命令行提示符.在WMIC命令行提示符上,命令以交互的方式执行 执行“wmic”命令启动WMIC命令行环境.这个命令可以在 ...
- 从0开始的Python学习010return语句&DocStrings
return语句 return语句用来从一个函数中 返回 即跳出函数.当然也可以从函数中返回一个值. #return 语句从一个函数返回 即跳出函数.我们也可选从函数返回一个值 def maximum ...
- python正则表达式相关记录
1 python中字符串前加‘r’,即可阻止‘\’导致的字符转义.但是在re.sub()中参数中加'r'不会起作用.
- JDBC获得连接时报connection refused
1,检查数据库服务器的IP是否正确. 2,检查用户名密码是否正确. 3,检查SID,获selecte instance_name from v$instance;
- linux ssh免密登陆远程服务器
10.170.1.18服务器免密登录到10.170.1.16服务器 首先登入一台linux服务器(10.170.1.18),此台做为母机(即登入其他linux系统用这台做为入口):执行一行命令生成ke ...
- python 词云学习
词云入门 三步曲 数据获取:使用爬虫在相关网站上获取文本内容 数据清洗:按一定格式对文本数据进行清洗和提取(文本分类,贴标签) 数据呈现:多维度呈现和解读数据(计算,做表,画图) 一 模块的安装 pi ...
- 【字】biang
biang biang面的名字由来:biangbiang面是陕西关中地区的一中地区美食,因为在做这种面时会发出biang biang的声音,biang biang面因此得名.biang字简体共有42笔 ...