本文首发于个人博客 Cyy’s Blog
转载请注明出处 https://cyyjs.top/blog/5b835dbd30e02e324caa737b

最近开发项目时,需要实现一个通知功能;当时想到的技术方案有两种:

  1. HTTP Polling 轮询方式
  2. 使用 WebSocket

第一种方式比较原始,有很多缺点,浪费带宽资源,也不实时;WebSocket虽然好,但对于目前业务来说,有点重,因为项目其实只需要从服务器发通知到客户端,不需要双向交互。后来发现了一项新的技术SSE

# 什么是 SSE

SSE即Server-send event,是服务器推送事件。一个网页获取新的数据通常需要发送一个请求到服务器,也就是向服务器请求的页面。使用server-sent event的方法,服务器可以在任何时刻向我们的web页面推送数据和信息。

# 如何使用

# 客户端

1、创建EventSource对象,传入一个服务器接口地址

const eventSource = new EventSource('http://127.0.0.1:3000/sse')

2、监听服务器发送的消息

eventSource.onmessage = function(e) {
    console.log(`message-data: ${e.data}`);
}

// 或
eventSource.addEventListener('message', e => {
    console.log(`message-data: ${e.data}`);
}, false);

// 监听自定义事件
eventSource.addEventListener('event-1', e => {
    console.log(`message-data: ${e.data}`);
}, false);

// 监听错误
eventSource.onerror = function (error) {
    console.log(error)
}

# 服务端

服务器端发送的响应内容应该使用值为"text/event-stream"的MIME类型,并按照固定的事件流格式即可。

这里以Nodejs作为服务器为例:

const http = require('http');
const fs = require('fs');
http.createServer(function (req, res) {
    if (req.url === '/sse') {
        res.writeHead(200, {
            "Content-Type": "text/event-stream" //设置头信息
        });
        let data = {
            id: 1,
            message: 'hello'
        }
        // 发送消息
        res.write(
            'event: event-1' + data.id + '\n' +
            'data:' + JSON.stringify(data) + '\n\n', // 必须“\n\n”结尾
        );
    } else {
        // 返回页面
        fs.readFile('./sse.html', function (err, content) {
            res.writeHead(200, { 'Content-Type': 'text/html' });
            res.end(content, 'utf-8');
        });
    }
}).listen(3000);

这样客户端就可以接收到服务器发送的数据了。

# 如何指定用户发送

如果我们到通知是以广播的形式发送,就很简单了,所有客户端监听同样的事件就可以了;那么要对指定用户发送通知,该如何实现呢?

目前我采用的方式是,客户端,根据用户id来监听不同的事件,例如用户id为1就监听event-1,这样服务端发送的时候,就指定发送的事件名称为event-1即可。

# 如何在程序中调用发送通知接口

例子中可以看到,当使用http请求http://127.0.0.1:3000/sse接口时,就会触发服务器发送事件,但是我们实际应用中场景中,触发条件肯定不会是这样的;比如我修改了数据,或者调用了别的接口,就需要去触发通知,怎么办呢?

这里使用Nodejsevents,修改服务端代码如下:


var http = require('http');
var fs = require('fs');
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
http.createServer(function (req, res) {
    if (req.url === '/sse') {
        res.writeHead(200, {
            "Content-Type": "text/event-stream" //设置头信息
        });
        myEmitter.on('sseEvent', (data) => { // 监听事件调用
            // 发送消息
            res.write(
                'event: event-1' + data.id + '\n' +
                'data:' + JSON.stringify(data) + '\n\n', // 必须“\n\n”结尾
            );
        });
    } else {
        // 返回页面
        fs.readFile('./sse.html', function (err, content) {
            res.writeHead(200, { 'Content-Type': 'text/html' });
            res.end(content, 'utf-8');
        });
    }
}).listen(3000);

然后在需要发通知的地方直接触发事件就行了:

myEmitter.emit('sseEvent', data);

# 参考链接

server-sent events
Node.js events