SSE技术和WebSocket技术实现即时通讯

文章目录

    • 一、SSE
      • 1.1 什么是SSE
      • 1.2 工作原理
      • 1.3 特点和适用场景
      • 1.4 API用法
      • 1.5 代码实现
    • 二、WebSocket
      • 2.1 什么是WebSocket
      • 2.2 工作原理
      • 2.3 特点和适用场景
      • 2.4 API用法
      • 2.5 代码实现
    • 三、SSE与WebSocket的比较

当涉及到实现实时通信的Web应用程序时,两种常见的技术选择是服务器发送事件(Server-Sent Events,SSE)和WebSocket,本文将详细讲讲这两种技术,并比较它们的异同点。

一、SSE

1.1 什么是SSE

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

1.2 工作原理

它的工作原理如下:

  1. 建立连接:客户端通过发送HTTP请求与服务器建立连接。在请求中,客户端指定了接收事件的终点(Endpoint)。
  2. 保持连接:服务器接收到连接请求后,保持连接打开,并定期发送事件数据给客户端。
  3. 事件流:服务器使用 “Content-Type: text/event-stream” 头部响应标识SSE连接,并使用特定格式的数据(事件流)发送给客户端。
  4. 客户端处理事件:客户端通过JavaScript的 EventSource 接口监听SSE连接,一旦接收到事件,就可以处理数据并更新页面。

1.3 特点和适用场景

SSE的特点和适用场景:

  • 单向通信:SSE是从服务器到客户端的单向通信模型,只能由服务器推送数据给客户端。
  • 实时更新:SSE适用于需要实时更新数据的应用场景,如股票行情、新闻推送等。
  • 简单易用:使用SSE相对简单,无需额外的库或框架支持,可以直接使用浏览器的原生API进行开发。

当下火热的ChatGPT实现对话消息的流式返回就是基于服务器发送事件SSE技术来实现的

1.4 API用法

EventSource 对象是 HTML5 新增的一个客户端 API,用于通过服务器推送实时更新的数据和通知。在使用 EventSource 对象时,可以通过以下方法进行配置和操作:
1.EventSource() 构造函数
EventSource的构造函数接收一个 URL 参数,通过该 URL 可以建立起与服务器的连接,并开始接收服务器发送的数据【服务器和客户端建立持久性连接的关键】。

const eventSource = new EventSource(url, options);
  • url:String 类型,表示与服务器建立连接的 URL。必填
  • options:Object 类型,表示可选参数。常用的可选参数包括:
  1. withCredentials:Boolean 类型,表示是否允许发送 Cookie 和 HTTP 认证信息。默认为 false。
  2. headers:Object 类型,表示要发送的请求头信息。
  3. retryInterval:Number 类型,表示与服务器失去连接后,重新连接的时间间隔。默认为 1000 毫秒。

2.EventSource.onmessage 事件
onmessage监听服务器发送的数据,当接收到数据时,就触发该事件,可以用EventSource的实例对象的监听事件函数来代替使用。
如下:

const sse = new EventSource('http://localhost:3000/api/sse' )
// 第一个参数对应后端nodejs自定义的事件名,默认事件名是message
sse.addEventListener('message', (e) => {
    console.log(e.data)
})

3. EventSource.onopen 事件
onopen 事件表示 EventSource 对象已经和服务器建立了连接,并开始接收来自服务器的数据。当 EventSource 对象建立连接时,触发该事件。
4.EventSource.close() 方法
close() 方法用于关闭 EventSource 对象与服务器的连接,停止接收服务器发送的数据。
5.EventSource.readyState 属性
readyState 属性表示当前 EventSource 对象的状态,它是一个只读属性,它的值有以下几个:

  • CONNECTING:表示正在和服务器建立连接。
  • OPEN:表示已经建立连接,正在接收服务器发送的数据。
  • CLOSED:表示连接已经被关闭,无法再接收服务器发送的数据。
    示例:
if (eventSource.readyState === EventSource.CONNECTING) {
  console.log('正在连接服务器...');
} else if (eventSource.readyState === EventSource.OPEN) {
  console.log('已经连接上服务器!');
} else if (eventSource.readyState === EventSource.CLOSED) {
  console.log('连接已经关闭。');
}

1.5 代码实现

下面我们通过代码来体会一下SSE技术,以下是一段文本,我们基于SSE技术实现:node后端读取文本,前端流式展示文本内容。

谁让你读了这么多书,又知道了双水村以外还有个大世界……
如果从小你就在这个天地里日出而作,日落而息,那你现在就会和众乡亲抱同一理想:
经过几年的辛劳,像大哥一样娶个满意的媳妇,生个胖儿子,加上你的体魄,会成为一名出色的庄稼人。
不幸的是,你知道的太多了,思考的太多了,因此才有了这种不能为周围人所理解的苦恼。

——《平凡的世界》

node后端index.js

const express = require('express')
const fs = require('fs')
const app = express()

app.get('/api/sse', (req, res) => {
    // 设置请求的客户端的响应标头,参数1:必选,三位数的http状态码,参数2:标头对象
    res.writeHead(200, {
        'Content-Type': 'text/event-stream',  // SSE(事件流)核心代码,表示使用SSE
        'Access-Control-Allow-Origin': '*'  // 解决跨域,* 这种方式不安全,仅用于测试
    })
    const data = fs.readFileSync('./index.txt', 'utf8')  // 异步utf8格式读取文件,得到的是字符串
    const arr = data.split('')  // 将字符串分割成数组
    let current = 0
    // mock SSE 数据
    let timer = setInterval(() => { // 定时器实现持久化返回数据
        if (current >= arr.length) {
            clearInterval(timer)
            return
        } else {
            // 返回自定义事件名 默认是message
            res.write(`id:${current}\n`)
            res.write(`event:lol\n`) // 定义发送事件名
            // 向请求的客户端发送响应内容 我的理解就是可以让客户端执行一段js代码
            res.write(`data:${arr[current++]}\n\n`)
        }
    }, 300);
})

app.listen(3000, () => {
    console.log('server is running');
})

使用node ./index.js运行node服务。

以上代码在node后端实现了localhost:3000的服务器的/api/sse接口通过Content-Type:text/event-stream头部标识SSE连接,并将文本内容以300ms返回一个字符的速度发送给客户端。

值得注意的是:

res.writeHead()方法实现:设置请求的客户端的响应标头,和res.writeHead()实现相同功能,但wirteHead()可以一次设置多个响应标头。

res.write()方法实现:向请求的客户端发送响应内容。在res.end()之前可以多次被执行调用,传入模板字符串时,我的理解是可以让客户端执行一段js代码。

注意看上述代码中的res.write片段:

// 返回自定义事件名 默认是message
res.write(`id:${current}\n`)
res.write(`event:lol\n`)
// 向请求的客户端发送响应内容 我的理解就是可以让客户端执行一段js代码
res.write(`data:${arr[current++]}\n\n`)

由于使用响应请求头Content-Type:text/event-stream开启事件流EventStream,因此可以看到浏览器请求SSE的请求时会有下图的表格,上述代码的id,event,data分别对应下图表格的前三列内容,\n则表示自动跳转下一个表格单元,因此执行第三个res.write()时最后跟两个\n,第一个用于跳转时间列的单元格,第二个用于跳转下一行第一个单元格。

在这里插入图片描述
下面我们再看看前端代码:index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SSE流式发送</title>
</head>

<body>
    <div id="data"></div>
    <script>
        document.addEventListener('keydown', (e) => {
        	// 按下回车键触发sse接口
            if (e.keyCode == 13) {
                const sse = new EventSource('http://localhost:3000/api/sse')
                sse.addEventListener('open', (e) => {
                    console.log(e.target);
                })
                // 接收到服务器发送的数据的监听方式1:
                // 后端nodejs自定义了事件名就要把message改成自定义的事件名
                sse.addEventListener('lol', (e) => {
                    document.getElementById('data').innerHTML += e.data
                    console.log(e);
                })
                // 监听方式2
                // sse.onmessage = (e) => {
                //     document.getElementById('data').innerHTML += e.data
                //     console.log(e);
                // }
                sse.onerror = (e) => {
                    console.log(e);
                }
            }
        })

    </script>
</body>

</html>

以上代码实现了按下回车键后,客户端发送http请求与服务器建立连接,并实例化EventSource对象与服务器保持持久连接后监听服务器返回数据。

使用LiverServer运行html文件。

最终的实现效果如下:
在这里插入图片描述

二、WebSocket

2.1 什么是WebSocket

WebSocket 是一种在单个 TCP 连接上进行全双工通信的网络协议。它是 HTML5 中的一种新特性,能够实现 Web 应用程序和服务器之间的实时通信,比如在线聊天、游戏、数据可视化等。

相较于 HTTP 协议的请求-响应模式,使用 WebSocket 可以建立持久连接,允许服务器主动向客户端推送数据,避免了不必要的轮询请求,提高了实时性和效率。同时,WebSocket 的连接过程也比较简单,可以通过 JavaScript 中的 WebSocket API 进行创建和管理,并且可以和现有的 Web 技术如 HTML、CSS 和 JavaScript 无缝集成。

WebSocket 协议是基于握手协议(Handshake Protocol)的,它在建立连接时使用 HTTP/HTTPS 发送一个初始握手请求,然后服务器响应该请求,建立连接后就可以在连接上进行数据传输了。

总之,WebSocket 提供了一种快速、可靠且灵活的方式,使 Web 应用程序能够实现实时通信,同时也提高了网络性能和用户体验。

为什么要用使用WebSocket?

因为http 通信只能由客户端发起,服务器返回查询结果,HTTP 协议做不到服务器主动向客户端推送信息。服务器有连续的状态变化,客户端要获知就非常麻烦。

我们只能使用轮询:每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。

轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开);

而WebSocket能做到服务器和客户端相互推送信息。

在这里插入图片描述

2.2 工作原理

  1. 握手阶段:客户端向服务器发送WebSocket握手请求,服务器返回握手响应。在这个阶段,客户端和服务器协商选择协议和版本。
  2. 建立连接:握手成功后,客户端和服务器之间建立持久连接,可以进行双向数据传输。
  3. 双向通信:一旦连接建立,客户端和服务器都可以主动发送消息给对方。数据可以以文本或二进制格式进行传输。
  4. 断开连接:当任一方决定关闭连接时,可以发送关闭帧来终止连接。

2.3 特点和适用场景

  • 双向通信:WebSocket支持双向通信,客户端和服务器可以互相发送消息。
  • 实时互动:WebSocket适用于实时互动的应用场景,如聊天应用、协作编辑等。
  • 复杂性和灵活性:相对于SSE,WebSocket更为灵活,可以处理更复杂的通信需求。它允许自定义消息格式、心跳检测、连接状态管理等。

2.4 API用法

前端的WebSocket对象提供了用于创建和管理WebSocket 连接,以及可以通过该连接发送和接收数据的 API。
使用示例:

const ws = new WebSocket('ws://localhost:8080')

node后端不再使用Express建立服务,而是安装ws库,创建socket服务。

下面我们直接看代码

2.5 代码实现

后端创建socket服务:ws.js

// 要先安装ws和它的声明文件@types/ws
const ws = require('ws')
// 创建 socket 服务 8080端口
const wss = new ws.Server({ port: 8080 }, () => {
  console.log("socket服务启动成功8080");
});
// 监听客户端的连接
wss.on('connection', (socket) => {
    // 监听客户端的消息
    console.log('客户端连接成功');
    // 监听客户端发送过来的消息
    socket.on('message', (e) => {
        console.log(e.toString());
        // 单独给发送消息的客户端发送消息
        socket.send(e.toString())
        // 给所有客户端群发消息
        wss.clients.forEach((client) => {
            client.send('群发消息:' + e.toString())
        })
    })
})

以上代码实现了创建8080端口的socket服务,并监听客户端发送过来的消息,并分别将收到的消息发送给一个客户端以及所有客户端。

前端ws.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket</title>
</head>
<body>
    <div>
        <input type="text" id="input">
        <button id="send">发送</button>
    </div>
    <div id="txt">服务端发给发给客户端的消息:</div></div>
    <script>
        // 建立8080端口的WebSocket连接
        // webSocket的协议要是用ws://或者wss:// 就跟http://和https://一样
        const ws = new WebSocket('ws://localhost:8080')
        // 监听open 表示连接成功
        ws.addEventListener('open', function(event){
            console.log('连接成功');
        })
        let input = document.querySelector('#input')
        let btn = document.querySelector('#send')
        btn.addEventListener('click', function () {
            // 发送消息 前后端都使用send发送消息
            if(input.value) {
                // 给服务器端发送消息
                ws.send(input.value)
                input.value = ''
            }
        })
        // 监听服务器端发送过来的消息
        ws.addEventListener('message', (e) => {
            // 渲染消息到页面
            document.querySelector('#txt').innerHTML += e.data
        })
    </script>
</body>
</html>

以上代码实现了建立8080端口的WebSocket连接,并实现点击btn将输入框的内容发送给服务端,最后监听服务端返回的消息实现渲染到页面上。

效果展示:
后端运行socket服务,前端使用LiveServer(会自动开一个5500的端口)打开html文件,可以看到前后端WebSocket连接成功。
在这里插入图片描述

这里我们开启两个客户端,分别验证服务端单独发送给客户端和群发消息的效果:
在这里插入图片描述

在左边的客户端的输入框输入你好后点击发送,可以得到如下结果

在这里插入图片描述
可以看到左右两边都收到了服务端群发的消息,左边的客户端比右边的客户端多了一条它发给客户端的消息。这表明WebSocket成功实现了服务端和客户端之间的相互通信,并且服务端可以给客户端群发消息。

三、SSE与WebSocket的比较

  1. 通信模型:SSE是单向通信模型,只能由服务器向客户端推送数据,而WebSocket是双向通信模型,客户端和服务器可以互相发送消息。

  2. 连接性:SSE使用长轮询或HTTP流技术,而WebSocket使用持久连接。SSE需要频繁地发起HTTP请求来获取数据【是这样的吗??】,而WebSocket只需在握手阶段建立一次连接,然后保持连接打开。另外WebSocket没有同源限制,客户端可以与任意服务器通信。

  3. 实时性:WebSocket提供了更低的延迟和更高的实时性,因为它支持双向通信,可以立即将数据推送给客户端。SSE虽然也可以实现实时性,但由于其单向通信模型,需要服务器定期发送数据

  4. 浏览器支持:WebSocket在现代浏览器中得到广泛支持,包括Chrome、Firefox、Safari等。SSE在大多数现代浏览器中也有支持,但在某些旧版本浏览器中可能存在兼容性问题。

  5. API复杂性:WebSocket提供了更灵活和复杂的API,可以处理更高级的通信需求。SSE相对简单,使用浏览器的原生 EventSource 接口即可。

选择SSE还是WebSocket取决于您的应用需求。如果您只需要服务器向客户端推送数据,并且实时性要求不高,SSE是一个简单可行的选择。如果您需要双向通信,实时性要求高,或需要处理复杂的通信需求,WebSocket可能更适合您的应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/62665.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网络安全【黑客技术】自学

1.网络安全是什么 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 2.网络安全市场 一、是市场需求量高&#xff1b; 二、则是发展相对成…

每天五分钟机器学习:梯度下降算法和正规方程的比较

本文重点 梯度下降算法和正规方程是两种常用的机器学习算法,用于求解线性回归问题。它们各自有一些优点和缺点,下面将分别对它们进行详细的讨论。 区别 1. 梯度下降算法是一种迭代的优化算法,通过不断迭代调整参数来逼近最优解。它的基本思想是根据目标函数的梯度方向,沿…

openGauss学习笔记-32 openGauss 高级数据管理-批处理模式

文章目录 openGauss学习笔记-32 openGauss 高级数据管理-批处理模式32.1 语法格式32.2 参数说明32.3 示例 openGauss学习笔记-32 openGauss 高级数据管理-批处理模式 openGauss支持从文本文件执行SQL语句。openGauss提供了gsql工具实现SQL语句的批量处理。 以下场景建议使用批…

测试人员简单使用Jenkins

一、测试人员使用jenkins干什么&#xff1f; 部署测试环境 二、相关配置说明 一般由开发人员进行具体配置 1.Repository URL&#xff1a;填写git地址 2.填写开发分支&#xff0c;测试人员可通过相应分支进行测试环境的构建部署 当多个版本并行时&#xff0c;开发人员可以通过…

【各个突破】Echart的象柱形图数值为0时,图像发生严重偏移,一招即可解决

【各个突破】Echart的象柱形图数值为0时&#xff0c;图像发生严重偏移&#xff0c;一招即可解决 1&#xff0c;问题描述2&#xff0c;解决方法3&#xff0c;最终结果 1&#xff0c;问题描述 当数值是0亩时&#xff0c;圆形图标发生位置偏移&#xff0c;据悉&#xff0c;该bug是…

掌握 JVM 调优命令

点击下方关注我&#xff0c;然后右上角点击...“设为星标”&#xff0c;就能第一时间收到更新推送啦~~~ JVM 日常调优总结起来就是&#xff1a;首先通过 jps 命令查看当前进程&#xff0c;然后根据 pid 通过 jinfo 命令查看和修改 jvm 参数&#xff0c;通过 jstat 命令查看 cla…

漫画 | TCP/IP之大明邮差

后记&#xff1a; 1973年&#xff0c;卡恩与瑟夫开发出了网络中最核心的两个协议&#xff1a;TCP协议和IP协议&#xff0c;随后为了验证两个协议的可用性&#xff0c;他们做了一个实验&#xff0c;在多个异构网络中进行数据传输&#xff0c;数据包在经过近10万公里的旅程后到达…

git删除已经提交的大文件

当你不小心把一个巨大的二进制文件提交到git仓库的时候&#xff0c;此时删除再提交也没有用了&#xff0c;大文件已经在仓库中留底了。另外比如需要删除某个需要保密的文件&#xff0c;都是相同的解决办法。 我本来想着把dll放在三方库里面提交到仓库里&#xff0c;省得在不同…

STM32 低功耗-待机模式

STM32 待机模式 文章目录 STM32 待机模式第1章 低功耗模式简介第2章 待机模式简介2.1 进入待机模式2.1 退出待机模式 第3章 待机模式代码部分总结 第1章 低功耗模式简介 在 STM32 的正常工作中&#xff0c;具有四种工作模式&#xff1a;运行、睡眠、停止和待机模式。 在系统或…

【九】mybatis 缓存模块设计

mybatis 缓存模块设计 简介&#xff1a;MyBatis提供了一级缓存和二级缓存&#xff0c;其中一级缓存基于SqlSession实现&#xff0c;而二级缓存基于Mapper实现。这里我们就来学习一下MyBatis缓存的使用&#xff0c;并分析MyBatis缓存的实现原理。 首先我们找到缓存模块的源码&a…

EVE-NG MPLS L2VPN static lsp

目录 1 拓扑 2 配置步骤 2.1 配置接口IP 和路由协议 2.2 配置MPLS LDP 2.3 配置L2VPN PW 2.4 验证L2VPN 1 拓扑 2 配置步骤 2.1 配置接口IP 和路由协议 PE1 interface LoopBack 0ip address 1.1.1.9 32 quitinterface GigabitEthernet1/0ip address 10.1.1.1 255.255…

【腾讯云 Cloud Studio 实战训练营】基于Cloud Studio构建React完成点餐H5页面

前言 【腾讯云 Cloud Studio 实战训练营】基于Cloud Studio 构建React完成点餐H5页面一、Cloud Studio介绍1.1 Cloud Studio 是什么1.2 相关链接1.3 登录注册 二、实战练习2.1 初始化工作空间2.2 开发一个简版的点餐系统页面1. 安装 antd-mobile2. 安装 less 和 less-loader3. …

网络安全之原型链污染

目录&#xff1a; 目录&#xff1a; 一、概念 二、举例 三、 实操了解 总结 四、抛出原题&#xff0c;历年原题复现 第一题&#xff1a; 五、分析与原理 第二题&#xff1a; 八、分析与原理 九、具体操作&#xff0c;payload与结果 结果&#xff1a; 一、概念 Java…

C++ 派生类的拷贝构造函数

当存在类的继承关系时&#xff0c;对于一个类&#xff0c;如果程序员没有编写拷贝构造函数&#xff0c;编译系统会在必要时自动生成一个隐含的拷贝构造函数&#xff0c;这个隐含的拷贝构造函数会自动调用基类的拷贝构造函数&#xff0c;然后对派生类新增的成员对象一一执行拷贝…

H5中的draggable

基本语法及事件 draggable 属性规定元素是否可拖动。必须设置&#xff0c;否则没有拖拽效果及事件触发 提示&#xff1a; 链接和图像默认是可拖动的。 提示&#xff1a; draggable 属性经常用于拖放操作 语法 <element draggable"true|false|auto"> 值描…

在服务器上搭建gitlab

目录 1.在服务器上下载gitlab 2.编辑站点位置 3.重载配置 4.访问gitlab 最终效果展示&#xff1a; 官方文档&#xff1a; 安装部署GitLab服务 1.在服务器上下载gitlab wget https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/gitlab-ce-12.9.0-ce.0.el7.x86_64.r…

Wavefront .OBJ文件格式解读【3D】

OBJ&#xff08;或 .OBJ&#xff09;是一种几何定义文件格式&#xff0c;最初由 Wavefront Technologies 为其高级可视化器动画包开发。 该文件格式是开放的&#xff0c;已被其他 3D 图形应用程序供应商采用。 OBJ 文件格式是一种简单的数据格式&#xff0c;仅表示 3D 几何体&…

Linux文本处理工具和正则表达式

Linux文本处理工具和正则表达式 一.查看、截取和修改文本的工具 1.查看文本的工具 cat 最常用的文件查看命令&#xff1b;当不指明文件或者文件名为一杠’-时&#xff0c;读取标准输入。 cat [OPTION]... [FILE]... -A&#xff1a;显示所有控制符(tab键:^I;行结束符:$) -…

01Mysql创建表

目录 一、题目要求 二、具体操作代码 三、查看结果&#xff1a; 一、题目要求 建立一张表&#xff1a; 表里面有多个字段&#xff0c;每一个字段对应一种数据类型 注意&#xff1a;表名&#xff0c;字段名都要起的有意义 二、具体操作代码 CREATE TABLE DataInfo (id INT …

Vercel 部署的项目发现APIkeys过期了怎么办

好不容易部署的Vercel&#xff0c;发现APIkeys过期了显示&#xff0c;查了查资料发现只要更新下新的apikeys&#xff0c;然后再重新部署下就好了。 重新设置APIkeys 1.1. 进去 Vercel 项目内部控制台&#xff0c;点击顶部的 Settings 按钮&#xff1b; 1.2 点击环境变量Enviorn…