常用的服务端推送技术,包括轮询、长轮询、websocket、server-sent-event(SSE)

传统的HTTP请求是由客户端发送一个request,服务端返回对应response,所以当服务端想主动给客户端发送消息时就遇到了问题。常见的业务场景如新消息提醒。

1、轮询(Polling)

最简单的方法是轮询,即客户端不断的发送请求来获取最新的消息。优点是实现简单。缺点是请求中有大半是无用,浪费带宽和服务器资源,同时,根据轮询的时间间隔不同,获取消息会有对应的延迟。

实例,新浪微博新消息提示。打开控制台可以发现 https://rm.api.weibo.com/2/remind/push_count.json 一个 jsonp 请求,这个请求每隔 30s 发送一次,每次需求 100ms 左右。

2、长轮询(Long Polling)

长轮询也比较容易理解,就是前端发起请求,并设置一个比较长的超时时间,后端接收到请求后,如果没有相关数据,会hold住请求直到有结果了,或者等待一定时间超时才返回。返回后,客户端会立即发起下一次请求。长轮询的控制权的服务器端,出现相关数据后会立即返回,实时性较高。

实例,QQ邮箱的新消息提醒。可以看到 https://wp.mail.qq.com/poll 请求不断发送, 没有新消息时,请求每次都会需要 30s,上一次请求返回后立即发送下一次请求,而当服务端有新消息时会立即返回,实时性较高。

用代码简单实现以上两种轮询

服务端代码

const express = require('express');
const port = 2333;
const app = express(); app.get('/start', start);
app.get('/getCurrentResult', getCurrentResult);
app.get('/getFinalResult', getFinalResult); app.listen(port, () => console.log(`Server listening on port ${port}`)); // 开始一个任务
function start(req, res) {
res.setHeader('Access-Control-Allow-Origin', '*'); // 允许跨域
_startTask();
res.json({
code: 0,
data: '开始任务'
});
}
// 返回实时结果
function getCurrentResult(req, res) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.json({
code: 0,
data: result
});
}
// 任务运行结束之后再返回运行结果
async function getFinalResult(req, res) {
res.setHeader('Access-Control-Allow-Origin', '*');
let result = await _startTask();
res.json({
code: 0,
data: result
});
} // 模拟执行一个任务
let result = null;
function _startTask() {
result = null;
return new Promise((res, rej) => {
// 任务需要10s 10s后得到result
setTimeout(() => {
result = 'hello world';
res(result);
}, 10000);
});
}

客户端代码

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>轮询&长轮询</title>
</head>
<body>
<button onclick="start(1)">开始任务 轮询</button>
<button onclick="start(2)">开始任务 长轮询</button>
<div id="hint"></div>
<script>
function start(type) {
console.log('===start===');
fetch('http://localhost:2333/start').then(res => {
return res.json();
}).then(function(data){
console.log(data);
setHint('任务执行中...');
type == 1 ? loop() : longPolling();
}).catch(function(err){
console.error(err);
});
} function loop() {
fetch('http://localhost:2333/getCurrentResult').then(res => {
return res.json();
}).then(function(data){
console.log(data);
if (!data.data) {
setTimeout(loop, 1000); // 1s轮询一次
} else {
setHint('执行成功 结果 = ' + data.data);
}
}).catch(function(err){
console.error(err);
});
} function longPolling() {
fetch('http://localhost:2333/getFinalResult').then(res => {
return res.json();
}).then(function(data){
console.log(data);
setHint('执行成功 结果 = ' + data.data);
}).catch(function(err){
console.error(err);
});
} function setHint(text) {
hint.innerHTML = text;
}
</script>
</body>
</html>

3、WebSocket

上面两种方式,实际上还是客户端单向发送消息,而 WebSocket 本质上解决了这个问题,WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

WebSocket 握手阶段采用 HTTP 协议,客户端浏览器首先要向服务器发起一个 HTTP 请求,其中附加头信息 Upgrade: WebSocket 表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。WebSocket 没有同源限制。

实例,LeetCode-CN 的新消息提醒,猜测是通过 WebSocket 实时返回是否有新消息,再通过 XHR 请求具体信息。

简单代码实现,使用了 ws 包

服务端代码

const WebSocket = require('ws');
const http = require('http'); const port = 2333; const server = http.createServer();
const wss = new WebSocket.Server({ server, path: '/ws' }); wss.on('connection', function(ws) {
console.log('WebSocket connection established');
let progress = 0; ws.send(`任务进度 -- ${progress}%`);
let timer = setInterval(() => {
// 推送任务完成进度
if (++progress % 10 == 0) {
ws.send(`任务进度 -- ${progress}%`);
}
if (progress == 100) {
clearInterval(timer);
ws.close();
}
}, 200); ws.on('close', () => {
console.log('WebSocket connection closed');
clearInterval(timer);
});
}); server.listen(port, function() {
console.log(`Server listening on port ${port}`);
});

客户端代码

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>WebSocket</title>
</head>
<body>
<button onclick="start()">开始任务</button>
<div id="hint" style="white-space: pre-line;"></div>
<script>
let ws;
function start() {
if (ws) ws.close(); console.log('===start==='); ws = new WebSocket('ws://localhost:2333/ws');
ws.onmessage = function(ev) {
let data = ev.data;
console.log(data);
showMessage(data);
}
ws.onerror = function() {
console.log('WebSocket error');
};
ws.onopen = function() {
console.log('WebSocket connection established');
};
ws.onclose = function() {
console.log('WebSocket connection closed');
ws = null;
};
} function showMessage(text) {
hint.innerHTML = hint.innerHTML + '\n' + text;
}
</script>
</body>
</html>

4、Sever-Sent Event(SSE)

SSE 是一种能让浏览器通过 HTTP 连接自动收到服务器端推送的技术,EventSource 是 浏览器提供的对应 API。通过 EventSource 实例打开与 HTTP 服务器的持久连接,该服务器以文本/事件流格式发送事件,连接会保持打开状态,直到服务端或客户端主动关闭。

与 WebSocket 区别,SSE 基于 HTTP 协议,使用简单,SSE 默认支持断线重连,但是 SSE 只能由服务端向客户端推动消息。

SSE 有四种字段,其他的字段会被忽略。字段之间用\n 分隔,每条消息要以 \n\n 结尾。

data    // 数据项
event // 事件项 默认为 message 可设置任意值
id // 数据标识符,用于断线重连
retry // 断线后重连时间

实际应用,SSE 在股价显示场景应用较多,如 东方财富网(感谢 @heart_ 提醒)

简单代码实现:

服务端代码

const express = require('express');
const port = 2333;
const app = express(); app.get('/sse', respondSSE); function respondSSE(req, res) {
let msg = 0;
let timer;
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
}); res.write(sseMsg({
data: '===start===',
// 默认 event 是 'message'
})); timer = setInterval(() => {
res.write(sseMsg({
id: Date.now(),
event: 'custom-event',
data: msg++,
retry: 2000
}));
}, 1000); res.on('close', function () {
clearInterval(timer);
console.log('SSE connection closed');
});
} const sseMsg = (sseObj) => {
let fields = ['id', 'event', 'data', 'retry'];
return fields
.filter(f => sseObj[f] != null)
.map(f => f + ':' + sseObj[f]).join('\n') + '\n\n';
} app.listen(port, () => console.log(`Server listening on port ${port}`));

客户端代码

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<button onclick="start()">开始</button>
<button onclick="over()">结束</button>
<div id="hint"></div>
<script>
let source;
function start() {
source = new EventSource('http://localhost:2333/sse');
source.addEventListener('open', () => {
console.log('SSE connection established');
}, false); source.addEventListener('message', e => {
console.log(e.data);
}, false); source.addEventListener('custom-event', e => {
console.log('custom-event data: ', e.data);
showMessage('新消息: ' + e.data + ' 条');
}, false);
} function over() {
source.close();
} function showMessage(text) {
hint.innerHTML = text;
}
</script>
</body>
</html>

5、HTTP/2 Server Push

文章比较少,而且和上面的推送并不一样,看到这篇讲得不错~

Node HTTP/2 Server Push 从了解到放弃

参考资料

一文了解服务端推送(含JS代码示例)的更多相关文章

  1. [译]servlet3.0与non-blocking服务端推送技术

    Non-blocking(NIO)Server Push and Servlet 3 在我的前一篇文章写道如何期待成熟的使用node.js.假定有一个框架,基于该框架,开发者只需要定义协议及相关的ha ...

  2. Netty实现一个简单聊天系统(点对点及服务端推送)

    Netty是一个基于NIO,异步的,事件驱动的网络通信框架.由于使用Java提供 的NIO包中的API开发网络服务器代码量大,复杂,难保证稳定性.netty这类的网络框架应运而生.通过使用netty框 ...

  3. 升级NGINX支持HTTP/2服务端推送

    内容概览 NGINX从1.13.9版本开始支持HTTP/2服务端推送,上周找时间升级了下NGINX,在博客上试验新的特性. 升级工作主要包括: 升级NGINX 修改NGINX配置 修改wordpres ...

  4. C# 服务端推送,十步十分钟,从注册到推送成功

    目标 展示 C# 服务端集成极光推送的步骤,多图少字,有图有真相. 使用极光推送, C# 服务端推送到 Demo App,Android 手机收到推送,整理为十个步骤,使用十分钟左右,完成从注册账号到 ...

  5. mqtt协议实现 java服务端推送功能(三)项目中给多个用户推送功能

    接着上一篇说,上一篇的TOPIC是写死的,然而在实际项目中要给不同用户 也就是不同的topic进行推送 所以要写活 package com.fh.controller.information.push ...

  6. Spring Boot 集成 WebSocket 实现服务端推送消息到客户端

    假设有这样一个场景:服务端的资源经常在更新,客户端需要尽量及时地了解到这些更新发生后展示给用户,如果是 HTTP 1.1,通常会开启 ajax 请求询问服务端是否有更新,通过定时器反复轮询服务端响应的 ...

  7. java SDK服务端推送 --极光推送(JPush)

    网址:https://blog.csdn.net/duyusean/article/details/86581475 消息推送在APP应用中越来越普遍,来记录一下项目中用到的一种推送方式,对于Andr ...

  8. 利用WebSocket和EventSource实现服务端推送

    可能有很多的同学有用 setInterval 控制 ajax 不断向服务端请求最新数据的经历(轮询)看下面的代码: setInterval(function() { $.get('/get/data- ...

  9. mqtt协议实现 java服务端推送功能(二)java demo测试

    上一篇写了安装mosQuitto和测试,但是用cmd命令很麻烦,有没有一个可视化软件呢? 有,需要在google浏览器下载一个叫MQTTLens的插件 打开MQTTLens后界面如下: 打开conne ...

随机推荐

  1. overflow属性的应用

    在使用JQueryUI chosen插件的时候,由于页面布局的原因,下拉列表框超出div范围,图形效果严重变形,一点解决的思路都没有,最后请教公司前端,瞬间解决,原来使用CSS 中的overflow属 ...

  2. pycharm中无法导入pip安装的包

    https://blog.csdn.net/mdxiaohu/article/details/82430060 2020.1.20 练习通过python操作数据库的时候需要导入一个包,因为看代码写的是 ...

  3. Android开发之《USB Camera》

    SimpleWebCam Source Code:https://bitbucket.org/neuralassembly/simplewebcam/src 1. USB摄像头UVC兼容(如今大部分兼 ...

  4. axios 传对象(JavaBean)到后台

    //user对象 let user = JSON.stringify({ userAccountNumber: own.userName, userPassword: own.userPassword ...

  5. 转:ZABBIX监控H3C设备的CPU和内存使用率

      由于最近监控的H3C路由器经常出现死机现象,SNMP获取不到数据,后面检查发现是CPU使用率过高,直接导致无法处理SNMP请求,所以需求来了,怎样通过SNMP监控H3C路由器的CPU和内存使用率? ...

  6. MySQL5.7报错[ERROR] Unix socket lock file is empty /tmp/mysql.sock.lock的解决方法

    发现MySQL服务器因系统磁盘写满导致服务停了,清理磁盘后启动服务时无法正常启动,查看localhost.err日志发现如下报错: [ERROR] Unix socket lock file is e ...

  7. Python-控制语句及函数

    if-elif-else for while 函数 函数定义 空函数 pass 返回多个值 可变参数 * 关键字参数 ** 控制语句 if - elif - else 比如,输入用户年龄,根据年龄打印 ...

  8. 未来京东真能成为中国第一大B2C电商平台吗?

    ​     2月10日,京东集团在北京举行2017年"科技引领未来"开年年会.在本届年会上,京东宣布全面向技术转型.京东集团CEO刘强东正式对外公布未来12年的战略:在以人工智能为 ...

  9. HTML笔记03------cookie

    新浪布局 初始布局代码: div.header+(div.container>(div.left+div.right))+div.footer ---------- .header{height ...

  10. 漫谈国内外Android生态:华为发布的 HMS 服务,对 Mate30 系列无法搭载 Google GMS 的补偿有多大(原创)

    如果既用过iPhone,也用过国际版Android,还用过国内的安卓,(并且这三种都用了半年以上),就能体会到GMS多重要.可以说,iOS的体验大幅度领先于国内的安卓,一多半的原因是国内安卓没有GMS ...