web 通讯技术:短轮询、长轮询与 SSE

长轮询和短轮询

长轮询和短轮询是两种不同的轮询机制,都是用于从服务器定期获取数据。

短轮询是最简单的轮询方式,客户端在固定的时间间隔发送请求到服务器,不管服务器是否有新的数据更新。

javascript 复制代码
import axios from 'axios';

function shortPolling() {
  setInterval(() => {
    axios.get('/api/data')
      .then(response => {
        console.log('短轮询数据:', response.data);
        // 处理数据
      })
      .catch(error => {
        console.error('短轮询错误:', error);
      });
  }, 5000); // 每5秒请求一次
}

shortPolling();

而长轮询是一种更高效的轮询方式,客户端发送请求到服务器后,服务器会保持这个请求开启,直到有新的数据可以发送给客户端,或者达到某个超时时间后,才会响应请求。

javascript 复制代码
import axios from 'axios';

function longPolling() {
  function poll() {
    axios.get('/api/data', {
      timeout: 60000 // 设置长时间的超时时间
    })
      .then(response => {
        console.log('长轮询数据:', response.data);
        // 处理数据
        poll(); // 数据处理完毕后,再次发起长轮询请求
      })
      .catch(error => {
        if (axios.isCancel(error)) {
          console.log('长轮询被取消:', error.message);
        } else {
          console.error('长轮询错误:', error);
        }
        setTimeout(poll, 5000); // 发生错误后,等待5秒再次发起请求
      });
  }

  poll();
}

longPolling();

从上面例子看出:

  • 短轮询适合实时性要求不高、服务器负载需要控制在较低水平的场景。
  • 长轮询适合需要较高实时性、减少请求次数的场景,

但是这两者都不如 Websocket 实时性和效率好。

WebSocket 和 SSE 通信过程

WebSocket 双向通信,客户端和服务器可以在任何时候互相发送数据。

如果想更轻量级,而且只需要服务端单向往客户端推送消息,我们可以使用 Server Send Event(SSE),类似私信、股票行情,订阅新闻就很适用 SSE。

WebSocket 的通信过程是这样的:

  • 客户端发起握手:客户端通过发送一个 HTTP 请求到服务器来初始化一个 WebSocket 连接。这个请求被称为握手请求,它包含了一个特殊的 Upgrade 头,这表明客户端想要切换协议从 HTTP 到 WebSocket。
  • 服务器响应握手:如果服务器支持 WebSocket,它会发送一个 101 状态码的 HTTP 响应,其中包含 Upgrade: websocket 和 Connection: Upgrade 头,表示同意协议切换。

双方握手完成后就可以传输文本数据和二进制数据了。

任何一方都可以发起关闭握手(close handshake),这是通过发送一个特殊的控制帧来完成的,该控制帧包含了关闭连接的代码和原因。

而基于 HTTP 协议的 Server Send Event 通信过程:

  1. 客户端请求:客户端(通常是一个浏览器)发起一个对服务器的普通 HTTP GET 请求。这个请求的头部会包含一个特殊的 Accept 字段,值为 text/event-stream,这表明客户端希望建立一个 SSE 连接。
  2. 服务器响应:服务器识别这个请求,并保持这个连接打开,而不是像通常的 HTTP 请求那样返回数据后关闭连接。服务器设置响应的 Content-Type 为 text/event-stream,并开始发送数据,可以多次发送数据。
  3. 发送消息:服务器按照 SSE 的格式发送消息,每条消息通常包含一个事件类型(event)、数据(data)和一个可选的 id(id)。消息以两个换行符 \n\n 结尾。例如
kotlin 复制代码
data: 第一条消息内容\n\n

或者,如果一条消息包含多行数据,它会这样发送:

arduino 复制代码
data: first line\n
data: second line\n\n

如果指定了事件类型和 id,它们将作为消息的一部分被发送:

makefile 复制代码
id: 1
event: myMessage
data: 第二条消息内容\n\n

4.客户端处理:客户端监听这个流,并在收到新消息时触发事件。在 JavaScript 中,这是通过创建一个 EventSource 对象并监听它的 onmessage 事件来实现的。如果服务器指定了事件类型,例如上面指定了 myMessage,客户端需要监听 myMessage 事件类型以收到数据。

5.保持连接:如果连接意外断开,客户端会自动尝试重新连接到服务器。如果服务器提供了消息的 id,客户端会在重新连接时发送一个 Last-Event-ID 头部,包含上次接收到的消息 id,这样服务器可以从断点继续发送。而 WebSocket 如果断开之后是需要手动重连的。

6.关闭连接:客户端可以通过调用 EventSource 对象的 close() 方法来关闭连接。服务器也可以通过发送特定的关闭消息来关闭连接。

CICD 平台的日志是实时打印的,ChatGPT 一段段加载回答,其实都是基于 SSE。

注意:SSE 通常传输的数据是文本格式,具体来说是使用 UTF-8 编码的文本数据。如果使用 SSE 传输大量二进制则需要编解码处理,不推荐。

Nest 实现 SSE 接口

我们实现一下,创建 nest 项目:

bash 复制代码
npx nest new sse-project

运行:

bash 复制代码
npm run start:dev

在 AppController 添加一个 stream 接口:

使用 @Sse() 装饰器来标记为 SSE 端点。

返回的是一个 Observable 对象,然后内部用 observer.next 返回消息。

sse1 我们先返回了 'hello',三秒后返回了 'world'。

我们支持下跨域:

React 接收 SSE 接口数据

写一个前端页面,创建 react 项目:

bash 复制代码
npx create-react-app --template=typescript sse-project-frontend

在 App.tsx 里写如下代码:

通过 new EventSource 这个原生 API,监听上面的 onmessage 回调函数,获取 sse 接口的响应。

将渲染 App 外层的严格模式注释,它会导致多余的渲染。

执行 npm run start。

因为 3000 端口被 nest 应用占用了,react 应用跑在 3001 端口。

点击 event1 按钮:

控制台先打印 'hello',三秒后打印 'world',我们可以取里面的 data 属性拿到最终数据:

点击 even2 按钮:

控制台不断打印:

表明我们不断收到服务端推送的数据。

响应的 Content-Type 是 text/event-stream:

然后在 EventStream 可以看到每次收到的消息:

SSE 日志实时推送

tail -f 命令可以实时看到文件的最新内容:

我们可以通过 child_process 模块的 exec 来执行这个命令,监听 log 文件改动,返回给客户端改动内容:

./log 指的是当前工作目录下名为 log 的文件。在这里,. 表示当前工作目录。

可以输入 node 然后再输入 process.cwd() 来查看当前的工作目录。

前端连接这个新接口:

输入 111 保存,再输入 222 保存:

控制台打印两条信息:

浏览器收到了实时的日志,可以对 data 属性值进行 JSON.parse()

很多构建日志都是通过 SSE 的方式实时推送的。

相关推荐
麻花20135 分钟前
WPF学习之路,控件的只读、是否可以、是否可见属性控制
服务器·前端·学习
.5486 分钟前
提取双栏pdf的文字时 输出文件顺序混乱
前端·pdf
jyl_sh14 分钟前
WebKit(适用2024年11月份版本)
前端·浏览器·客户端·webkit
zhanghaisong_20151 小时前
Caused by: org.attoparser.ParseException:
前端·javascript·html·thymeleaf
Eric_见嘉1 小时前
真的能无限试(白)用(嫖)cursor 吗?
前端·visual studio code
DK七七1 小时前
多端校园圈子论坛小程序,多个学校同时代理,校园小程序分展示后台管理源码
开发语言·前端·微信小程序·小程序·php
老赵的博客2 小时前
QSS 设置bug
前端·bug·音视频
Chikaoya2 小时前
项目中用户数据获取遇到bug
前端·typescript·vue·bug
南城夏季2 小时前
蓝领招聘二期笔记
前端·javascript·笔记
Huazie2 小时前
来花个几分钟,轻松掌握 Hexo Diversity 主题配置内容
前端·javascript·hexo