Server-Sent Events(SSE)简单实现实时通信


字数:1.5k 阅读时长:7分钟 阅读:85

Server-Sent EventsSSE)是一种基于HTTP的实时通信协议,它允许服务器向客户端推送信息。相比于传统的轮询方式,SSE 提供了更加轻量级和实时的通信机制。在本文中,我们将深入浅出地介绍如何简单实现 Server-Sent Events,以便在你的应用程序中实现实时通信。

SSE 实时通信

一、什么是 Server-Sent Events?

Server-Sent Events 允许服务器单向的向客户端发送事件流。这些事件流以纯文本形式传输,使用简单的文本格式,如data: message\n\n。客户端通过监听这些事件流,可以在接收到新事件时执行相应的操作。SSE 的一大优势在于它建立在标准的 HTTP 协议之上,不需要额外的握手和连接管理。

SSE 特点

  1. 简单性:SSE构建在HTTP协议之上,这意味着您无需引入额外的库或协议就可以开始使用。对于那些希望保持代码库干净并避免复杂性的开发人员来说,这是一个巨大的优势。
  2. 单向通信:SSE是一种单向通信协议,意味着数据只能从服务器流向客户端。这种单向性适用于各种情况,例如实时新闻更新、股票价格变动等,这些场景中,只有服务器需要推送数据。
  3. 自动重连:SSE连接在意外断开时会自动尝试重新连接。这种机制确保了在网络故障或连接中断后能够及时恢复通信,为用户提供连续的数据流。
  4. 事件流:SSE使用”事件流”(event stream)将数据从服务器发送到客户端。每个事件都可以包含一个事件标识符、事件类型和数据字段。客户端可以根据这些信息来解析和处理接收到的数据。

二、实现 Server-Sent Events

在线体验:https://next-blog.tiven.cn/sse

要实现 Server-Sent Events,我们需要创建一个 HTTP 服务器,负责向客户端推送事件流。以下是一个使用 Node.js 和 Next.js 框架的简单示例:

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
36
37
// pages/api/sse.js

import { nanoid } from 'nanoid'

export default function GET(req, res) {
res.writeHead(200, {
'Content-Type': 'text/event-stream; charset=utf-8',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
})
// res.status(200).json({ text: 'Hello' })
res.write(
`data: ${JSON.stringify({
id: 1,
message: 'Hello, world!',
timestamp: new Date().toISOString(),
})}\n\nid: 1\n\n`
)
res.write(`event: update\ndata: bye-bye\n\nid: ${nanoid(6)}\n\n`)
res.write('event: hi\ndata: hi\n\nid: 100\n\n')
let n = 0
let t = setInterval(() => {
n++
if (n > 5) {
clearInterval(t)
res.end('event: end\ndata: 结束通信。\n\nid: 9999\n\n')
} else {
let id = nanoid(6)
let data = JSON.stringify({
id,
message: `hi,这是第 ${n} 条消息。`,
timestamp: new Date().toISOString(),
})
res.write(`data: ${data}\n\nid: ${id}\n\n`)
}
}, 1000)
}

在这个示例中,我们创建了一个 /events 的路由,该路由返回 SSE 格式的数据。客户端可以通过访问 /events 路径来获取实时的事件流。服务器每隔一秒向客户端发送当前时间,你可以根据实际需求修改发送的数据。

三、在客户端使用 Server-Sent Events

客户端可以使用 JavaScript 中的 EventSource 对象来监听服务器发送的事件。以下是一个简单的 HTML 示例:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// pages/sse/index.jsx

import { useReactive } from 'ahooks'
import { Button, List, Space, Typography } from 'antd'
import { useRef } from 'react'

export const config = {
maxDuration: 30,
}

export default function Page() {
const sse = useRef()
const that = useReactive({
list: [],
})

function connect() {
init()
}

function close() {
sse.current?.close()
}

function init() {
sse.current = new EventSource('/api/sse', {withCredentials: true})

// 连接一旦建立,就会触发open事件,可以在onopen属性定义回调函数。
sse.current.onopen = function (event) {
// console.log(event)
// console.log(sse.current.CONNECTING)
// console.log(sse.current.OPEN)
// console.log(event.lastEventId)
}

// 客户端收到服务器发来的数据,就会触发message事件,
// 可以在onmessage属性的回调函数。
sse.current.onmessage = function (event) {
// console.log('onmessage:')
let {lastEventId, data, type} = event
console.log({lastEventId, data, type})
that.list.push({lastEventId, data})
}

sse.current.addEventListener('update', function (event) {
// console.log('onupdate:')
let {lastEventId, data, type} = event
console.log({lastEventId, data, type})
})

sse.current.addEventListener('hi', function (event) {
// console.log('onhi:')
let {lastEventId, data, type} = event
console.log({lastEventId, data, type})
})

sse.current.addEventListener('end', function (event) {
// console.log('onhi:')
let {lastEventId, data, type} = event
console.log({lastEventId, data, type})
close()
})

// 如果发生通信错误(比如连接中断),就会触发error事件,
// 可以在onerror属性定义回调函数。
sse.current.onerror = function (event) {
// handle error event
}
}

function clear() {
that.list = []
}

return (
<div className="w-100% flex flex-col justify-center items-center">
<Space size="middle" style={{display: 'flex'}}>
<Button onClick={close} danger>
断开连接
</Button>
<Button onClick={connect} type="primary">
建立连接
</Button>
</Space>
<br/>
<List
header={
<div className="flex justify-between">
通信结果
<Button size="small" onClick={clear}>
清空
</Button>
</div>
}
footer={null}
bordered
style={{width: '100%'}}
dataSource={that.list}
renderItem={({lastEventId, data}) => (
<List.Item>
<Typography.Text mark>[{lastEventId || 0}]</Typography.Text> {data}
</List.Item>
)}
/>
</div>
)
}

在这个示例中,我们使用 EventSource 对象连接到 /events 路径,然后监听 message 事件来处理服务器发送的数据。当连接关闭时,我们还监听了 error 事件以处理连接关闭的情况。
通过这样的简单实现,你可以在应用程序中实现实时通信,为用户提供更加即时和动态的体验。 Server-Sent Events 是一个强大而简单的工具,适用于多种场景,如实时聊天、通知推送等。

参考文档:


欢迎访问:天问博客

本文作者: Tiven
发布时间: 2024-01-18
最后更新: 2024-01-23
本文标题: Server-Sent Events(SSE)简单实现实时通信
本文链接: https://www.tiven.cn/p/68de9f15/
版权声明: 本作品采用 CC BY-NC-SA 4.0 许可协议进行许可。转载请注明出处!
欢迎留言,提问 ^_^
个人邮箱: tw.email@qq.com
notification icon
博客有更新,将会发送通知给您!