实际应用中经常会有实现页面无刷新更新内容的需求。本文就轮询、长轮询、SSE(Server Sent Event)和WS(WebSocket)介绍具体的应用场景。
相关知识点:es6 API、nodejs、html5
文章结构:
轮询
长轮询
SSE(Server Sent Event)
WS(WebSocket)
1. 轮询-数据拉取
轮询是在特定的的时间间隔(如每隔1秒),由浏览器(客户端)对服务器发出HTTP request,然后由服务器返回最新的数据给客户端的浏览器。不管服务端数据有没有变化,客户端都会发起请求,来获取数据。
特点:客户端主动向服务器请求数据,服务器被动推送消息。
缺点:浏览器需要不断的向服务器发出请求,然而HTTP request 的header是非常长的,里面包含的有用数据可能只是一个很小的值,这样会占用很多的带宽,造成资源浪费。同时也不能保证及时更新最新信息。
// 客户端// 1、原生js操作var xhr = new XMLHttpRequest(); setInterval(() => { xhr.open('GET', '/user'); xhr.onreadystatechange = () => { if (xhr.readyState == 4) { console.log(xhr.response); } }; xhr.send(); }, 1000);// 2、es6API fetchsetInterval(() => { fetch('/user') .then(response => response.json()) .then(json => { console.log(json); }) .catch(error => console.error('fetch ', error)); }, 1000);// 3、jquery ajaxsetInterval(() => { $.ajax('/get') .done((data) => { console.log(data); }) .fail(() => { console.log('ajax error'); }) }, 1000);
2. 长轮询
客户端发送请求后,服务器端不会立即返回数据,服务器端会阻塞请求,连接不会立即断开,直到服务器端有数据更新或者是连接超时才返回,客户端才再次发出请求,新建连接,如此反复,从而获取最新数据。
特点:客户端主动向服务器请求数据,服务器被动推送消息。一个连接断开后,就有新的连接产生,跟轮询(短轮询)相比,能减少资源浪费但依旧很浪费资源。
// 客户端// 1、原生js操作function getXHRNewData() { let xhr = new XMLHttpRequest(); xhr.open('GET', '/user'); xhr.onreadystatechange = () => { if (xhr.readyState == 4) { console.log(xhr.response); getXHRNewData(); } }; xhr.send(); } getXHRNewData();// 2、es6API fetchfunction getFetchNewData() { fetch('/user') .then(response => { console.log(response.json()); }) .catch(error => { console.error('fetch ', error); }) .finally(() => { getFetchNewData(); }); } getFetchNewData();// 3、jquery ajaxfunction getAjaxNewData() { $.ajax('/get') .done((data) => { console.log(data); }) .fail(() => { console.log('ajax error'); }) .always(() => { getAjaxNewData(); }); } getAjaxNewData();
3. SSE(Server Sent Event)
Server-Sent是HTML5提出一个标准。由客户端发起与服务器之间创建TCP连接,然后并维持这个连接,直到客户端或服务器中的任何一放断开,ServerSent使用的是"问"+"答"的机制,连接创建后浏览器会周期性地发送消息至服务器询问,是否有自己的消息。
特点:客户端只需连接一次,Server就定时推送,除非其中一端断开连接。并且SSE会在连接意外断开时自动重连。
缺点:并没有达到最新消息服务器端的实时推送。
// 客户端// 显示聊天信息let chat = new EventSource("/chat-room"); chat.onmessage = function (event) { let msg = event.data; $(".list-group").append("<li class='list-group-item'>" + msg + "</li>"); // chat.close(); 关闭server-sent event};// 自定义事件chat.addEventListener("myChatEvent", function (event) { let msg = event.data; $(".list-group").append("<li class='list-group-item'>" + msg + "</li>"); });
// 服务端 nodejsvar express = require('express');var router = express.Router(); router.get('/chat-room', function (req, res, next) { // 当res.white的数据data 以\n\n结束时 就默认该次消息发送完成,触发onmessage方法,以\r\n不会触发onmessage方法 res.header({ "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive" }); // res.white("event: myChatEvent\r\n"); 自定义事件 res.write("retry: 10000\r\n"); // 指定通信的最大间隔时间 res.write("data: start~~\n\n"); res.end(); // 不加end不会认为本次数据传输结束 会导致不会有下一次请求});
4. WS(WebSocket)-全双工通信
这也是一个HTML5标准中的一项内容,他要求浏览器可以通过JavaScript脚本手动创建一个TCP连接与服务端进行通讯。WebSocket使用了ws和wss协议,需要服务器有与之握手的算法才能将连接打开。即可以满足"问"+"答"的响应机制,也可以实现主动推送的功能。
特点:省流量,客户端什么时候想发就发,服务器端什么时候想回就回,两边都有监听者socket在负责。可以实现服务端消息的实时推送。
// 客户端// WebSocket对象一共支持四个消息事件 onopen, onmessage, onclose和,let socket = new WebSocket('ws://localhost:2000/'); socket.onopen = function () { console.log("connected"); socket.send('Hello Server~~'); }; socket.onmessage = function (event) { $(".list-group").append(`<li class='list-group-item'>${event.data}</li>`); }; socket. = function () { console.log("connect error"); };
// 服务器端 nodejs 需要提前安装ws模块:npm install wslet Server = require("ws").Server;let wss = new Server({ port: 2000}); wss.on("connection", function (ws) { ws.on("message", function (data) { ws.send("服务器时间:" + new Date() + " => " + data); // 一个send方法就会触发前端的message事件 // ws.send(JSON.stringify({"name": "test"})); }); ws.on("close", function () { console.log("close websocket。"); }); });
5. 参考文章
作者:家里有颗核桃树
链接:https://www.jianshu.com/p/ef054d3d1b62