轮询、SSE和webSocket
轮询(Polling)是一种
CPU
决策如何提供周边设备服务的方式,又称“程控输入输出”(Programmed I/O)。轮询法的概念是:由CPU定时发出询问,依序询问每一个周边设备
是否需要其服务,有即给予服务,服务结束后再问下一个周边,接着不断周而复始。
短轮询
短轮询的基本思路:
- 浏览器每隔一段时间向浏览器发送 http 请求,服务器端在收到请求后,不论是否有数据更新,都直接进行响应。
- 这种方式实现的即时通信,本质上还是浏览器发送请求,服务器接受请求的一个过程,通过让客户端不断的进行请求,使得客户端能够模拟实时地收到服务器端的数据的变化。
具体操作:可以通过定时器/延时器发起http请求
// 定时器
setInterval(function() {
axios.request({
url: '/get_vote',
method: 'get'
}).then(function (response) {
//do something
})
}, 10000);
优缺点👇
- 优点是比较简单,易于理解。
- 缺点是这种方式由于需要不断的建立 http 连接,严重浪费了服务器端和客户端的资源。当用户增加时,服务器端的压力就会变大,这是很不合理的。
长轮询
长轮询的基本思路:
- 首先由客户端向服务器发起请求,当服务器收到客户端发来的请求后,服务器端不会直接进行响应,而是先将这个请求挂起,然后判断服务器端数据是否有更新。
- 如果有更新,则进行响应,如果一直没有数据,则到达一定的时间限制才返回。客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。
具体实现: 后端写sleep(秒) 睡眠挂起请求,就是把前端的定时器移动到了后端, 后端while循环,不停的问数据库有没有结果。 没有进入定时睡眠,有则跳出循环处理逻辑。
// 获取最新的投票结果
function getData() {
axios.request({
method: "get",
url: '接口地址',
}).then(function (data) {
//do something
if (response.data != '') {
// 获取到最新的数据do somethings
}
// 获取完数据后,再发送请求,看还有没有新数据生成
getData()
})
}
优缺点👇
- 长轮询和短轮询比起来,它的优点是明显减少了很多不必要的 http 请求次数,相比之下节约了资源。
- 长轮询的缺点在于,连接挂起也会导致资源的浪费(服务器压力大,频繁操作询问数据库有没有新结果)
webSocket
由于 http
存在一个明显的弊端(消息只能有客户端推送到服务器端,而服务器端不能主动推送到客户端),导致如果服务器如果有连续的变化,这时只能使用轮询,而轮询效率过低,并不适合。于是 WebSocket
被发明出来
WebSocket
是 Html5 定义的一个新协议,与传统的 http 协议不同,Websocket
是一个持久化的协议,该协议允许由服务器主动的向客户端推送信息。
使用 WebSocket 协议的缺点是在服务器端的配置比较复杂。WebSocket 是一个全双工的协议,也就是通信双方是平等的,可以相互发送消息。
websocket
特点
- 支持双向通信,实时性更强;
- 可以发送文本,也可以二进制文件;
- 协议标识符是
ws
,加密后是wss
; - 较少的控制开销。连接创建后,
ws
客户端、服务端进行数据交换时,协议控制的数据包头部较小。在不包含头部的情况下,服务端到客户端的包头只有2~10
字节(取决于数据包长度),客户端到服务端的的话,需要加上额外的4字节的掩码。而HTTP
协议每次通信都需要携带完整的头部; - 支持扩展。ws协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议。(比如支持自定义压缩算法等)
- 无跨域问题。
websocket 最大的特点就是可以双向通信。 通信双方都可以主动发送信息。
实现比较简单,服务端库如
socket.io
、ws
,可以很好的帮助我们入门。而客户端也只需要参照api
实现即可:阮一峰websocket
WebSocket
的用法示例:
//创建WebSocket的对象。参数可以是 ws 或 wss,后者表示加密。
var ws = new WebSocket('wss://echo.websocket.org');
//发送请求
ws.onopen = function (evt) {
console.log('Connection open ...');
ws.send('Hello WebSockets!');
};
//接收数据
ws.onmessage = function (evt) {
console.log('Received Message: ', evt.data);
ws.close();
};
//关闭连接
ws.onclose = function (evt) {
console.log('Connection closed.');
};
适用/兼容场景:
FLASH Socket
- 长轮询: 定时发送
ajax
long poll
: 发送 --> 有消息时再response
常用api
new WebSocket(url)
ws.onerror = fn
ws.onclose = fn
ws.onopen = fn
ws.onmessage = fn
ws.send()
长连接SSE
SSE是HTML5新增的功能,SSE(sever-sent events)服务器端推送事件,是指服务器推送数据给客户端,而不是传统的请求响应模式。
EventSource
是服务器推送的一个网络事件接口。一个EventSource实例会对HTTP服务开启一个持久化的连接,以text/event-stream
格式发送事件, 会一直保持开启直到被要求关闭。
与WebSocket不同的是,SSE是服务端单向推送数据到客户端。数据信息被单向从服务端到客户端分发,当不需要以消息形式将数据从客户端发送到服务器时,这使它们成为绝佳的选择。例如,对于处理社交媒体状态更新,新闻提要或将数据传递到客户端存储机制(如IndexedDB或Web存储)之类的,EventSource无疑是一个有效方案。
注:IE不支持
// 前端接收数据信息
// 加上兼容判断
if(typeof(EventSource)!=="undefined"){
var source = new EventSource('/test/xx'); //指定路由发送
source.onopen = function(e) {
//当连接正式建立时触发
console.log(e);
};
source.onmessage = function(e) {
//监听信息的传输
var data = JSON.parse(e.data),
origin = e.origin;
console.log(data);
if (!data) {
// 数据传输完毕,无数据时关闭连接
source.close()
}
//data 服务器端传回的数据
//origin服务器端URL的域名部分,有protocol,hostname,port
//lastEventId用来指定当前数据的序号.主要用来断线重连时数据的有效性
};
source.onerror = function(e) {
//当连接发生error时触发
console.log(e);
};
}else{
console.log("不支持SSE");
}
// 后端单向发送数据:以nodejs为例
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Access-Control-Allow-Origin": "*" //允许跨域
});
var num =0;
function sendData(){
if(num===10){
res.end();
}else{
res.write("id: " + num + "\n");
res.write("data: " + num + "\n\n");
num++;
}
// 定时发送数据
setTimeout(sendData,1000);
}
sendData()
Node服务端代码实例
const http = require('http');
const
SSE = require('sse');
// 多个客户端
var sseClients = [];
// 配置服务
var server = http.createServer(function(req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('okay');
});
server.listen(8080, '127.0.0.1', function() {
var sse = new SSE(server, { path: '/test/xx', verifyRequest: (req) => {
return true;
}});
sse.on('connection', function(client) {
client.on('close', function() {
let index = sseClients.indexOf(client);
if (index > -1) {
sseClients.splice(index, 1);
}
});
sseClients.push(client);
client.send('Hello world');
client.count = 1;
// 定时发送信息
setInterval(() => {
sseClients.forEach(function (item, index) {
item.send(`[${sseClients.length}]服务端推送给客户端${index} : ${item.count}`);
item.count++;
});
}, 1000);
});
});
优缺点👇
- 优点:不用每次建立新连接,延迟较低;SSE和WebSocket相比,最大的优势是便利,服务端不需要其他的类库,开发难度较低。
- 缺点:如果客户端有很多,那就要保持很多长连接浏览器一直转圈,这会占用服务器大量内存和连接数。
转载自:https://juejin.cn/post/7086104218259423268