百度上有很多关于 SSE 的概念描述,详细了解可以自行查阅。
简单总结 SSE是基于 HTTP 协议的服务端推送技术,其中并不是传统意义上的 HTTP 请求 / 响应模型,SSE 可保持一次请求长久连接,但也不是 WebSocket 双工通信模型;SSE 只是支持服务端主动推送到客户端,可以理解成 单项长连接;
应用场景:GPT 问答后会逐步给出回复,下面会通过 node + react 简单模拟一下,先看效果;
SSE特点总结
- 基于 HTTP 协议
- 单工长连接
- 内置断线重连机制
- 连接数量 HTTP1.1 / 6个 HTTP2 / 可协商(默认100)
代码实践
node端基于KOA2
const router = require('koa-router')()
const { PassThrough } = require("stream");
router.get('/', async (ctx, next) => {
/**配置 HTTP 相关请求方式 */
ctx.request.socket.setTimeout(0);
ctx.req.socket.setNoDelay(true);
ctx.req.socket.setKeepAlive(true);
/**设置 SSE 请求头 */
ctx.set({
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
});
const stream = new PassThrough();
ctx.status = 200;
ctx.body = stream;
const content = "简单总结 SSE是基于 HTTP 协议的服务端推送技术,其中并不是传统意义上的 HTTP 请求 / 响应模型,SSE 可保持一次请求长久连接,但也不是 WebSockets 双工通信模型;SSE 只是支持服务端主动推送到客户端,可以理解成 单项长连接;";
// 将内容分割成多个事件消息
const messages = content.split(''); // 每个字符作为一个消息
const sendMessage = () => {
if (messages.length > 0) {
const message = messages.shift(); // 获取并移除第一个消息
stream.write(`data: ${message}\n\n`); // 发送消息,包括换行符
setTimeout(sendMessage, 100); // 延迟后发送下一个消息
} else {
stream.end(); // 没有更多消息时结束流
}
};
sendMessage(); // 开始发送消息
stream.on("close", () => {
console.log("已关闭");
});
});
在上面代码中
ctx.request.socket.setTimeout(0);
设置请求的超时时间 ,0 表示不会超时
ctx.req.socket.setNoDelay(true);
禁用 Nagle 算法,TCP 默认会合并小的数据包,以减少空间中数据包的数量,但SSE的特点就是只要有数据会要快速返回,因此禁用此算法;
ctx.req.socket.setKeepAlive(true);
启用 TCP 的 keep-alive 机制。当 TCP 连接的一方检测到长时间没有活动时,可以发送一个 keep-alive 探测包给对方,以确认连接是否仍然有效;
客户端基于react
import React, { useState } from "react"
import styled, { keyframes } from "styled-components"
const blinkAnimation = keyframes`
0% {
opacity: 1;
}
100% {
opacity: 0;
}
`;
const CursorContent = styled.span`
display: inline-block;
line-height: 20px;
width: 2px;
height: 20px;
background: #000;
animation: ${blinkAnimation} 0.5s linear infinite;
`
const TextContainer = styled.div`
`
const Home:React.FC<any> = ()=>{
const handleClick = ()=>{
const eventSource = new EventSource('/api/') /*此处需要配置react反向代理*/
// 监听消息事件
eventSource.onmessage = (event:any) => {
// const result = JSON.parse(event)
console.log(event);
setContent((state)=>{
return state+event.data
})
}
eventSource.onopen = () => {
console.log("SSE 连接成功,状态${eventSource.readyState}<br />")
}
eventSource.onerror = () => {
console.log("SSE 连接错误,状态${eventSource.readyState}<br />")
eventSource.close();
}
}
const [content,setContent] = useState("")
return (
<div>
<button onClick={()=>handleClick()}>什么是SSE</button>
<TextContainer>
{content}
<CursorContent></CursorContent>
</TextContainer>
</div>
)
}
export default Home