Web Sockets定义了一种在经过一个单一的 socket 在网络上进行全双工通信的通道。仅仅是传统的 HTTP 通信的一个增量的提升,尤为对于实时、事件驱动的应用来讲是一个飞跃。 经过Polling(轮询)、Long-Polling(长轮询)、Websocket、sse的对比。四种Web即时通讯技术比较它们的实现方式和各自的优缺点。 对比优缺点以下:javascript
# | 轮询(Polling) | 长轮询(Long-Polling) | Websocket | sse |
---|---|---|---|---|
通讯协议 | http | http | tcp | http |
触发方式 | client(客户端) | client(客户端) | client、server(客户端、服务端) | client、server(客户端、服务端) |
优势 | 兼容性好容错性强,实现简单 | 比短轮询节约资源 | 全双工通信协议,性能开销小、安全性高,可扩展性强 | 实现简便,开发成本低 |
缺点 | 安全性差,占较多的内存资源与请求数 | 安全性差,占较多的内存资源与请求数 | 传输数据须要进行二次解析,增长开发成本及难度 | 只适用高级浏览器 |
延迟 | 非实时,延迟取决于请求间隔 | 同短轮询 | 实时 | 非实时,默认3秒延迟,延迟可自定义 |
上面基本上包含了各个实现方式的优势和缺点,它们基于什么协议、由那端主动发送数据。html
短轮询(Polling)的实现思路就是浏览器端每隔几秒钟向服务器端发送http请求,服务端在收到请求后,不管是否有数据更新,都直接进行响应。在服务端响应完成,就会关闭这个Tcp链接,以下图所示:前端
示例代码实现以下:java
function Polling() {
fetch(url).then(data => {
// somthing
}).catch(err => {
console.log(err);
});
}
setInterval(polling, 5000);
复制代码
Tcp
链接是很是消耗资源的,服务端响应完成就会关闭这个Tcp
链接,下一次请求再次创建Tcp
链接。**Alex Russell(Dojo Toolkit 的项目 Lead)**称这种基于HTTP长链接
、无须在浏览器端安装插件的“服务器推”技术为“Comet”
。 经常使用的COMET分为两种:基于HTTP的长轮询(long-polling)技术,以及基于iframe的长链接流(stream)模式。node
客户端发送请求后服务器端不会当即返回数据,服务器端会阻塞请求链接不会当即断开,直到服务器端有数据更新或者是链接超时才返回,客户端才再次发出请求新建链接、如此反复从而获取最新数据。大体效果以下:web
客户端的代码以下:express
function LongPolling() {
fetch(url).then(data => {
LongPolling();
}).catch(err => {
LongPolling();
console.log(err);
});
}
LongPolling();
复制代码
当咱们在页面中嵌入一个iframe并设置其src时,服务端就能够经过长链接“源源不断”地向客户端输出内容。 例如,咱们能够向客户端返回一段script
标签包裹的javascript
代码,该代码就会在iframe中执行。所以,若是咱们预先在iframe
的父页面中定义一个处理函数process(),而在每次有新数据须要推送时,在该链接响应中写入。那么iframe中的这段代码就会调用父页面中预先定义的process()函数。(是否是有点像JSONP传输数据的方式?)后端
// 在父页面中定义的数据处理方法
function process(data) {
// do something
}
// 建立不可见的iframe
var iframe = document.createElement('iframe');
iframe.style = 'display: none';
// src指向后端接口
iframe.src = '/long_iframe';
document.body.appendChild(iframe);
复制代码
后端仍是以node为例浏览器
const app = http.createServer((req, res) => {
// 返回数据的方法,将数据拼装成script脚本返回给iframe
const iframeSend = data => {
let script = `<script type="text/javascript"> parent.process(${JSON.stringify(data)}) </script>`;
res.write(script);
};
res.setHeader('connection', 'keep-alive');
// 注意设置相应头的content-type
res.setHeader('content-type', 'text/html; charset=utf-8');
// 当有数据更新时,服务端“推送”数据给客户端
EVENT.addListener(MSG_POST, iframeSend);
req.socket.on('close', () => {
console.log('iframe socket close');
// 注意在链接关闭时移除监听,避免内存泄露
EVENT.removeListener(MSG_POST, iframeSend);
});
});
复制代码
效果以下: 安全
不过使用iframe有个小瑕疵,所以这个iframe至关于永远也不会加载完成,因此浏览器上会一直有一个loading标志。
他的优缺点和上面的长轮询同样。
WebSocket的一些特性和基础使用方法在这里就很少赘述了,请看另外一篇博客webSocket(一) 浅析; 大体代码以下: 服务端
const express = require('express');
const app = express();
const server = require('http').Server(app);
const WebSocket = require('ws');
const wss = new WebSocket.Server({port: 8080});
wss.on('connection', function connection(ws) {
console.log('server: receive connection');
ws.on('message', function incoming(message) {
console.log('server: recevied: %s', message);
});
ws.send('world');
});
app.get('/', function (req, res) {
res.sendfile(__dirname + '/index.html');
});
app.listen(3000);
复制代码
客户端
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = function () {
console.log('ws onopen');
ws.send('from client:hello');
};
ws.onmessage = function (e) {
console.log('ws onmessage');
console.log('from server:' + e.data);
}
复制代码
运行效果以下:
Server-Sent
是HTML5
提出一个标准。由客户端发起与服务器之间建立TCP
链接,而后并维持这个链接,直到客户端或服务器中的任何一方断开,ServerSent
使用的是"问"+"答"的机制,链接建立后浏览器会周期性地发送消息至服务器询问,是否有本身的消息。其实现原理相似于咱们在上一节中提到的基于iframe的长链接模式。 HTTP响应内容有一种特殊的content-type —— text/event-stream
,该响应头标识了响应内容为事件流,客户端不会关闭链接,而是等待服务端不断得发送响应结果。 SSE规范比较简单,主要分为两个部分:浏览器中的EventSource对象,以及服务器端与浏览器端之间的通信协议。
在浏览器中能够经过EventSource构造函数来建立该对象
var source = new EventSource('/sse');
复制代码
而SSE
的响应内容能够当作是一个事件流,由不一样的事件所组成。这些事件会触发前端EventSource
对象上的方法。
// 默认的事件
source.addEventListener('message', function (e) {
console.log(e.data);
}, false);
// 用户自定义的事件名
source.addEventListener('my_msg', function (e) {
process(e.data);
}, false);
// 监听链接打开
source.addEventListener('open', function (e) {
console.log('open sse');
}, false);
// 监听错误
source.addEventListener('error', function (e) {
console.log('error');
});
复制代码
EventSource经过事件监听的方式来工做。注意上面的代码监听了y_msg事件,SSE支持自定义事件,默认事件经过监听message来获取数据。实现代码以下:
客户端
// 显示聊天信息
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>");
});
复制代码
服务端 nodejs
var 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不会认为本次数据传输结束 会致使不会有下一次请求
});
复制代码
上面四种Web即时通讯技术比较,能够从不一样的角度考虑,它们的优先级是不一样的,基本上能够分为两大类基于http
和tcp
两种通讯中的一种。
兼容性考虑:短轮询>长轮询>长链接SSE>WebSocket
从性能方面考虑:WebSocket>长链接SSE>长轮询>短轮询
服务端推送:WebSocket>长链接SSE>长轮询