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 的方式实时推送的。

相关推荐
一颗花生米。2 小时前
深入理解JavaScript 的原型继承
java·开发语言·javascript·原型模式
学习使我快乐012 小时前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio19952 小时前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
勿语&3 小时前
Element-UI Plus 暗黑主题切换及自定义主题色
开发语言·javascript·ui
黄尚圈圈3 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水4 小时前
简洁之道 - React Hook Form
前端
正小安6 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
小飞猪Jay8 小时前
C++面试速通宝典——13
jvm·c++·面试
_.Switch8 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光8 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js