一条正在向外淌水的消防水管。

WebSocketStream:对数据流集成 WebSocket API

通过应用背压,避免应用被 WebSocket 消息淹没或者使用消息淹没 WebSocket 服务器。

Published on Updated on

Translated to: English, Português, 한국어, Pусский, 日本語

背景

WebSocket API

WebSocket APIWebSocket 协议提供了一个 JavaScript 接口,从而可以实现用户浏览器和服务器之间的双向交互通信会话。通过此 API,您可以向服务器发送消息并接收事件驱动的响应,无需为了获取回复轮询服务器。

Streams API

Streams API 允许 JavaScript 以编程方式访问通过网络接收的数据块流,并根据需要处理它们。数据流上下文中的一个重要概念是背压。背压是指单个数据流或管道链调节读取或写入速度的过程。当数据流本身或管道链中较晚的数据流仍然在忙、且尚未准备好接受更多数据块时,它会通过管道链反向发送信号,来适当地减慢交付速度。

当前 WebSocket API 存在的问题

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

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

假设您有一个应用需要在收到新消息时执行繁重的数据处理操作。您可能会设置类似于下面代码的流程,并且因为您要await(等待)process()调用的结果,结果应该让人满意,对吧?

// 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 通过将数据流与 WebSocket API 集成,来处理不存在或不符合人机工程学的背压问题。这意味着可以“免费”应用背压,无需任何额外负担。

WebSocketStream API 的建议用例

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

  • 需要保持交互性的高带宽 WebSocket 应用程序,特别是视频和屏幕共享应用。
  • 同样,在浏览器中生成大量需要上传到服务器数据的视频捕捉等应用程序。通过背压,客户端可以停止生成数据,而不是在内存中积累数据。

当前状态

步骤状态
1. 创建解释文档已完成
2. 创建规范初稿进行中
3. 收集反馈并对设计进行迭代进行中
4. 极早期试验已完成
5. 发布未开始

如何使用 WebSocketStream API

介绍性示例

WebSocketStream API 是基于 Promise 的,这样在现代 JavaScript 世界中处理它就变得很自然。首先构造一个新的WebSocketStream并将 WebSocket 服务器的 URL 传递给它。接下来,您等待connection,这会产生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.connection;
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.connection;

选定的protocol以及潜在的extensionsWebSocketStream.connection promise 中可用的字典的一部分。此 promise 会提供有关实时连接的所有信息,因为如果连接失败则则无关紧要。

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

有关关闭的 WebSocketStream 连接的信息

之前可从 WebSocket API 的WebSocket.oncloseWebSocket.onerror事件获得的信息现在可通过WebSocketStream.closed promise 获得。如果出现不洁关闭,promise 将拒绝,否则它将解析为服务器发送的代码和原因。

CloseEvent状态代码列表中解释了所有可能的状态代码及其含义。

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

关闭 WebSocketStream 连接

可以使用AbortController关闭 WebSocketStream。因此,将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!
}

演示

在支持的浏览器上,您可以在嵌入式 iframe 中或直接在 Glitch 上查看 WebSocketStream API 的运行情况。

反馈意见

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

告诉我们您对 API 设计的看法

API 是否存在无法按预期工作的情况?或者是否缺少实现您的想法所需的方法或属性?对安全模型有疑问或评论?请在相应 GitHub repo 上提交规范问题,或将您的想法添加到现有问题中。

报告实现问题

您是否发现 Chrome d 实现错误?或者实现与规范不同?请前往 new.crbug.com 提交错误。请务必提供尽可能多的细节、简单的重现说明,并进入 Components 框中的Blink>Network>WebSocketsGlitch 非常适合共享快速简便的重现案例。

展示您对 API 的支持

您是否打算使用 WebSocketStream API?您的公开支持有助于 Chrome 团队确定功能的优先级,并向其他浏览器供应商展示支持这些功能的重要性。

请使用标签 #WebSocketStream 向 @ChromiumDev 发送推文,与我们分享您的使用方式和地点。

实用链接

鸣谢

WebSocketStream API 由 Adam RiceYutaka Hirano 实现。主页横幅由 Daan MooijUnsplash上提供。

Updated on 改进文章

This site uses cookies to deliver and enhance the quality of its services and to analyze traffic. If you agree, cookies are also used to serve advertising and to personalize the content and advertisements that you see. Learn more about our use of cookies.