Binary Data
จนถึงตอนนี้ทุกข้อความเป็นสตริง แต่ WebSocket ก็พก ไบต์ ได้อย่างมีความสุขเช่นกัน — รูปภาพ, เสียง, protocol buffer, สถานะเกมที่ถูกอัดแน่นในรูปแบบ binary บทเรียนสุดท้ายนี้ครอบคลุม frame สองรสชาติ, วิธีที่เบราว์เซอร์ส่ง binary data กลับมาให้คุณ และวิธีจับตาดูว่ามีข้อมูลรอจะออกไปอยู่เท่าไร
text กับ binary frame
หัวข้อที่มีชื่อว่า “text กับ binary frame”ในระดับโปรโตคอล ทุกข้อความ WebSocket เป็นได้ทั้ง text frame หรือ binary frame:
- text frame พก text ที่เข้ารหัสแบบ UTF-8 คุณสร้าง frame ชนิดนี้โดยส่งสตริงเข้า
send()และรับกลับมาเป็นสตริง - binary frame พกไบต์ดิบโดยไม่มีสมมติฐานเรื่องการเข้ารหัส คุณสร้าง frame ชนิดนี้โดยส่ง binary data เข้า
send()
ฝั่งรับจะถูกบอกว่า frame นั้นเป็นชนิดใด จึงไม่มีวันกำกวมระหว่าง “สตริงสี่ตัวอักษร 1234” กับ “สี่ไบต์ดิบ” คุณเลือกรสชาติเพียงแค่ดูจากสิ่งที่คุณส่งเข้า send()
การส่ง binary
หัวข้อที่มีชื่อว่า “การส่ง binary”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: binaryType
หัวข้อที่มีชื่อว่า “การรับ binary: binaryType”นี่คือการตั้งค่าหนึ่งเดียวที่สำคัญสำหรับ binary เมื่อ binary frame มาถึง event.data ควรเป็นอะไร — Blob หรือ ArrayBuffer? คุณตัดสินใจด้วย ws.binaryType:
ws.binaryType = 'arraybuffer'; // event.data will be an ArrayBuffer// orws.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
การเฝ้าดู bufferedAmount
หัวข้อที่มีชื่อว่า “การเฝ้าดู bufferedAmount”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"] echo ArrayBuffer แบบสด
หัวข้อที่มีชื่อว่า “echo ArrayBuffer แบบสด”เดโมใช้ echo socket ในหน้าเว็บที่มี WebSocket API จริง จึงรันได้ทุกที่โดยไม่ต้องมี server เดโมตั้ง binaryType เป็น 'arraybuffer', ส่ง Uint8Array เล็ก ๆ และอ่านไบต์ที่ echo กลับมาออกมาตรง ๆ ในแอปจริงมีเพียงบรรทัดแรก — new WebSocket('wss://your-server') — ที่เปลี่ยนไป
สี่ไบต์ออกไปเป็น 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 |
| Blob | file/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