WebSocketStream: tích hợp các luồng với API WebSocket

Ngăn ứng dụng của bạn bị quá tải trong các thông báo WebSocket hoặc khiến máy chủ WebSocket tràn ngập thông báo bằng cách áp dụng áp lực ngược (backpressure).

Thông tin khái quát

API WebSocket

WebSocket API cung cấp giao diện JavaScript cho giao thức WebSocket, giúp bạn có thể mở phiên giao tiếp tương tác hai chiều giữa trình duyệt của người dùng và máy chủ. Với API này, bạn có thể gửi thông báo đến máy chủ và nhận phản hồi theo sự kiện mà không cần thăm dò máy chủ để tìm câu trả lời.

API luồng

API luồng cho phép JavaScript truy cập theo lập trình các luồng dữ liệu nhận được qua mạng và xử lý các luồng đó như mong muốn. Một khái niệm quan trọng khi nói đến luồng là backpressure (áp lực). Đây là quá trình một luồng hoặc một chuỗi ống điều chỉnh tốc độ đọc hoặc ghi. Khi chính luồng hoặc một luồng sau đó trong chuỗi đường dẫn vẫn đang bận và chưa sẵn sàng chấp nhận thêm nhiều phần, luồng sẽ gửi tín hiệu ngược thông qua chuỗi để làm chậm quá trình phân phối khi thích hợp.

Sự cố với API WebSocket hiện tại

Không thể áp dụng backpressure cho các tin nhắn đã nhận

Với API WebSocket hiện tại, việc phản ứng với một thông báo sẽ xảy ra trong WebSocket.onmessage, một EventHandler được gọi khi nhận được thông báo từ máy chủ.

Giả sử bạn có một ứng dụng cần thực hiện các thao tác xử lý dữ liệu nặng nề mỗi khi nhận được một thông báo mới. Có thể bạn sẽ thiết lập luồng tương tự như mã bên dưới và vì bạn await kết quả của lệnh gọi process() nên bạn đã ổn phải không?

// 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);
};

Sai! Vấn đề với API WebSocket hiện tại là không có cách nào để áp dụng backpressure. Khi thông báo đến nhanh hơn khả năng xử lý của phương thức process(), quá trình kết xuất sẽ lấp đầy bộ nhớ bằng cách lưu vào bộ đệm các thông báo đó, không phản hồi do mức sử dụng CPU 100% hoặc cả hai.

Áp dụng backpressure cho tin nhắn đã gửi không công thái học

Bạn có thể áp dụng backpressure cho thông báo đã gửi, nhưng liên quan đến việc thăm dò thuộc tính WebSocket.bufferedAmount, vốn không hiệu quả và không tiện dụng. Thuộc tính chỉ đọc này trả về số byte dữ liệu đã được đưa vào hàng đợi bằng cách sử dụng lệnh gọi đến WebSocket.send(), nhưng chưa được truyền tới mạng. Giá trị này được đặt lại về 0 sau khi tất cả dữ liệu trong hàng đợi đã được gửi, nhưng nếu bạn tiếp tục gọi WebSocket.send(), giá trị sẽ tiếp tục tăng.

WebSocketStream API là gì?

API WebSocketStream xử lý vấn đề áp lực ngược không tồn tại hoặc không mang tính công thái học bằng cách tích hợp các luồng với API WebSocket. Điều này có nghĩa là bạn có thể áp dụng backpressure "miễn phí" mà không mất thêm phí.

Các trường hợp sử dụng được đề xuất cho API WebSocketStream

Ví dụ về những trang web có thể sử dụng API này bao gồm:

  • Các ứng dụng WebSocket băng thông cao cần giữ lại khả năng tương tác, cụ thể là chia sẻ video và màn hình.
  • Tương tự như vậy, tính năng quay video và các ứng dụng khác tạo ra nhiều dữ liệu trong trình duyệt và cần được tải lên máy chủ. Với áp lực ngược, máy khách có thể ngừng tạo dữ liệu thay vì tích luỹ dữ liệu trong bộ nhớ.

Trạng thái hiện tại

| Bước | Trạng thái | | ------------------------------------------ | ---------------------------- | | 1. Tạo nội dung giải thích | [Toàn bộ][giải thích] | | 2. Tạo bản nháp ban đầu của thông số kỹ thuật | [Đang tiến hành][thông số kỹ thuật] | | 3. Thu thập ý kiến phản hồi và lặp lại thiết kế | [Đang tiến hành](#phản hồi) | | 4. Bản dùng thử theo nguyên gốc | [Complete][ot] | | 5. Ra mắt | Chưa bắt đầu |

Cách sử dụng WebSocketStream API

Ví dụ giới thiệu

API WebSocketStream hoạt động dựa trên hứa hẹn, giúp việc xử lý hoạt động tự nhiên trong thế giới JavaScript hiện đại trở nên tự nhiên. Bạn bắt đầu bằng cách tạo một WebSocketStream mới rồi truyền vào đó URL của máy chủ WebSocket. Tiếp theo, bạn đợi kết nối là opened, dẫn đến ReadableStream và/hoặc WritableStream.

Bằng cách gọi phương thức ReadableStream.getReader(), cuối cùng bạn sẽ có được ReadableStreamDefaultReader. Sau đó, bạn có thể thu thập dữ liệu read() cho đến khi luồng hoàn tất, tức là cho đến khi luồng trả về một đối tượng của biểu mẫu{value: undefined, done: true}.

Theo đó, bằng cách gọi phương thức WritableStream.getWriter(), cuối cùng bạn sẽ có được WritableStreamDefaultWriter mà sau đó bạn có thể write() dữ liệu.

  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);
  }

Áp lực ngược

Còn tính năng backpressure đã hứa hẹn thì sao? Như tôi đã viết ở trên, bạn sẽ được sử dụng "miễn phí" và không cần làm gì thêm. Nếu process() mất thêm thời gian, thông báo tiếp theo sẽ chỉ được sử dụng sau khi quy trình đã sẵn sàng. Tương tự, bước WritableStreamDefaultWriter.write() sẽ chỉ tiếp tục nếu thấy an toàn.

Ví dụ nâng cao

Đối số thứ hai cho WebSocketStream là một túi tuỳ chọn cho phép tiện ích trong tương lai. Hiện tại, tuỳ chọn duy nhất là protocols, hoạt động giống như đối số thứ hai cho hàm khởi tạo WebSocket:

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

protocol đã chọn cũng như extensions tiềm năng là một phần của từ điển có sẵn thông qua cam kết WebSocketStream.opened. Lời hứa này sẽ cung cấp tất cả thông tin về kết nối trực tiếp, vì không liên quan nếu không kết nối được.

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

Thông tin về kết nối WebSocketStream đã đóng

Thông tin có sẵn từ các sự kiện WebSocket.oncloseWebSocket.onerror trong API WebSocket hiện đã có sẵn thông qua lời hứa WebSocketStream.closed. Lời hứa sẽ bị từ chối trong trường hợp đóng không sạch, nếu không, lời hứa sẽ phân giải thành mã và lý do do máy chủ gửi.

Tất cả các mã trạng thái có thể có và ý nghĩa của chúng được giải thích trong danh sách mã trạng thái CloseEvent.

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

Đóng kết nối WebSocketStream

Bạn có thể đóng WebSocketStream bằng AbortController. Do đó, hãy truyền AbortSignal đến hàm khởi tạo WebSocketStream.

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

Thay vào đó, bạn cũng có thể sử dụng phương thức WebSocketStream.close(), nhưng mục đích chính của phương thức này là cho phép chỉ định và lý do được gửi đến máy chủ.

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

Nâng cao dần dần và khả năng tương tác

Chrome hiện là trình duyệt duy nhất triển khai API WebSocketStream. Để có khả năng tương tác với API WebSocket cũ, bạn không thể áp dụng áp lực ngược cho các thông báo đã nhận. Bạn có thể áp dụng backpressure cho thông báo đã gửi, nhưng liên quan đến việc thăm dò thuộc tính WebSocket.bufferedAmount, vốn không hiệu quả và không tiện dụng.

Phát hiện tính năng

Để kiểm tra xem API WebSocketStream có được hỗ trợ hay không, hãy sử dụng:

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

Bản minh hoạ

Trên các trình duyệt được hỗ trợ, bạn có thể thấy API WebSocketStream hoạt động trong iframe được nhúng hoặc trực tiếp trên Glitch.

Ý kiến phản hồi

Nhóm Chrome muốn biết về trải nghiệm của bạn với API WebSocketStream.

Cho chúng tôi biết về thiết kế API

Có điều gì về API không hoạt động như bạn mong đợi không? Hay có thiếu phương thức hoặc thuộc tính nào mà bạn cần triển khai ý tưởng không? Bạn có câu hỏi hoặc nhận xét về mô hình bảo mật? Gửi vấn đề về thông số kỹ thuật trên kho lưu trữ GitHub tương ứng hoặc thêm ý kiến vào một vấn đề hiện có.

Báo cáo sự cố triển khai

Bạn có phát hiện thấy lỗi trong quá trình triển khai của Chrome không? Hay cách triển khai có khác với quy cách không? Hãy báo cáo lỗi tại new.crbug.com. Hãy nhớ đưa ra nhiều chi tiết nhất có thể, hướng dẫn đơn giản để sao chép và nhập Blink>Network>WebSockets vào hộp Thành phần. Sự cố rất hữu ích để chia sẻ các trường hợp mô phỏng nhanh chóng và dễ dàng.

Hỗ trợ API

Bạn có định sử dụng API WebSocketStream không? Sự hỗ trợ công khai của bạn giúp nhóm Chrome ưu tiên các tính năng và cho các nhà cung cấp trình duyệt khác thấy tầm quan trọng của việc hỗ trợ các tính năng đó.

Gửi một tweet đến @ChromiumDev bằng hashtag #WebSocketStream và cho chúng tôi biết vị trí cũng như cách bạn đang sử dụng hashtag này.

Các đường liên kết hữu ích

Xác nhận

API WebSocketStream do Adam gạoYutaka Hirano triển khai. Hình ảnh chính của Daan Mooij trên Unsplash.