WebSocketStream:将流与 WebSocket API 集成

通过应用背压,防止您的应用在 WebSocket 消息中被淹没或让 WebSocket 服务器被淹没。

背景

WebSocket API

WebSocket API 提供了支持 WebSocket 协议的 JavaScript 接口, 因此可打开一个双向交互式通信会话 用户浏览器与服务器之间的通信。 借助此 API,您可以向服务器发送消息并接收事件驱动型响应 而无需轮询服务器进行回复。

Streams API

Streams API 允许 JavaScript 以编程方式访问通过网络接收的数据块流 并根据需要进行处理 关于数据流的一个重要概念是 backpressure 方法。 这是单个流或管道链 调节读取或写入速度。 数据流本身或管道链中后面的数据流仍处于忙碌状态 并且尚未准备好接受更多分块, 它会通过链向后发送信号,以酌情减慢投放速度。

当前 WebSocket API 存在的问题

无法对收到的消息应用背压

使用当前的 WebSocket API 时,对消息的响应发生在 WebSocket.onmessage、 从服务器收到消息时调用 EventHandler

假设您有一个应用需要执行密集的数据处理操作 。 您可以设置类似于以下代码的流程, 由于您对 process() 调用的结果执行了 await 操作,因此应该没有问题,对吧?

// A heavy data crunching operation.
const process = async (data) => {
  return new Promise((resolve) => {
    window.setTimeout(() => {
      console.log('WebSocket message processed:', data);
      return resolve('done');
    }, 1000);
  });
};

webSocket.onmessage = async (event) => {
  const data = event.data;
  // Await the result of the processing step in the message handler.
  await process(data);
};

答错了!当前 WebSocket API 存在的问题是无法应用背压。 当消息到达速度快于 process() 方法处理速度时, 渲染进程会通过缓冲这些消息来填满内存, 由于 100% CPU 使用率或同时出现这两种情况而无响应。

对发送的邮件应用背压不符合人体工程学

可以对已发送的消息应用背压,但需要轮询 WebSocket.bufferedAmount 这既效率低下,又不符合人体工程学。 此只读属性返回已排队的数据字节数 调用 WebSocket.send()、 但尚未传输到网络。 所有已加入队列的数据发送完毕后,此值就会重置为零。 但如果你一直调用 WebSocket.send(), 将继续攀升

什么是 WebSocketStream API?

WebSocketStream API 可处理不存在或不符合人体工程学的背压问题 来实现。 这意味着可以“免费”应用背压,无需任何额外费用。

WebSocketStream API 的推荐用例

可以使用此 API 的网站示例包括:

  • 需要保持交互性的高带宽 WebSocket 应用, 尤其是视频和屏幕共享
  • 同样,视频拍摄和其他在浏览器中生成大量数据的应用 需要上传到服务器的数据 借助背压,客户端可以停止生成数据,而不是在内存中累积数据。

当前状态

步骤 状态
1. 创建铺垫消息 完成
2. 创建规范的初始草稿 进行中
3. 收集反馈和对设计进行迭代 进行中
4. 源试用 完成
5. 发布 尚未开始

如何使用 WebSocketStream API

入门示例

WebSocketStream API 基于 promise,因此处理起来感觉很自然 新时代的 JavaScript 领域 首先,构造一个新的 WebSocketStream 并向其传递 WebSocket 服务器的网址。 接下来,等待连接变为 opened, 这会导致 ReadableStream 和/或 WritableStream

通过调用 ReadableStream.getReader() 方法,您最终会获得一个 ReadableStreamDefaultReader, 然后,您可以read() 直到数据流完成,即直到它返回以下形式的对象, {value: undefined, done: true}

因此,通过调用 WritableStream.getWriter() 方法,您最终会获得一个 WritableStreamDefaultWriter, 然后,您可以write() 数据。

  const wss = new WebSocketStream(WSS_URL);
  const {readable, writable} = await wss.opened;
  const reader = readable.getReader();
  const writer = writable.getWriter();

  while (true) {
    const {value, done} = await reader.read();
    if (done) {
      break;
    }
    const result = await process(value);
    await writer.write(result);
  }

背压

承诺的背压功能怎么样? 如前所述,您可以“免费”获取,无需执行额外的步骤。 如果 process() 需要额外的时间,则只有在流水线准备就绪后,才会使用下一条消息。 同样,WritableStreamDefaultWriter.write() 步骤 只有在确保安全的情况下才会继续运行。

高级示例

WebSocketStream 的第二个参数是一个允许将来扩展的选项包。 目前唯一的选项是 protocols。 其行为方式与 WebSocket 构造函数的第二个参数

const chatWSS = new WebSocketStream(CHAT_URL, {protocols: ['chat', 'chatv2']});
const {protocol} = await chatWSS.opened;

所选 protocol 及可能的 extensions 都是字典的一部分 通过 WebSocketStream.opened promise 获取。 有关实时连接的所有信息都由此 promise 提供, 因为它在连接失败时无关。

const {readable, writable, protocol, extensions} = await chatWSS.opened;

有关关闭的 WebSocketStream 连接的信息

可从 WebSocket.oncloseWebSocket.onerror 个活动 现可通过 WebSocketStream.closed promise 获取。 如果发生异常关闭,promise 将拒绝, 否则,将解析为服务器发送的代码和原因。

下文介绍了所有可能的状态代码及其含义, CloseEvent 状态代码的列表

const {code, reason} = await chatWSS.closed;

关闭 WebSocketStream 连接

可以使用 AbortController。 因此,请将 AbortSignal 传递给 WebSocketStream 构造函数。

const controller = new AbortController();
const wss = new WebSocketStream(URL, {signal: controller.signal});
setTimeout(() => controller.abort(), 1000);

作为替代方案,您还可以使用 WebSocketStream.close() 方法, 但其主要目的是允许指定 代码 以及原因。

wss.close({code: 4000, reason: 'Game over'});

渐进增强和互操作性

Chrome 是目前唯一实现 WebSocketStream API 的浏览器。 为了实现与传统 WebSocket API 的互操作性 无法对收到的消息应用背压。 可以对已发送的消息应用背压,但需要轮询 WebSocket.bufferedAmount 这既效率低下,又不符合人体工程学。

功能检测

如需检查 WebSocketStream API 是否受支持,请使用以下命令:

if ('WebSocketStream' in window) {
  // `WebSocketStream` is supported!
}

演示

在支持的浏览器中,您可以看到 WebSocketStream API 在嵌入式 iframe、 或直接在 Glitch 上

反馈

Chrome 团队希望了解您使用 WebSocketStream API 的体验。

向我们介绍 API 设计

API 是否有什么无法按预期运行? 或者,您是否缺少实现自己的想法所需的方法或属性? 对安全模型有疑问或意见? 在相应的 GitHub 代码库上提交规范问题, 或添加您对现有问题的看法。

报告实现存在的问题

您在 Chrome 的实现过程中是否发现了错误? 或者,实现是否与规范不同? 在 new.crbug.com 上提交 bug。 请务必提供尽可能多的细节信息,包括重现问题的简单说明, 然后在组件框中输入 Blink>Network>WebSocketsGlitch 非常适合用于分享快速简便的重现案例。

表示对 API 的支持

您打算使用 WebSocketStream API 吗? 您的公开支持可帮助 Chrome 团队确定各项功能的优先顺序 并向其他浏览器供应商展示支持它们的重要性。

使用 # 标签向 @ChromiumDev 发送推文 #WebSocketStream 并告诉我们您使用它的地点和方式。

实用链接

致谢

WebSocketStream API 由 Adam Rice 实施, 平野由高。 主打图片,作者:Daan Mooij 不启动