SSE请求多种实现方式总结

SSE请求多种实现方式总结


文前推荐一下👉
前端必备工具推荐网站(图床、API和ChatAI、智能AI简历、AI思维导图神器等实用工具):

站点入口:http://luckycola.com.cn/


什么是SSE

SSE(Server-Sent Events)是一种用于实现服务器主动向客户端推送数据的技术,也被称为"事件流"(Event Stream)。它基于 HTTP 协议,利用了其长连接特性,在客户端与服务器之间建立一条持久化连接,并通过这条连接实现服务器向客户端的实时数据推送。

SSE 和 Socket 区别

SSE(Server-Sent Events)和 WebSocket 都是实现服务器向客户端实时推送数据的技术,但它们在某些方面还是有一定的区别。

适用于场景

chatGPT 返回的数据 就是使用的SSE 技术

实时数据大屏 如果只是需要展示 实时的数据可以使用SSE技术 而不是非要使用webSocket


一、怎么实现SSE请求(基础版本)

1、前端实现:

EventSource 对象是 HTML5 新增的一个客户端 API,用于通过服务器推送实时更新的数据和通知。在使用 EventSource 对象时,如果服务器没有正确地设置响应头信息(如:Content-Type: text/event-stream),可能会导致 EventSource 对象无法接收到服务器发送的数据。

前端示例代码

js 复制代码
const sse = new EventSource('http://localhost:3000/api/sse' )

sse.addEventListener('open', (e) => {
    console.log(e.target)
})
//对应后端nodejs自定义的事件名lol
sse.addEventListener('lol', (e) => {
    console.log(e.data)
})

2、 nodejs 后端示例代码

js 复制代码
import express from 'express';
const app = express();
app.get('/api/sse', (req, res) => {
    res.writeHead(200, {
        'Content-Type': 'text/event-stream', //核心返回数据流
        'Connection': 'close'
    })
    const data = fs.readFileSync('./index.txt', 'utf8')
    const total = data.length;
    let current = 0;
    //mock sse 数据
    let time = setInterval(() => {
        console.log(current, total)
        if (current >= total) {
            console.log('end')
            clearInterval(time)
            return
        }
        //返回自定义事件名
        res.write(`event:lol\n`)
        /返回数据
        res.write(`data:${data.split('')[current]}\n\n`)
        current++
    }, 300)
})
app.listen(3000, () => {
    console.log('Listening on port 3000');
});

上面./index.txt的内容就是一些随机的测试文本,如下

text 复制代码
悲索之人烈焰加身,堕落者 不可饶恕,我即是引路的灯塔,也是净化的清泉。
永恒燃烧的羽翼,带我脱离凡间的沉沦,圣火将你洗涤。
今由烈火审判,于光明中得救。
利刃在手,制裁八方!

3、特点

这是一个最基础的实现版本,但是存在一个问题:这种sse的实现方式只能是GET请求,所以对参数传递的长度会有严重的限制

比如在AI聊天场景这种方式就不太适合,其实我们也可以通过浏览器Fetch API实现SSE

二、Fetch API实现SSE(升级版本)

fetch 本身不直接支持流式输出,但你可以使用 ReadableStream 和 TextDecoder 等 Web Streams API 来实现类似的效果。

1、 node后端代码

代码如下(示例):

js 复制代码
router.get("/sse", (req: Request, res: Response)=>{
    console.log('/sse')
    res.writeHead(200, {
        'Content-Type': 'text/event-stream', //核心返回数据流
        'Connection': 'close'
    })
    const data = fs.readFileSync(path.join(__dirname, '../../source/index.txt'), 'utf8');
    const total = data.trim().length;
    let current = 0;
    //mock sse 数据
    const time = setInterval(() => {
        console.log(current, total)
        if (current >= total) {
            console.log('end')
            res.write(`event:end\n`)
            res.write(`data:\n\n`);
            clearInterval(time)
            return
        }
        //返回自定义事件名
        res.write(`event:lol\n`)
        // 返回数据
        res.write(`data:${data.split('')[current]}\n\n`);
        current++
    }, 50)
});

2、 前端Fecth请求实现

代码如下(示例):

js 复制代码
function streamOutput(msg) {
  // 发送 POST 请求
  fetch('/sse', {
    method:"POST",
    body:JSON.stringify({ "content": msg}),
    timeout: 0,
    dataType:"text/event-stream",
    headers:{
      "Content-Type":"application/json"
    },
  }).then(response => {
    // 检查响应是否成功
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    // 返回一个可读流
    return response.body;
  }).then(body => {
    disableLoading();
    const reader = body.getReader();
    // 读取数据流
    function read() {
      return reader.read().then(({ done, value }) => {
        // 检查是否读取完毕
        if (done) {
          console.log('已传输完毕');
          return;
        }
        // 处理每个数据块
        console.log('收到的数据:', value);
        
        // 继续读取下一个数据块
        read();
      });
    }
    // 开始读取数据流
    read();
  }).catch(error => {console.error('Fetch error:', error);});
}

3、特点

这种方式的优点很直接可以支持POST请求方式来实现SSE效果,而且请求参数长度可以得到很大的拓展,符合长文本输入的需求.另外Fetch是浏览器原生API支持度好,简单易用.

三、Fecth结合EventSource实现SSE(终极版本)

这种方式结合了两种实现方式,是不是很特别,他的实现类似Websoket,后端需要通过保存前端的EventSource 队列来管理,我们直接上代码

1、node后端代码示例

js 复制代码
const express = require('express');  
const router = express.Router();  
  
let clients = [];  
  
// 创建一个 SSE 端点  
router.get('/events', (req, res) => {  
    res.setHeader('Content-Type', 'text/event-stream');  
    res.setHeader('Cache-Control', 'no-cache');  
    res.setHeader('Connection', 'keep-alive');  
  
    // 发送初始消息  
    res.write('data: {"status": "connected"}\n\n');  
  
    // 将客户端添加到监听列表  
    clients.push(res);  
  
    req.on('close', () => {  
        // 客户端断开连接时从列表中移除  
        clients = clients.filter(client => client !== res);  
        console.log('Client disconnected');  
    });  
  
    // 保持连接打开  
    res.on('close', () => {  
        clients = clients.filter(client => client !== res);  
    });  
});  
  
// 生成 PPT 并发送更新到所有客户端  
router.post('/sse', async (req, res) => {  
    let markdownContent = `这是语段测试文本\n\n成东南侧不对称\n\n呢哈哈哈哈哈哈哈哈`;
    const slideContents = markdownContent.split('\n\n');  
  
    for (let slideContent of slideContents) {  
        const event = JSON.stringify({   
        	finish: false,
            content:  slideContent
        });  
  
        // 发送消息到所有连接的客户端  
        clients.forEach(client => {  
            if (client.writable) {  
                client.write(`data: ${event}\n\n`);  
            }  
        });  
    }  
  
    // 完成后可以发送一个完成消息,但通常 SSE 不需要这样做  
    // 因为客户端可以通过关闭连接来检测完成  
    let endEvent =  JSON.stringify({   
        	finish: true,
            content:  ''
        });  
    client.write(`data: ${endEvent}\n\n`);  
});  
  
module.exports = router;

2、前端代码示例

html 复制代码
    <script>  
        // SSE连接  
        var evtSource = new EventSource('/events');  
  
        evtSource.onmessage = function(e) {  
            console.log("EventSource收到信息:", e.data) 
        };  
  
        evtSource.onerror = function(e) {  
            console.error('EventSource failed:', e);  
            evtSource.close();  
        };  
  
        function sendMesssageFn() {  
    // 使用fetch API发送POST请求  
    fetch('/sse', {  
        method: 'POST', // 或者 'PUT'  
        headers: {  
            'Content-Type': 'application/json',  
        },  
        body: JSON.stringify(data), // 必须是JSON字符串  
    })  
    .then(response => {  
        if (!response.ok) {  
            throw new Error('Network response was not ok');  
        }  
        return response.text(); // 或者返回response.json()如果后端返回JSON  
    })  
    .then(text => {  
        // 这里假设后端返回纯文本消息  
    })  
    .catch(error => {  
        console.error('There was a problem with your fetch operation:', error);  
    });  
}

sendMesssageFn();
    </script> 

3、特点

这种方式虽然也是通过fecth进行信息请求通信,但是不同的是他的消息监听仍然是通过EventSource实现的,所以不需要通过getReader解析信息.

四、总结

SSE是一种单工的通信方式,实现方式十分多样,每一种实现都有各自的优点缺点,应该根据需求进行合理的选择.

相关推荐
活宝小娜11 分钟前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点14 分钟前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow15 分钟前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o16 分钟前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
Guofu_Liao19 分钟前
大语言模型---LoRA简介;LoRA的优势;LoRA训练步骤;总结
人工智能·语言模型·自然语言处理·矩阵·llama
开心工作室_kaic1 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
刚刚好ā1 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
沉默璇年3 小时前
react中useMemo的使用场景
前端·react.js·前端框架
yqcoder3 小时前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
2401_882727573 小时前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架