文前推薦一下👉
前端必備工具推薦網站(圖牀、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 對象無法接收到服務器發送的數據。
### 前端示例代碼
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 後端示例代碼
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的內容就是一些隨機的測試文本,如下
悲索之人烈焰加身,墮落者 不可饒恕,我即是引路的燈塔,也是淨化的清泉。
永恆燃燒的羽翼,帶我脱離凡間的沉淪,聖火將你洗滌。
今由烈火審判,於光明中得救。
利刃在手,制裁八方!
3、特點
這是一個最基礎的實現版本,但是存在一個問題:這種sse的實現方式只能是GET請求,所以對參數傳遞的長度會有嚴重的限制
比如在AI聊天場景這種方式就不太適合,其實我們也可以通過瀏覽器Fetch API實現SSE
二、Fetch API實現SSE(升級版本)
fetch 本身不直接支持流式輸出,但你可以使用 ReadableStream 和 TextDecoder 等 Web Streams API 來實現類似的效果。
1、 node後端代碼
代碼如下(示例):
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請求實現
代碼如下(示例):
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後端代碼示例
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、前端代碼示例
<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是一種單工的通信方式,實現方式十分多樣,每一種實現都有各自的優點缺點,應該根據需求進行合理的選擇.