ข้ามไปยังเนื้อหา

Binary Data

จนถึงตอนนี้ทุกข้อความเป็นสตริง แต่ WebSocket ก็พก ไบต์ ได้อย่างมีความสุขเช่นกัน — รูปภาพ, เสียง, protocol buffer, สถานะเกมที่ถูกอัดแน่นในรูปแบบ binary บทเรียนสุดท้ายนี้ครอบคลุม frame สองรสชาติ, วิธีที่เบราว์เซอร์ส่ง binary data กลับมาให้คุณ และวิธีจับตาดูว่ามีข้อมูลรอจะออกไปอยู่เท่าไร

ในระดับโปรโตคอล ทุกข้อความ WebSocket เป็นได้ทั้ง text frame หรือ binary frame:

  • text frame พก text ที่เข้ารหัสแบบ UTF-8 คุณสร้าง frame ชนิดนี้โดยส่งสตริงเข้า send() และรับกลับมาเป็นสตริง
  • binary frame พกไบต์ดิบโดยไม่มีสมมติฐานเรื่องการเข้ารหัส คุณสร้าง frame ชนิดนี้โดยส่ง binary data เข้า send()

ฝั่งรับจะถูกบอกว่า frame นั้นเป็นชนิดใด จึงไม่มีวันกำกวมระหว่าง “สตริงสี่ตัวอักษร 1234” กับ “สี่ไบต์ดิบ” คุณเลือกรสชาติเพียงแค่ดูจากสิ่งที่คุณส่งเข้า send()

send() รับ binary input สามแบบเพิ่มเติมจากสตริง:

// An ArrayBuffer — a fixed-length block of raw bytes.
const buffer = new ArrayBuffer(8);
ws.send(buffer);
// A TypedArray (or any ArrayBufferView) — a typed view over bytes.
const bytes = new Uint8Array([1, 2, 3, 4]);
ws.send(bytes);
// A Blob — file-like binary data, e.g. straight from an <input type="file">.
const blob = new Blob(['raw content']);
ws.send(blob);

ทั้งสามแบบถูกส่งเป็น binary frame เดียว ArrayBuffer และ TypedArray เป็นตัวเลือกปกติเมื่อคุณกำลังประกอบข้อความ binary ในcode ส่วน Blob โดดเด่นเมื่อข้อมูลมีอยู่แล้วในรูปไฟล์หรือมาจาก media API

นี่คือการตั้งค่าหนึ่งเดียวที่สำคัญสำหรับ binary เมื่อ binary frame มาถึง event.data ควรเป็นอะไร — Blob หรือ ArrayBuffer? คุณตัดสินใจด้วย ws.binaryType:

ws.binaryType = 'arraybuffer'; // event.data will be an ArrayBuffer
// or
ws.binaryType = 'blob'; // event.data will be a Blob (the default)

ค่าเริ่มต้นคือ 'blob' ตั้งเป็น 'arraybuffer' เมื่อคุณต้องการอ่านไบต์ในทันทีและแบบ synchronous — เช่นเพื่อห่อไว้ใน DataView หรือ Uint8Array แล้วดึงฟิลด์ออกมา การอ่าน Blob เป็นแบบ asynchronous ดังนั้นสำหรับโปรโตคอล binary ที่ต้องการ latency ต่ำ 'arraybuffer' มักจะเหมาะกว่า

ws.binaryType = 'arraybuffer';
ws.onmessage = (event) => {
if (typeof event.data === 'string') {
console.log('text frame:', event.data);
} else {
const view = new Uint8Array(event.data); // event.data is an ArrayBuffer
console.log('binary frame, first byte:', view[0]);
}
};

สังเกตว่า handler onmessage เดียวกันต้องแยกแขนงตามชนิดของ event.data: เป็นสตริงสำหรับ text frame และเป็น binary type ที่คุณเลือกสำหรับ binary frame

send() คืนค่าทันที แต่ไบต์ไม่ได้อยู่บนสายเสมอไป — ถ้าคุณส่งเร็วกว่าที่เครือข่ายจะระบายออก ไบต์เหล่านั้นจะกองพะเนินอยู่ใน buffer ภายใน ฟิลด์อ่านอย่างเดียว ws.bufferedAmount บอกคุณว่ายังมีกี่ไบต์ที่อยู่ในคิว:

if (ws.bufferedAmount < 1_000_000) {
ws.send(nextChunk); // only push more if the buffer is not backing up
}

นี่คือสัญญาณ backpressure ของคุณ เมื่อสตรีม binary data ขนาดใหญ่หรือถี่ ๆ ให้ตรวจ bufferedAmount ก่อนส่งเพิ่ม ถ้าค่านี้ไต่ขึ้นเรื่อย ๆ แสดงว่าคุณกำลังผลิตเร็วกว่าที่การเชื่อมต่อจะพาไปได้ และการเรียก send() แบบไม่ดูตาม้าตาเรือจะทำให้หน่วยความจำบวม

flowchart TD
  A["You call ws.send(payload)"] --> B{"What did you pass?"}
  B -- "string" --> T["Text frame (UTF-8)"]
  B -- "ArrayBuffer / TypedArray / Blob" --> N["Binary frame (raw bytes)"]
  N --> C{"ws.binaryType on the receiver"}
  C -- "'arraybuffer'" --> AB["event.data is an ArrayBuffer"]
  C -- "'blob'" --> BL["event.data is a Blob"]
การเลือกชนิด frame เมื่อคุณส่ง

เดโมใช้ echo socket ในหน้าเว็บที่มี WebSocket API จริง จึงรันได้ทุกที่โดยไม่ต้องมี server เดโมตั้ง binaryType เป็น 'arraybuffer', ส่ง Uint8Array เล็ก ๆ และอ่านไบต์ที่ echo กลับมาออกมาตรง ๆ ในแอปจริงมีเพียงบรรทัดแรก — new WebSocket('wss://your-server') — ที่เปลี่ยนไป

JavaScript

สี่ไบต์ออกไปเป็น binary frame และกลับมาเป็น ArrayBuffer — เพราะเราตั้ง binaryType = 'arraybuffer' — ซึ่งจากนั้นเราอ่านผ่าน Uint8Array ถ้าเราปล่อย binaryType ไว้ที่ค่าเริ่มต้น 'blob' event.data จะกลายเป็น Blob ที่ต้องอ่านแบบ asynchronous แทน

Binary Typeใช้เมื่อTrade-off
ArrayBufferต้องการอ่านไบต์แบบ synchronousต้อง copy data ใน memory
TypedArray (Uint8Array)มี structured binary dataต้อง align กับ ArrayBuffer
Blobfile/media data ที่อ่านแบบ asyncอ่านแบบ async เท่านั้น

ลืมตั้ง binaryType ก่อน onopen อาการ:

  • binary message มาถึง แต่ event.data เป็น Blob แทน ArrayBuffer
  • code พยายาม new Uint8Array(event.data) แต่ fail เพราะ Blob ไม่ใช่ ArrayBuffer
  • ตั้ง ws.binaryType = 'arraybuffer' ทันทีหลัง constructor ก่อน onopen

ไม่ Check bufferedAmount — ส่งเร็วเกินไป อาการ:

  • loop ส่ง chunk ขนาดใหญ่ติดกัน — bufferedAmount พุ่งสูง
  • memory ฝั่ง client บวม network timeout
  • check if (ws.bufferedAmount < threshold) ก่อนส่ง chunk ถัดไปเสมอ

💡 ตัวอย่างจากของจริง

Binance/Bybit WebSocket Streams:

  • ส่ง market data เป็น binary — compact กว่า JSON 3-5x
  • binaryType = 'arraybuffer' + DataView เพื่อ parse order book update latency ต่ำ

Figma Multiplayer:

  • ส่ง drawing operation เป็น binary frame
  • ตรวจ bufferedAmount ก่อนส่ง batch operation เพื่อป้องกัน lag
ค่าใดของ ws.binaryType ที่ทำให้ event.data เป็น ArrayBuffer?
ข้อใดที่คุณ "ไม่สามารถ" ส่งเข้า ws.send() ได้?
ws.bufferedAmount บอกอะไรคุณ?