常用的服务端推送技术,包括轮询、长轮询、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. DOCKER中centos7的中文支持

    直接编写看下能否改变成识别中文字体 写到你的~/.bashrc里吧,然后重启终端(我写的是英文的啊,改成你要的) export LC_ALL=en_US.UTF-8 export LANGUAGE=e ...

  2. cs231n spring 2017 lecture1 Introduction to Convolutional Neural Networks for Visual Recognition

    1. 生物学家做实验发现脑皮层对简单的结构比如角.边有反应,而通过复杂的神经元传递,这些简单的结构最终帮助生物体有了更复杂的视觉系统.1970年David Marr提出的视觉处理流程遵循这样的原则,拿 ...

  3. 关于安装MongoDB4.0.9启动服务时显示connect failed错误的解决

    1.在安装完MongoDB4.0.9后在其/bin目录下打开CMD输入mongo测试服务是否开启,结果显示connect failed错误 解决方法: 出现这个错误的主要原因时因为在我们计算机的服务里 ...

  4. Css兼容性大全

    知识有所欠缺  疯狂脑补抄袭经验中... 兼容性处理要点1.DOCTYPE 影响 CSS 处理 2.FF: 设置 padding 后, div 会增加 height 和 width, 但 IE 不会, ...

  5. 将js进行到底:node学习3

    node重要API之NET--TCP编程之旅 废话:最近去了一趟上海会了会一个程序员朋友,途径SNH48握手会,说好我就去看看,没想到握手了王诗蒙,掉入巨坑:塞纳河.回来后边听着<春夏秋冬> ...

  6. Mysql数据导出到excel-基于python

    阅读本文大概需要 6分钟. 数据除了在测试平台显示,有时候也会习惯用excel以及邮件展示,那么我们可以在测试平台上加一个导出excel功能,方便操作,下面介绍主要代码以及逻辑. 使用操作数据库的py ...

  7. 29.eval函数

    eval 函数 eval() 函数十分强大 -- 将字符串 当成 有效的表达式 来求值 并 返回计算结果 123456789101112131415 # 基本的数学计算In [1]: eval(&qu ...

  8. Autotestplat体验中心

    web端 移动端 可戳[阅读原文]进行体验

  9. gerrit Q&A

    One or more refs/for/ names blocks change upload 原因 这是错误的原因是底层的git仓库有一些不正确的引用,通常是有些开发者使用过程中,直接推送到git ...

  10. Java 并发编程 -- Fork/Join 框架

    概述 Fork/Join 框架是 Java7 提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架.下图是网上流传的 Fork Join 的 ...