使用sse技术实现长链接(一)

本篇主要介绍使用sse创建长连接来实现服务器推送功能。

本文讲解服务端实现思路。

基础

sse全称Server Sent Events,特点在于建立更长时间的连接以等待后续数据的传输。

ssewebsocket的最大区别在于,websocket是全双工的,服务端和客户端可以基于一个连接互相发送消息,sse的客户端只能基于这个连接接收消息。

前提

服务器向浏览器发送的数据,必须是 UTF-8 编码的文本,具有如下的 HTTP 头信息:

1
2
3
Content-Type: text/event-stream # 必须,声明流式信息
Cache-Control: no-cache
Connection: keep-alive

数据包

每个数据包可以包含若干条数据,数据之间通过\n\n分割。每个数据可以分为多行,每行有固定的格式,如下。

1
[field]: value\n

其中,field可选值有四种:id / event / data / entry
id - 表示数据标识符,相当于每一条数据的编号,可以通过Last-Event-ID请求头来建立晚上断线重连机制。
event - 表示事件类型,默认为message,服务端也可以进行自定义。
entry - 服务器可以用这个字段,指定浏览器重新发起连接的时间间隔。
data - 数据传输主体,一条数据可以有多个data,每个data\n结尾。

特殊数据包(注释)

field为空的数据行,视为注释
为了确保连接状态,有时会发送一个注释行

最小 Koa 示例

下面是一个最小可读的 Koa SSE 路由示例,演示基本发送流程与心跳(示例尽量短):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 简单示例:koa-sse-server.js
const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) => {
if (ctx.path !== '/sse') return next();
ctx.set({
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
});
ctx.status = 200;

// 每秒发送一个字符(如 "hello")
const text = 'hello';
let idx = 0;
const sendChar = () => {
if (idx < text.length) {
ctx.res.write(`data: ${text[idx]}\n\n`);
idx++;
}
};
sendChar();
const charTimer = setInterval(sendChar, 1000);

// 心跳,防止连接被代理/浏览器关闭
const h = setInterval(() => ctx.res.write(': heartbeat\n\n'), 20000);

ctx.req.on('close', () => {
clearInterval(h);
clearInterval(charTimer);
});
ctx.respond = false; // 关闭自动响应
});
app.listen(3000);

小结与要点

  • 必须设置 Content-Type: text/event-stream,并尽量禁用代理缓冲(在代理配置里)。
  • 如果需要保证不丢失重要消息,应在服务端保存消息历史并实现基于 Last-Event-ID 的补发逻辑(本篇只说明思路,示例简短)。
  • 在生产环境下注意连接数量与内存占用,必要时通过负载均衡或将推送任务下沉到专门的消息服务。

系列导航

参考链接