SSE、EventSource了解

EventSource接口是web内容和服务器发送事件通信的接口
一个EventSource实例会对HTTP服务器开启一个持久化的连接,以text/event-stream格式发送事件
该连接会一直保持开启直到调用EventSource.close()关闭

EventSource是一种Web API,用于建立和服务器之间的【单向持久化】连接
接受来自服务器的事件推送,常用于:实时应用,eg: 通知、聊天或动态数据更新
EventSource的工作原理是:客户端通过HTTP请求和服务器建立一个长连接,之后服务器推送数据,客户端接收到数据时会触发相应的事件
一个EventSource实例会对HTTP服务器开启一个持久化的连接,以text/event-stream格式发送事件
此连接会一直保持开启直到通过调用EventSource.close()关闭

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

来自服务端传入的消息会以事件的形式分发到代码中,若接收消息中存在一个event字段
触发的事件和event字段的值相同,若不存在event字段则将触发通用的message事件

SSE介绍

SSE的简单模型:一个客户端去从服务端订阅一条“流”,之后服务器端可以发送消息给客户端直到服务端或客户端关闭该“流”,eventsource也称之为“server-sent-event"

SSE数据帧的格式

event-source必须编码成utf8的格式,消息的每个字段都是用"\n"来做分割,下面4个规范定义好的字段:
1.Event: 事件类型
2.Data: 发送的数据
3.ID:每一条事件流的ID
4.Retry: 告知浏览器在所有的连接丢失之后重新开启新的连接等待的事件,在自动重连连接的过程中,之前收到的最后一个事件流ID会被发送到服务器

SSE通信过程

SSE通信过程,底层实现被浏览器给封装好包括数据的处理,流程如下:

示例请求:

携带的数据是JSON格式的,浏览器会整合成一个Object:
https://github.com/linxiaowu66/nodejs-rtc-demo/blob/master/es.js 【demo】

主要特性

  1. 单向通信
  • 客户端通过EventSource与服务器建立连接,但服务器只向客户端推送数据而客户端不能通过该连接发送数据

在SSE(Server-Sent Events)中,“单向”指的是从服务器到客户端的数据流而不是客户端不断请求数据
eg: 当存在新的消息、通知或动态数据时,服务器可主动推送到客户端不需要客户端不断发送请求(eg: 每隔一段时间轮询一次)
客户端发送数据的方式:客户端通过普通的HTTP请求(eg: POST、GET)来和服务器交互,但和SSE连接本身无关
客户端可向服务器发送数据,这个过程和SSE数据流(即从服务器推送到客户端的消息)是分开的

  1. 基于HTTP协议
  • 使用标准的HTTP协议进行通信,通常用于服务器推送(Server-Sent Events, 简称SSE)
  • 数据以文本形式发送,通常是UTF8编码
  1. 文本数据格式
  • 从服务器发送的消息格式是文本类型,通常使用【{'Content-Type': 'text/event-stream'}】内容类型,每条消息是由多行组成并以特定格式传输
  1. 自动重连
  • 若连接中断,EventSource会自动重新连接,重试机制可通过设置【reconnect】时间间隔来调整
  1. 事件监听
  • 客户端可通过监听不同的事件类型(eg: message、open、error)来处理接收到的数据
  • 客户端可根据事件类型选择性地处理消息
  1. 支持跨域
  • EventSource默认支持跨域,需要服务器在响应头中设置适当的CORS头(eg: Access-Control-Allow-Origin)

WebSocket VS SSE

  • SSE
    • 单向通信:服务器通过已建立的连接将数据推送给客户端
    • 客户端不能通过SSE向服务器发送数据;eg: 客户端需要发送数据,但需要通过常规的HTTP请求(eg: POST)
  • WebSocket
    • 双向通信:客户端和服务器都可以在任何时候通过WebSocket连接发送数据
    • 这种方式适用于更复杂的实时交互场景(eg: 多人在线聊天),双方都可以实时通信

EventSource特性

EventSource和WebSocket的区别:

EventSource 服务器发送事件是单向的
数据消息只能从服务端发送到客户端(eg: 用户浏览器)
适应场景:社交媒体状态更新、消息来源(news feed)或数据传递到客户端存储机制等

不使用HTTP/2时,服务器发送事件受到打开连接数的限制(相对于浏览器)
使用HTTP/2时,最大并发HTTP流的数量是服务器和客户端协商的(默认100)

在ChatGPT的场景中:客户端和服务器的交互是双向的
  1. 客户端发送请求:客户端输入问题或消息,发送一个HTTP请求到服务器
  2. 服务器响应:服务器处理请求并返回响应数据(eg:ChatGPT的回答)

使用SSE来推送实时通知(eg:接收到新的消息、更新模型状态等),服务器通过SSE向客户端推送这些信息
在聊天过程中,客户端仍然是通过传统的HTTP请求来向服务器发送消息

聊天和SSE的结合
  1. 实时通知(SSE)
  • 服务器可使用SSE向客户端推送新消息通知,告诉客户端存在新的消息等待查看
  1. 消息发送和接收(普通HTTP请求)
  • 用户输入的消息通过常规的HTTP请求(eg: POST/messages)发送到服务器,服务器处理后返回相应的聊天内容
    ** SSE是专门用于让服务器向客户端推送信息的技术,而客户端仍然可以通过传统的HTTP请求与服务器交互来发送数据**

ChatGPT中逐字显示的效果中关键技术就是流式的接口请求接口SSE
服务器发送事件(Server-Sent Events)就是一种单向通信协议,通常用于需求实时数据更新的应用场景
eg: 新闻更新、社交媒体通知、股票行情等

JavaScript中的SSE

EventSource接口是HTML5规范的一部分,允许Web页面通过标准HTTP连接接收服务器推送的数据

浏览器支持时间线:
  1. Firefox: 在Firefox4.0中首次支持EventSource,该版本2011年3月发布
  2. Chrome: 在Chrome9.0中添加对EventSource的支持,该版本2010年11月发布
  3. Safari: 在Safari6.0中添加对EventSource的支持,该版本2012年7月发布
  4. Internet Explorer: 直到Internet Explorer10.0,微软才对EventSource支持,该版本与2012年10月发布

EventSource的方法和属性

EventSource() 构造函数:创建一个新的EventSource,从指定的URL接受服务器发送事件
----------- 实例属性 ---------- 从父接口EventTarget继承属性
从其父接口EventTarget继承属性
EventSource.readyState: 一个代表连接状态的数字:值是CONNECTING(0)、OPEN(1) 或 CLOSED(2)
EventSource.url: 一个表示事件源的URL字符串
EventSource.withCredentials: 一个布尔值,表示EventSource对象是否使用跨资源共享(CORS)凭据来实例化
----------- 实例方法 ----------- 从父接口EventTarget继承方法
EventSource.close(): 关闭连接(若存在)将readyState属性设置为CLOSED,若连接已关闭则该方法不执行任何操作
-------------- 事件 -------------
error: 在事件源连接未能打开时触发
message: 在事件源连接收到数据时触发
open: 在和事件源的连接打开时触发 (事件源本身可发送具有event字段的消息,将创建一个以该值为key的特定事件
示例:创建一个EventSource来从服务器接受未命名的事件;
const eventSource = new EventSource("targetUrl"); // 创建一个EventSource实例并指明目标服务器URL以接受来自服务器的消息
const eventList = document.querySelect("ul");
eventSource.onmessage = (e) => {
const newElement = document.createElement("li")
newElement.textContent = `message: ${e.data}`
eventList.appendChild(newElement)
}

每个接受到的事件会导致EventSource对象的onmessage事件处理程序运行

监听具名事件,为每种类型的事件添加一个监听器

// 创建一个新的EventSource对象,指向服务器端的SSE接口
// 构造函数接收两个参数:url和configuration对象
// configuration对象只有一个选项:withCredentials 默认为false,指示CORS是否包含凭据(credentials)
const sse = new EventSource("/api/v1/sse");
/**
"0" - connecting
"1" - open
"2" - closed
*/
console.log(sse.readyState);
// 监听连接成功
sse.onopen = function(event) {
console.log("成功连接")
}
// 监听连接错误
sse.error = function(err) {
console.log('onerror', err)
}
// 监听服务端默认的消息事件
sse.onmessage = function(e) {
console.log('onmessage', e.data)
}
/**
仅仅监听下面的事件
event: notice
data: useful data
id: someid
*/
sse.addEventListener("notice", (e) => {
console.log(e.data)
})
/**
将监听具有字段 "event: update"的事件
sse.addEventListener("update", (e) => {
console.log(e.data)
})
/**
"message"事件是一个特例,可捕获没有event字段的事件
以及具有特定类型“event: message"的事件
不会触发任何其他类型的事件
*/
sse.addEventListener("message", (e) => {
console.log(e.data)
})
基础概念:server-send events(缩写SSE) EventSource基于http协议的单向通信

IE兼容性解决办法:npm install event-source-polyfill,可自动重新连接(WebSocket需要借助于第三方库eg: socket.io实现重连)

封装EventSource

class EventSource {
constructor(url, onmessage) {
this.eventSourceUrl = url
this.onmessage = onmessage
this.eventSource = null
this.initEventSource(url)
}
initEventSource(url) {
if('EventSource' in window) {
let _that = this
// 实例化EventSource
this.eventSource = new EventSource(_that.eventSourceUrl);
// EventSource打开
this.eventSource.onopen = function() {
console.log('EventSource连接成功', _that.eventSourceUrl);
}
// EventSource接收到新的消息
this.eventSource.onmessage = function(event) {
try {
if(event.data && typeof event.data === "string") {
let data = JSON.parse(JSON.stringify(event.data))
// 业务逻辑回调
if(typeof _that.onmessage === 'function') {
_that.onmessage(data)
}
}
} catch (error) {
console.log("EventSource初始化异常", error);
}
}
// EventSource关闭
this.eventSource.onclose = function() {
console.log('EventSource连接断开', _that.eventSourceUrl);
}
// EventSource错误
this.eventSource.onerror = function(error) {
console.log("EventSource连接错误", error)
this.close()
}
} else {
throw new Error('当前浏览器不支持EventSource对象')
}
}
// 关闭eventsource
close() {
this.eventSource.close()
this.eventSourceUrl = ""
this.eventSource = null
this.onmessage = null
}
}
export default EventSource

EventSource

EventSource是服务器推送的一个网络事件接口,一个EventSource实例会对HTTP服务开启一个持久化的连接
以text/event-stream格式发送事件,会一直保持开启直到被要求关闭
对比WebSocket是单向可重连的通信,对比WebSocket是简单方便地,在特定地场景下 eg: 聊天消息、市场价格

🚨注意🚨

EventSource每次收完消息,必须手动关闭evtSource.close(),才不会自动重新连接
自动重连是EventSource的特性之一,这个关闭的前提是服务器下发字段告诉客户端能够关闭才可以关闭

WebSocket VS EventSource

JS三种实时通信技术:Eventsource VS Websocket VS Socket.io

https://juejin.cn/post/7258554591838306341

eventsource

eventsource是HTML5 API:实现服务端向客户端的即时推送
通过eventsource,可建立一个持久连接从而实现服务器端的事件推送到客户端

eventsource的优点:

  1. 简单易用,和HTTP协议兼容
  2. 只需要一个长连接,服务器可推送任意数量的事件
  3. 适用于服务端向客户端发送频率较低的数据
  4. 可自动重连,并且在连接断开时会触发error和close事件,方便处理异常的情况
    eventsource的缺点:
  5. 不支持双向通信
  6. 不支持二进制数据传输
  7. 兼容性存在问题,不支持IE浏览器

eventsource在实际项目中存在的多种应用场景:

  1. 实时数据展示:eventsource可用于实时展示服务端的数据变化。eg: 一个监控系统可通过eventsource和服务端建立连接,服务器端检测到数据变化后,即时推送给客户端,客户端可实时展示最新的数据
  2. 实时聊天应用:eventsource可用于实现实时聊天功能。当存在新消息时,服务器可使用eventsource向客户端推送消息,客户端即时收到消息并进行展示。可实现类似weixin、qq等实时聊天的功能
  3. 实时博客评论:eventsource可用于发送实时通知提醒,eg: 一个新闻网站可使用eventsource向用户发送最新的新闻推送,用户无需手动刷新页面即可获取最新的新闻内容
  4. 实时博客评论:eventsource可用于实时更新博客评论,当有新的评论提交时,服务器可使用eventsource将新评论推送给其他正在浏览该博客用户,实现实时更新评论的效果。

使用EventSource实现ChatGPT的打字效果:

  1. 在服务器端,创建一个能够生成GPT文本的函数或API,这个函数可以接收到用户的输入,并根据输入生成相应的文本。
  2. 在前端页面中,使用JS创建一个新的EventSource对象,指定服务器端的URL eg:
var eventSource = new EventSource('/gpt');
  1. 在前端页面中,监听来自服务器端的message事件,该事件会在服务器端有新的数据发送时触发。在事件处理函数内部,将接收到的数据逐步添加到文本区域中,模拟打字效果 eg:
eventSource.addEventListener('message', function(event) {
var textArea = document.getElementById('output')
textArea.value += event.data; // 将接收到的数据逐步添加到文本区域中
})
  1. 在前端页面中,创建一个输入框或其他交互元素,用于用户输入。当用户输入完成后,将输入内容发送到服务器端。可通过监听输入框的键盘事件或点击按钮等方式实现。eg:
var inputElement = document.getElementById('input')
inputElement.addEventListener('keyup', function(event) {
if(event.key === 'Enter') {
var userInput = inputElement.value
// 发送用户输入给服务器端
var xhr = new XMLHttpRequest()
xhr.open('POST', '/gpt')
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send(JSON.stringify({ input: userInput }));
inputElement.value = '' // 清空输入框
}
})

当用户在前端页面中输入内容并发送给服务器端后,服务器端会生成相应的GPT文本,并使用EventSource将文本逐步发送给前端页面,实现打字效果的输出

eventsource适用于简单的推送场景,websocket适用于双向通信场景
socket.io是一个更高级的实时通信库

Websocket

Websocket和eventsource不同,websocket是一种全双工通信协议,它能够在浏览器和服务器之间建立双向通信的连接。相比于eventsource的单向通信,websocket可同时实现浏览器向服务器的推送以及服务器向浏览器的推送,实现真正的双向通信

Websocket具有极低的延迟,适用于实时游戏、聊天应用等场景,使用ws或wss协议,能够在浏览器和服务器之间建立长时间的连接。
通过websocket可以发送和接收消息,实时更新数据并处理各种事件

优点:

  1. 支持双向通信,客户端和服务端都可以发送和接收消息
  2. 可发送二进制数据,支持大文件传输
  3. 协议比较轻量级,能够节省网络带宽和服务器资源
  4. 兼容性较好,大部分现代浏览器都支持WebSocket

缺点:

  1. 需要在服务端实现WebSocket协议的支持
  2. 相对于HTTP请求来说,WebSocket连接需要占用更多的服务端资源
  3. 安全性问题:需要注意放置CSRF和XSS攻击,避免恶意用户利用WebSocket劫持会话或注入脚本等
const webSocket = new WebSocket('ws://localhost:8080')
webSocket.addEventListener('open', (event) => {
console.log('WebSocket connection established!')
})
webSocket.addEventListener('message', (event) => {
console.log('Received message data: ', event.data);
})
webSocket.addEventListener('close', (event) => {
console.log('WebSocket connection close !');
})
webSocket.addEventListener('error', (event) => {
console.error('An error occurred: ', event);
})
// 发送消息到服务器
webSocket.send('Hello, Server!');

Socket.io

Socket.io是一个基于websocket的实时通信库,提供更简单、更高级的API,使得实时通信变得更加容易上手
socket.io能够自动选择最佳的通信方式,若浏览器不支持websocket,会自动降级为使用轮询方式进行通信

Socket.io不仅仅提供基本的通信功能,还提供一些小的技巧来简化开发流程,eg: 支持房间(Room)概念
允许多个用户分组在同一个房间中,可通过向特定房间发送消息来实现群聊等功能

优点:

  • 支持双向通信
  • 支持广播和房间功能,使得开发者可轻松实现实时应用程序
  • 自带多种传输方式,eg: Websocket、HTTP长轮询、JSONP等,可根据浏览器或设备的不同渲染最佳传输方式

缺点:

  • 使用Socket.IO的应用程序需要使用Socket.IO作为通信层,不能在应用程序中集成原生WebSocekt或EventSource
  • 对比EventSource和WebSocket,Socket.IO相对来说更加庞大,需要引入相应的客户端库和服务器端差劲啊,若应用程序只需要简单的实时通信
    则使用EventSource或WebSocket更加适合
// 客户端
const socket = io("http://localhost:3000")
socket.on('connect', () => {
console.log('Socket.io connection established!');
})
socket.on('message', () => {
console.log('Received message data:', data);
})
socket.on('disconnect', () => {
console.log('Socket.io connection closed!');
})
socket.emit('message', 'Hello, server!');
// 服务端
const io = require('socket.io')(3000);
io.on('connection', (socket) => {
console.log('A new client is connected !);
socket.on('message', (data) => {
console.log('Received message data: ', data)
socket.emit('message', `You said: ${data}`);
})
socket.on('disconnection’,(socket) => {
console.log('Client disconnected!')
});
})
posted @   Felix_Openmind  阅读(301)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
历史上的今天:
2023-02-09 Array常见使用场景
2023-02-09 Flex弹性布局实现四列网格
2022-02-09 加载额外配置文件的方式
*{cursor: url(https://files-cdn.cnblogs.com/files/morango/fish-cursor.ico),auto;}
点击右上角即可分享
微信分享提示