SSE:用于流式传输的协议
一.什么是SSE
SSE协议是一种基于http协议的单向通信协议,服务端可以向客户端发送数据,但是客户端不能向服务器发送数据。客户端通过创建一个到服务器的单向连接来监听事件。可以将一次性返回数据包改为流式返回数据。SSE协议支持断线重连,也支持自定义响应事件。比如ChatGpt使用的通信方式就是SSE协议,相比于websocket通信这是一个更为轻量级的通信方式,使用方法简单。但是在浏览器原生的EventSource不支持设置请求头,需要借助第三方包去实现,同时也需要后端设置接口的响应头Content-Type:text/event-stream
二.SSE和WebSocket的区别
WebSocket API
WebSocket是基于TCP协议的一种用于应用层的网络协议,它实现了浏览器与服务器之间的全双工通信,它允许服务器主动发信息给客户端。所以,浏览器和服务器只需要完成一次握手就可以建立持久性的连接,并且能够实现双向数据传输。
特点:
1.传输的数据格式可以是文本也可以是二进制形式
2.不受同源策略的限制,可以与任意服务端进行通信
3.兼容HTTP协议,默认端口同样是80(ws)和443(ws)
4.客户端和服务端通信时开销较少,与HTTP协议不同,不需要每次都携带完整的头部信息
5.若在通信过程中连接中断,需要自己实现断线重连
区别:
1.sse协议仅支持服务端向客户端发送数据,而websocket支持双向通信,服务端和客户端之间可以互相通信
2.sse是一种轻量级的通信协议,而websocket整体的一些方法事件较为复杂
3.sse支持断线重连机制,而websocket需要自己实现断线重连
4.sse是基于HTTP协议的通信协议,而websocket是基于TCP协议的网络层通信协议
三.前端使用SSE
<h1>fetchSSE Demo</h1>
<button onclick="connectFetch()">建立 fetchSSE 连接</button>
<button onclick="closeSSE()">断开 fetchSSE 连接</button>
<br />
<br />
<div id="message"></div>
<script>
const messageElement = document.getElementById('message')
let controller = null
// 建立 FETCH-SSE 连接
const connectFetch = () => {
controller = new AbortController()
fetchEventSource('http://127.0.0.1:3001/fetch-sse', {
method: 'POST',
body: JSON.stringify({
content: 'xxx'
}),
signal: controller.signal,
onopen: () => {
messageElement.innerHTML += `FETCH 连接成功<br />`
},
onclose: () => {
messageElement.innerHTML += `FETCH 连接关闭<br />`
},
onmessage: (event) => {
const data = JSON.parse(event)
messageElement.innerHTML += `${data.id} --- ${data.time} --- body参数:${JSON.stringify(data.body)}` + '<br />'
},
onerror: (e) => {
console.log(e)
}
})
}
// 断开 FETCH-SSE 连接
const closeSSE = () => {
if (controller) {
controller.abort()
controller = undefined
messageElement.innerHTML += `FETCH 连接关闭<br />`
}
}
const fetchEventSource = (url, options) => {
fetch(url, options)
.then(response => {
if (response.status === 200) {
options.onopen && options.onopen()
return response.body
}
})
.then(rb => {
const reader = rb.getReader()
const push = () => {
// done 为数据流是否接收完成,boolean
// value 为返回数据,Uint8Array
return reader.read().then(({ done, value }) => {
if (done) {
options.onclose && options.onclose()
return
}
options.onmessage && options.onmessage(new TextDecoder().decode(value))
// 持续读取流信息
return push()
})
}
// 开始读取流信息
return push()
})
.catch((e) => {
options.error && options.error(e)
})
}