一文了解服务端推送(含JS代码示例)
常用的服务端推送技术,包括轮询、长轮询、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代码示例)的更多相关文章
- [译]servlet3.0与non-blocking服务端推送技术
Non-blocking(NIO)Server Push and Servlet 3 在我的前一篇文章写道如何期待成熟的使用node.js.假定有一个框架,基于该框架,开发者只需要定义协议及相关的ha ...
- Netty实现一个简单聊天系统(点对点及服务端推送)
Netty是一个基于NIO,异步的,事件驱动的网络通信框架.由于使用Java提供 的NIO包中的API开发网络服务器代码量大,复杂,难保证稳定性.netty这类的网络框架应运而生.通过使用netty框 ...
- 升级NGINX支持HTTP/2服务端推送
内容概览 NGINX从1.13.9版本开始支持HTTP/2服务端推送,上周找时间升级了下NGINX,在博客上试验新的特性. 升级工作主要包括: 升级NGINX 修改NGINX配置 修改wordpres ...
- C# 服务端推送,十步十分钟,从注册到推送成功
目标 展示 C# 服务端集成极光推送的步骤,多图少字,有图有真相. 使用极光推送, C# 服务端推送到 Demo App,Android 手机收到推送,整理为十个步骤,使用十分钟左右,完成从注册账号到 ...
- mqtt协议实现 java服务端推送功能(三)项目中给多个用户推送功能
接着上一篇说,上一篇的TOPIC是写死的,然而在实际项目中要给不同用户 也就是不同的topic进行推送 所以要写活 package com.fh.controller.information.push ...
- Spring Boot 集成 WebSocket 实现服务端推送消息到客户端
假设有这样一个场景:服务端的资源经常在更新,客户端需要尽量及时地了解到这些更新发生后展示给用户,如果是 HTTP 1.1,通常会开启 ajax 请求询问服务端是否有更新,通过定时器反复轮询服务端响应的 ...
- java SDK服务端推送 --极光推送(JPush)
网址:https://blog.csdn.net/duyusean/article/details/86581475 消息推送在APP应用中越来越普遍,来记录一下项目中用到的一种推送方式,对于Andr ...
- 利用WebSocket和EventSource实现服务端推送
可能有很多的同学有用 setInterval 控制 ajax 不断向服务端请求最新数据的经历(轮询)看下面的代码: setInterval(function() { $.get('/get/data- ...
- mqtt协议实现 java服务端推送功能(二)java demo测试
上一篇写了安装mosQuitto和测试,但是用cmd命令很麻烦,有没有一个可视化软件呢? 有,需要在google浏览器下载一个叫MQTTLens的插件 打开MQTTLens后界面如下: 打开conne ...
随机推荐
- 关于Angular2与蚂蚁的NG-ZOORO一同开发时[disabled]="true"动态绑定失效的解决方法
在使用Angular2与蚂蚁的NG-ZOORO一同开发时,当我们的表单使用的是formControlName="value"时[disabled]="true" ...
- 事件和异常的传播 · 农场主的黑科技.
inBound事件的传播 何为inBound事件以及ChannelInboundHandler ChannelRead事件的传播ChannelRead是典型的inbound事件,以他为例了解inbou ...
- UnitTest测试框架-操作步骤
一.UnitTest 1. TestCase 说明:测试用例 1.新建类并集成unittest.TestCase 2. TestSuite 说明:测试套件(多条用例) 方法: 1. 实例化 suite ...
- Ionic3学习笔记(七)Storage
本文为原创文章,转载请标明出处 目录 简介 安装 配置 使用 1. 简介 Storage可以很容易的存储键值对和JSON对象.Storage在底层使用多种存储引擎,根据运行平台选择最佳的存储方式. 当 ...
- 使用js闭包的好处
使用闭包有以下几大好处: a:希望一个变量长期驻扎在内存中. b:避免全局变量的污染.
- OpenStack入门
云计算优势 降低成本,安全稳定,易扩展. 云计算三种服务模式 IaaS:基础设施即服务 IaaS(Infrastructure-as-a- Service):基础设施即服务.消费者通过Internet ...
- python设置检查点简单实现
说检查点,其实就是对过去历史的记录,可以认为是log.不过这里进行了简化.举例来说,我现在又一段文本.文本里放有一堆堆的链接地址.我现在的任务是下载那些地址中的内容.另外因为网络的问题或者网站的问题, ...
- zookeeper 实战 - Pymjer 的博客
下载 $ wget http://apache.forsale.plus/zookeeper/zookeeper-3.4.9/zookeeper-3.4.9.tar.gz 配置conf/zoo.cfg ...
- Linux系统发行版本及其区别
1 Linux系统组成 Linux操作系统=Linux内核+GNU软件及系统软件+必要的应用程序.下表为Linux系统各组成部分的贡献人员: Linux内核 GNU组件(gcc.bash) 其他必要应 ...
- C#连接Informix数据库
最近在工作中遇到了需要连接Informix数据库的问题,在通过研究后发现了可以通过多种方式实现,我选择的是通过IBM Informix .NET Provider.该方式需要引用IBM.Data.In ...