Server-sent events(SSE)& EventSource 客户端使用与服务器基础实现(基于Node.js)

Posted Naisu Xu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Server-sent events(SSE)& EventSource 客户端使用与服务器基础实现(基于Node.js)相关的知识,希望对你有一定的参考价值。

目的

传统的Web前后台通讯主要是使用HTTP方式进行的,通常是前台需要什么就主动向后台请求,后台无法直接向前台发送数据。通常后台数据如果频繁变动的话前台主要靠 轮询 或是 长连接 方式来处理,两种方式相对来说都不是很优雅。

而到了现在有两种方案来处理这个问题: WebSocket 和 Server-sent events(SSE) 。 WebSocket 可以实现实时的双向通讯,功能上来说是非常强的,不过相对于HTTP而言是一种另起炉灶的技术。Server-sent events 则是在HTTP之上扩展出来的功能,有点像前面提到的 长连接 ,但是这是原生的标准,功能上更加完善,使用起来也更加方便。这篇文章将对 Server-sent events 相关内容做个说明。

基础说明

Server-sent events 是H5中加入的功能,它是在HTTP上扩展出来的功能,使得服务器可以主动发送数据给客户端。在客户端使用 EventSource 接口来处理 Server-sent events 。

Server-sent events 最核心的其实就是新增了一个 MIME type : text/event-stream 。看名字就是知道这是一个流,只要不结束的的话就一直可以传数据。

实际使用中只要客户端主动发起访问接口,建立连接后就不用管了,服务器会在需要的时候主动推送消息。

Server-sent events 这种原生的功能有一个好处是浏览器端默认会自动重连。

客户端使用

客户端使用 EventSource 接口来处理 Server-sent events 。使用方法主要如下:

    var es = new EventSource("/sse"); // 声明EventSource对象并连接url

    es.onmessage = (e) => {} // 收到服务器消息时触发

    es.onopen = (e) => {} // 连接建立时触发

    es.onerror = (e) => {} // 发生错误时触发

    // es.close(); // 关闭EventSource连接

    // console.log(es.url); 

    // console.log(es.readyState); // 连接状态: 0 - connecting; 1 - open; 2 - closed; 

客户端基本的使用是比较简单的,演示需要结合下面服务器进行。


对于前端来说直接使用JS的EventSource接口就可以使用SSE了,对于其它语言作为客户端来说可能没有现成的方法可用,但其实要用上也挺简单,使用 GET 方法访问链接,并在头文件中包含下面属性即可(只是建议,并不是必须):
accept: text/event-stream
cache-control: no-store
connection: keep-alive
这样就可以建立起连接了,之后等待并处理来自服务器的数据就行。

服务器实现

Server-sent events 是在HTTP上扩展出来的功能,所以服务器实现只需要在HTTP服务器的基础上稍作处理即可,最主要的就是设置响应头中 MIME type 为 text/event-stream 。下面是个最简单的例子:

const http = require('http')

const server = http.createServer((req, res) => {

     // 访问链接 /sse
    if (req.url == '/sse') {
        res.writeHead(200, {
            "Content-Type": "text/event-stream",
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
        });
        res.write("data: " + 'connected' + "\\n\\n");

        // 服务器定期向客户端主动发消息
        interval = setInterval(function () { 
            res.write("data: " + 'hello world' + "\\n\\n");
        }, 5000);

        // req.on('close', () => {}) // 客户端断开连接时触发

        return
    }

    // 其它任何链接都返回网页
    res.statusCode = 200
    res.end(`
    <script>
        var es = new EventSource("/sse");
        es.onmessage = (e) => { console.log(e); }
        es.onopen = (e) => { console.log(e); }
        es.onerror = (e) => { console.log(e); }
    </script>
    `)
})

server.listen(80, '127.0.0.1', () => {
    console.log(`Server running at http://127.0.0.1/`)
})


上面例子中服务器在收到EventSource的连接请求后返回了 200 状态码,并在响应头中加入了 text/event-stream ,这些内容发送给客户端后连接就算建立完成了。之后只要有需要时服务器再向客户端发送消息即可,上面例子中使用定时器来模拟发送消息。

Server-sent events中服务器发送的数据必须是 UTF-8 编码的文本,还有一定的格式要求:

  • 数据必须是一段一段发送,每端之间必须有空行
    比如上面例子中的 \\n\\n ,后一个换行符就提供 了空行
  • 数据前有特定的标识字段
    比如上面例子中发送消息使用 data: 数据文本

上面提到的标识字段可选值如下:

  • data:
  • event:
    定义事件类型,未设置此项的情况下发送数据会触发 message 事件,即默认的 onmessage 方法;
  • id:
    标识当前这段信息用的编号,客户端可以通过 lastEventId 属性来获取,如果连接断开,客户端在建立重连的时候会在请求头中的 Last-Event-ID 字段中填写最近的 id ;
  • retry:
    指定浏览器重新发起连接的时间间隔;
  • :
    会被忽略的信息,网络中的服务器可能会关闭长时间(比如15秒)未传输数据的连接,可以使用该方式发送消息来保持连接;

针对上面的一些内容可以使用下面方式进行测试:

const http = require('http')

const server = http.createServer((req, res) => {

     // 访问链接 /sse
    if (req.url == '/sse') {
    	console.log(req.headers);
        res.writeHead(200, {
            "Content-Type": "text/event-stream",
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
        });
        res.write("retry: 10000\\n");
        res.write("event: connected\\n");
        res.write("data: connected\\n\\n");


        // 服务器定期向客户端主动发消息
        interval = setInterval(function () { 
            res.write("data: " + "hello world" + "\\n");
            res.write("id: " + "aaa" + "\\n\\n");

            res.write("event: " + "naisu" + "\\n");
            res.write("data: " + "233~~~" + "\\n\\n");
        }, 5000);

        return
    }

    // 其它任何链接都返回网页
    res.statusCode = 200
    res.end(`
    <script>
        var es = new EventSource("/sse");
        es.onmessage = (e) => { console.log(e); } // 使用data取数据
        es.addEventListener("connected", (e) => { console.log(e) }, false); // 监听自定义的事件
        es.addEventListener("naisu", (e) => { console.log(e) }, false); // 监听自定义的事件
    </script>
    `)
})

server.listen(80, '127.0.0.1', () => {
    console.log(`Server running at http://127.0.0.1/`)
})

注意事项

Server-sent events 虽然简单,但使用时还有一些事项需要注意:

  • 浏览器连接数量限制
    大多数浏览器对于同一个IP或域名都有连接数量限制(比如chrome上限是6个),对于普通的HTTP请求,连接一下又断开了这没什么问题,但 Server-sent events 是长连接,超过6个就无法再连接了;
  • 长时间未传输数据连接被关闭
    真实环境中数据传输过程中可能会经过代理服务器等网络节点,这些节点可能会关闭长时间未传输数据的连接;
  • 数据不实时传输
    真实环境中数据传输过程中可能会经过代理服务器等网络节点,这些节点可能会缓存数据,达到一定数量后才转发,可以关闭相关缓存以提高实时性;

总结

Server-sent events的使用总的来说挺简单的,更多内容可以参考下面链接:
https://html.spec.whatwg.org/multipage/server-sent-events.html
http://www.ruanyifeng.com/blog/2017/05/server-sent_events.html

以上是关于Server-sent events(SSE)& EventSource 客户端使用与服务器基础实现(基于Node.js)的主要内容,如果未能解决你的问题,请参考以下文章

Server-Sent Events 服务端发送事件

Long-Polling, Websockets, SSE(Server-Sent Event) 之间有什么区别?

WebFlux系列 Server-Sent Events

server-sent event后台用.net怎样实现

Server-sent events(SSE)& EventSource 客户端使用与服务器基础实现(基于Node.js)

Web Worker 和 Server-Sent Events