HTTP 协议遵循经典的客户端 - 服务器模型, 客户端发送一个请求, 然后等待服务器端的响应, 服务器端只能在接收到客户端的请求之后进行响应, 不能主动的发送数据到客户端.
客户端想要在不刷新页面的情况下实时获取到服务器端最新的数据, 可以通过以下途径:
轮询
长轮询
HTTP 流
SSE
web Sockets
1. 轮询
客户端 (浏览器) 定时向服务器端发送请求, 获取最新的数据. 可以通过在一个定时器中触发 Ajax 请求来实现:
- // 每两秒触发一次 Ajax 请求, 获取最新的数据
- setInterval(function(){
- //do some Ajax call here to retrieve latest data
- },2000);
short-polling.PNG
优点:
实现简单, JS 端进行一些更改即可, 无需后端服务任何改动
缺点:
轮询的间隔过长, 会导致用户不能及时接收到更新的数据; 轮询的间隔过短, 会导致查询请求过多, 增加服务器端的负担
2. 长轮询
长轮询方法实现原理如下:
客户端发起一个请求到服务器端(http request)
服务器端一直保持连接打开, 直到有数据数据可发送给客户端, 再返回这个请求(http response)
客户端收到服务器端返回的数据后, 处理数据, 并立马发起一个新的请求
...
long-polling.PNG
- //server 端示例(Node.JS)
- var aTargets = [];
- App.get('/notification', function(req, res) {
- aTargets.push(res);
- //res.end(); 这里不调用 res.end(), 让 http request 连接一直存活着
- })
- // 此方法会在有新的数据时调用
- onNewNotification : function (data) {
- aTargets.forEach(function(res){
- res.send(data);// 当有新的数据时, 再调用 res.send(data)返回最新的数据, 结束一次 http 请求
- })
- }
优点:
可以及时获取到最新的数据
相较于轮询策略, 减少了请求数量
缺点:
服务器端要一直保持连接, 不能释放, 由于一个服务器能够处理的连接数有限, 当达到服务器处理的上限的时候, 服务器将无法响应新的请求
3. HTTP 流
HTTP 流区别于轮询和长轮询方法, 它在客户端网页的生命周期内, 只需要使用一个 HTTP 连接, 也就是只会向服务器发送一个请求, 对于这个请求, 服务器会保持 HTTP 连接(不返回 response), 然后周期性的向浏览器发送数据.
- //server 端示例(Node.JS)
- let express = require("express");
- let App = express();
- App.use(express.static("resources"));
- App.get("/httpstream",function(req, res){
- var x = 0;
- res.setHeader('Connection', 'Transfer-Encoding');
- res.setHeader('Content-Type', 'text/html; charset=utf-8');
- res.setHeader('Transfer-Encoding', 'chunked');// 声明数据传输编码为 chunked, 让浏览器及时处理
- setInterval(function(){
- res.write(x+++"|"); // 每隔 2s 向客户端发送一次数据
- },2000);
- });
- App.listen(3000);
服务器端接收到请求后, 每隔两秒向客户端输出一点文字, 但是不会使用 res.end()或者 res.send()结束当前 http 请求.
- // 客户端示例 JS
- var xhr = new XMLHttpRequest();
- var received = 0;
- var result = "";
- xhr.open("get","/httpstream",true);
- xhr.onreadystatechange = function () {
- if (xhr.readyState == 3) { //readystate 3 表示正在解析数据
- result = xhr.responseText.substring(received);// 截取最新的数据
- received += result.length;
- console.log(result);
- }
- }
- xhr.send();
随着不断从服务器端接收到数据, 客户端的 readyState 会周期性的变成 3,responseText 包含所有的数据源. 通过 received 来记录之前已经处理过的数据长度, 然后在 responseText 中截取最新的数据.
http-stream.PNG
优点:
页面的整个生命周期内, 只需要建立一个 http 连接
缺点:
如果接入的客户端过多, 服务器端会因为 http 连接有限而无法为新的客户端提供服务
客户端接收到的数据流会越来越大, 最终可能会引发页面的性能问题
4. SSE
SSE(Server-Sent Events)是基于 HTTP 实现的一套服务器向客户端发送数据的 API. 他是针对上面说到的三种方法 (轮询, 长轮询, HTTP 流) 的一个标准 API 实现.
使用 SSE API 可以创建到服务器端的但相连接, 服务器可以通过这个连接发送任意数据. 它有以下特点:
断开自动连接
服务器响应的 MIME 类型必须是 text/event-stream
需要浏览器 API 支持(参考浏览器兼容性)
使用方法如下:
- // 客户端 JS
- var source = new EventSource(url);
- // 建立连接时触发
- source.onopen = function () {
- //do something here
- };
- // 从服务器端接收到新的事件时触发
- source.onmessage = function (event) {
- var data = event.data; // 服务器返回的数据存放在 event.data 中
- };
- // 连接异常时触发
- source.onerror = function () {
- //do something here
- };
客户端创建一个 EventSource 对象, 绑定到对应的 url, 然后监听该对象的 onmessage 事件就可以获取到最新的数据.
- //server 端示例(Node.JS)
- let express = require("express");
- let App = express();
- App.use(express.static("resources"));
- App.get("/httpstream",function(req, res){
- var x = 0;
- res.writeHead(200, {
- "Content-Type":"text/event-stream",
- "Cache-Control":"no-cache",
- "Connection":"keep-alive"
- });
- // 每个 1s 往客户端发送一条数据
- setInterval(function(){
- res.write("data:" + x++ + "\n\n");// 发送的数据格式必须是 "data: <内容>/n/n"
- },1000);
- });
- App.listen(3000);
- 5. Web Sockets
不同于 SSE,Web Sockets 采用了一套全新的协议 (ws/wss) 来建立客户端到服务器端的全双工, 双向通信连接.
关于 Web sockets 的使用, 这篇文章: http://www.ruanyifeng.com/blog/2017/05/websocket.html 已经介绍的非常全面了, 我就不再赘述.
优点:
双向通信, 实时连接
相较于 HTTP 请求更加高效(不需要握手, 连接始终存在; 无需携带头部信息)
缺点:
稳定性和成熟度问题
建议:
在使用的过程中, 请根据产品将来的使用环境 (支持的浏览器类型, 版本), 使用场景(双向通信, 单向通信) 这些点, 并结合每一种方法的优缺点去考虑, 然后选取对应的策略.
来源: http://www.jianshu.com/p/72372741df5f