WebSocketStream: การผสานรวมสตรีมกับ WebSocket API

ป้องกันไม่ให้แอปจมอยู่กับข้อความ WebSocket หรือทำให้เซิร์ฟเวอร์ WebSocket ทำงานหนักเกินไปด้วยข้อความที่ใช้แรงกดดัน

ข้อมูลเบื้องต้น

WebSocket API

WebSocket API จัดเตรียมอินเทอร์เฟซ JavaScript ให้กับโปรโตคอล WebSocket ซึ่งทำให้สามารถเปิดเซสชันการสื่อสารแบบ 2 ทาง ระหว่างเบราว์เซอร์ของผู้ใช้และเซิร์ฟเวอร์ คุณสามารถใช้ API นี้ในการส่งข้อความไปยังเซิร์ฟเวอร์และรับการตอบกลับที่ขับเคลื่อนด้วยเหตุการณ์ได้ โดยไม่ต้องสำรวจเซิร์ฟเวอร์เพื่อตอบกลับ

API ของสตรีม

Streams API อนุญาตให้ JavaScript เข้าถึงสตรีมข้อมูลที่ได้รับผ่านเครือข่ายแบบเป็นโปรแกรมได้ และประมวลผลได้ตามต้องการ แนวคิดสำคัญในบริบทของสตรีมคือ backpressure นี่คือกระบวนการที่สตรีมเดียวหรือโซ่ไปป์ ควบคุมความเร็วในการอ่านหรือเขียน เมื่อสตรีมนั้นเองหรือสตรีมในภายหลังในห่วงโซ่ไปป์ยังคงไม่ว่าง และยังไม่พร้อมที่จะรองรับกลุ่มอื่นๆ อีก โดยจะส่งสัญญาณย้อนกลับผ่านห่วงโซ่เพื่อชะลอการนำส่งตามความเหมาะสม

ปัญหาเกี่ยวกับ 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 ปัจจุบันคือไม่มีวิธีที่จะใช้ Backpressure เมื่อข้อความมาถึงเร็วกว่าที่เมธอด process() จะจัดการได้ กระบวนการแสดงผลจะทำให้หน่วยความจำเต็ม โดยการบัฟเฟอร์ข้อความเหล่านั้น ไม่ตอบสนองเนื่องจากการใช้งาน CPU 100% หรือทั้งสองอย่าง

การใช้แรงกดดันย้อนกลับกับข้อความที่ส่งไม่เป็นไปตามหลักการยศาสตร์

คุณสามารถทำได้โดยใช้แรงกดดันย้อนกลับกับข้อความที่ส่ง แต่จะต้องมีการสำรวจ WebSocket.bufferedAmount ที่ไร้ประสิทธิภาพและไม่ผิดหลักสรีรศาสตร์ พร็อพเพอร์ตี้แบบอ่านอย่างเดียวนี้จะแสดงจำนวนไบต์ของข้อมูลที่อยู่ในคิว โดยใช้การโทรไปยัง WebSocket.send() แต่ยังไม่ได้ส่งไปยังเครือข่าย ค่านี้จะรีเซ็ตเป็น 0 เมื่อส่งข้อมูลที่อยู่ในคิวทั้งหมดแล้ว แต่ถ้าคุณโทรหา WebSocket.send() ซ้ำๆ โมเดลก็จะปีนต่อไป

WebSocketStream API คืออะไร

WebSocketStream API รับมือกับปัญหาความดันย้อนกลับที่ไม่มีอยู่หรือไม่ได้ถูกสรีรศาสตร์ ด้วยการผสานรวมสตรีมกับ WebSocket API ซึ่งหมายความว่าคุณจะใช้แรงดันย้อนกลับ "ฟรี" ได้โดยไม่มีค่าใช้จ่ายเพิ่มเติม

กรณีการใช้งานที่แนะนำสำหรับ WebSocketStream API

ตัวอย่างเว็บไซต์ที่ใช้ API นี้ได้ ได้แก่

  • แอปพลิเคชัน WebSocket แบบแบนด์วิดท์สูงที่จำเป็นต้องรักษาการโต้ตอบ โดยเฉพาะวิดีโอ และการแชร์หน้าจอ
  • ในทำนองเดียวกัน การจับภาพวิดีโอและแอปพลิเคชันอื่นๆ ที่สร้างข้อมูลจำนวนมากในเบราว์เซอร์ ที่จะอัปโหลดไปยังเซิร์ฟเวอร์ การใช้แรงดันย้อนกลับจะทำให้ไคลเอ็นต์หยุดสร้างข้อมูลแทนการสะสมข้อมูลในหน่วยความจำ

สถานะปัจจุบัน

ขั้นตอน สถานะ
1. สร้างคำอธิบาย เสร็จสมบูรณ์
2. สร้างข้อกำหนดคร่าวๆ เบื้องต้น กำลังดำเนินการ
3. รวบรวมความคิดเห็นและ ทำซ้ำในการออกแบบ กำลังดำเนินการ
4. ช่วงทดลองใช้จากต้นทาง เสร็จสมบูรณ์
5. เปิดตัว ยังไม่เริ่ม

วิธีใช้ WebSocketStream API

ตัวอย่างแนะนำ

WebSocketStream API ทำตามสัญญา ซึ่งช่วยให้จัดการสิ่งต่างๆ ได้อย่างเป็นธรรมชาติ ในโลก JavaScript สมัยใหม่ คุณเริ่มต้นด้วยการสร้าง WebSocketStream ใหม่และส่ง URL ของเซิร์ฟเวอร์ 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);
  }

แรงกดดันด้านหลัง

แล้วฟีเจอร์ Backpressure ตามที่สัญญาไว้มีอะไรบ้าง ตามที่เราได้กล่าวไว้ข้างต้น คุณจะใช้งานได้แบบ "ฟรี" โดยไม่ต้องดำเนินการใดๆ เพิ่มเติม หาก process() ใช้เวลานานขึ้น ระบบจะใช้งานข้อความถัดไปเมื่อไปป์ไลน์พร้อมใช้งานแล้วเท่านั้น เช่นเดียวกับขั้นตอน WritableStreamDefaultWriter.write() จะดำเนินการต่อเมื่อทำได้อย่างปลอดภัยเท่านั้น

ตัวอย่างขั้นสูง

อาร์กิวเมนต์ที่สองของ WebSocketStream เป็นกระเป๋าสำหรับใช้ขยายเวลาในอนาคต ปัจจุบันตัวเลือกเดียวคือ protocols ซึ่งมีลักษณะการทำงานเหมือนกับ อาร์กิวเมนต์ที่สองไปยังตัวสร้าง WebSocket:

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

protocol ที่เลือกและ extensions ที่เป็นไปได้เป็นส่วนหนึ่งของพจนานุกรม พร้อมใช้งานผ่านสัญญา WebSocketStream.opened สัญญานี้ให้ข้อมูลทั้งหมดเกี่ยวกับการเชื่อมต่อแบบถ่ายทอดสด เนื่องจากไม่มีความเกี่ยวข้องหากการเชื่อมต่อล้มเหลว

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

ข้อมูลเกี่ยวกับการเชื่อมต่อ WebSocketStream แบบปิด

ข้อมูลที่พร้อมใช้งานจาก WebSocket.onclose และ WebSocket.onerror กิจกรรม ใน WebSocket API พร้อมให้บริการแล้วผ่าน WebSocketStream.closed คำสัญญาจะปฏิเสธในกรณีที่การปิดการขายอย่างไม่สะอาด มิเช่นนั้น ก็จะเปลี่ยนเป็นโค้ดและเหตุผลที่เซิร์ฟเวอร์ส่งมา

รหัสสถานะที่เป็นไปได้ทั้งหมดและความหมายมีอธิบายอยู่ใน รายการรหัสสถานะ CloseEvent

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

การปิดการเชื่อมต่อ WebSocketStream

สามารถปิด 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 โปรดใส่รายละเอียดให้มากที่สุดเท่าที่จะเป็นไปได้ วิธีการง่ายๆ ในการทำซ้ำ แล้วป้อน Blink>Network>WebSockets ในช่องคอมโพเนนต์ Glitch เหมาะสำหรับการแชร์เคสการทำซ้ำที่ง่ายและรวดเร็ว

แสดงการรองรับ API

คุณกำลังวางแผนที่จะใช้ WebSocketStream API ใช่ไหม การสนับสนุนแบบสาธารณะของคุณช่วยให้ทีม Chrome จัดลำดับความสำคัญของฟีเจอร์ต่างๆ ได้ และแสดงให้ผู้ให้บริการเบราว์เซอร์รายอื่นเห็นถึงความสำคัญในการสนับสนุนของตน

ส่งทวีตไปยัง @ChromiumDev โดยใช้แฮชแท็ก #WebSocketStream และแจ้งให้เราทราบถึงตำแหน่งและวิธีที่คุณใช้งาน

ลิงก์ที่มีประโยชน์

กิตติกรรมประกาศ

WebSocketStream API ดำเนินการโดย Adam Rice และ ยูทากะ ฮิราโนะ รูปภาพหลักโดย Daan Mooij ใน หน้าจอแนะนํา