hello~亲爱的看官老爷们你们好~过完年第一周已经结束,是时候开始制定新的工做计划了。主要负责的项目是数据可视化平台,而使用中若是服务器能有推送能力让页端获得相关通知的话,就能实现不少功能上的优化。鉴于项目中 Node 端已经正式投入使用,前端拥有了本身的服务器,搞事情起来天然方便不少。前端
若干年前,服务器并无主动推送的能力,主要是经过轮询的方式来达到近似于服务器推送的能力。如今不须要这么麻烦,轮询只做为向下兼容的方案便可,当前主流的服务器推送是使用 SSE 或者 WebSocket 来实现的。二者对好比下:git
是否基于新协议 | 是否双向通讯 | 是否支持跨域 | 接入成本 | |
---|---|---|---|---|
SSE | 否(Http ) |
否(服务器单向) | 否(Firefox 支持跨域) | 低 |
WebSocket | 是(ws ) |
是 | 是 | 高 |
须要稍微解释一下的是接入成本。SSE 是相对轻量级的协议,(Node)代码实现上比较简单,而 WebSocket 是比较复杂的协议,虽然也有类库能够套用,也许页端方面二者代码量差很少,但服务器方面实现就复杂很多了。同时,要实现 WebSocket,是须要另起一个服务,而 SSE 并不须要。github
比较以后,对 SSE 与 WebSocket 有了大体的理解。项目对服务器推送的要求是发送通知,而将来可能须要接入实时同步的功能,结合项目实际状况与接入成本后,选择了 SSE。跨域
最后看一下浏览器支持状况以做参考:浏览器
IE
就 let it go
吧,平常不支持~其余浏览器仍是绿油油的,支持度仍是挺高的。服务器
项目中使用 Egg 做为框架,底层是 Koa2 的,于是使用 Koa2 做为示例。Node 端关键代码以下:app
app.use(async (ctx) => {
const { res, request: { url } } = ctx;
res.writeHead(200, {
'Content-Type': 'text/event-stream', // 服务器声明接下来发送的是事件流
});
let stream = new PassThrough();
let i = 0;
let timer = setInterval(() => {
if (i === 5) {
stream.write('event: pause\n'); // 事件类型
} else {
stream.write('event: test\n'); // 事件类型
}
stream.write(`id: ${+new Date()}\n`); // 消息ID
stream.write(`data: ${i}\n`); // 消息数据
stream.write('retry: 10000\n'); // 重连时间
stream.write('\n\n'); // 消息结束
i++;
}, 1000);
stream.on('close', function() {
console.log('closed.')
clearInterval(timer);
})
ctx.body = stream;
});
复制代码
服务器告诉客户端,返回的类型是事件流(text/event-stream),查阅 MDN 文档可知:事件流仅仅是一个简单的文本数据流,文本应该使用 UTF- 8 格式的编码。每条消息后面都由一个空行做为分隔符。以冒号开头的行为注释行,会被忽略。框架
以后就是消息主体了,尽管例子使用 setInterval
模拟不断发送推送,但换成任意条件触发推送也是能够的。stream.write
调用了5次,对应规范中的各个字段,理解以下:async
event
为消息的事件类型。客户端在 EventSource
中能够经过 addEventListener
收听相关的消息。该字段可省略,省略后客户端触发 message
事件。id
为事件 ID。做为客户端内部的“最后一个事件 ID”的属性值,用于重连,不可省略。data
为消息的数据字段,简单说就是客户端监听时间后,经过e.data
拿到的数据。retry
为重连时间,可省略该参数。\n\n
,不可省略。除了上面规定的字段名,其余全部的字段名都会被忽略。更详细的解释能够查阅 MDN 文档。有一个小细节须要注意,在 SSE 的草案中提到,"text/event-stream" 的 MIME 类型传输应当在静置 15 秒后自动断开。但实测(仅用了 Chrome)后发现,即便静置时间超过 15 秒,浏览器与客户端均不会断开链接。查阅了很多文章,均建议维护一套发送 \n\n
的心跳机制。我的认为此举有助于提升客户端程序的健壮性,但不是必须的。测试
最后是监听事件流的 close
事件,用于结束这次的连接。测试后发现,不管是让客户端调用 close
方法(下文有例子~),仍是异常结束,包括关闭窗口、关闭程序等,都能触发服务器的 close
事件。
客户端代码更简单,示例以下:
const source = new EventSource('http://localhost:3000/test');
source.addEventListener('open', () => {
console.log('Connected');
}, false);
source.addEventListener('message', e => {
console.log(e.data);
}, false);
source.addEventListener('pause', e => {
source.close();
}, false);
复制代码
前端童鞋对于这样的代码应该挺熟悉的,一切都是事件触发,根据不一样的事件执行对应的代码。稍微说明一下 EventSource
拥有的属性和方法,相信你们就能够愉快地使用了。
EventSource
有三个默认的事件,分别是:
open
:在链接打开时被调用。message
:收到一个没有 event 属性的消息时被调用。error
:当发生错误时被调用。两个只读属性:
readyState
:表明链接状态。可能值是CONNECTING (0), OPEN (1), 或者 CLOSED (2)。url
:表明链接的 URL。一个方法:
close
:调用后关闭链接(也就是上文所说起的)。更详细的解释能够查阅 MDN 文档
关于服务器 SSE 的简单介绍就到此为止了,能够看到,SSE 开发起来仍是比较简单的,接入成本很是低。但并非说 WebSocket 就是很差的,抛开实际场景谈业务就是耍流氓。此外上述代码只是演示,还能进一步进行优化的。如为了减轻服务器开销,能够创建一套机制有目的地断开与重连等,你们能够自行实现。
相关的代码已经丢到 Github 上,欢迎查阅。
感谢各位看官大人看到这里,知易行难,但愿本文对你有所帮助~谢谢!