⚠️ Maintenance. Notice: the site is currently in maintenance mode, so the amount of available material is temporarily limited.
Home / Tutorials / Binary dataБинарные данные

Sending binary data: Blob vs ArrayBufferБинарные данные: Blob или ArrayBuffer

Why send bytes at all?Зачем вообще слать байты?

JSON over WebSocket is convenient, but text is heavy: numbers become strings, keys repeat on every message, and parsing costs CPU. For high-frequency data — game state, audio chunks, sensor streams, position updates — a compact binary frame can be several times smaller and noticeably faster. WebSocket carries binary natively, so there is no base64 tax.

JSON поверх WebSocket удобен, но текст тяжёл: числа становятся строками, ключи повторяются в каждом сообщении, а парсинг тратит CPU. Для высокочастотных данных — состояние игры, аудио-чанки, потоки сенсоров, обновления координат — компактный бинарный кадр может быть в несколько раз меньше и заметно быстрее. WebSocket передаёт бинарные данные нативно, поэтому налога на base64 нет.

Choosing a binaryTypeВыбор binaryType

Incoming binary messages arrive as a Blob by default. For most apps you want an ArrayBuffer instead — it's synchronous to read and works directly with typed arrays:

binary-type.js
const ws = new WebSocket(url);
ws.binaryType = "arraybuffer";   // or the default "blob"

ws.onmessage = (e) => {
  if (typeof e.data === "string") {
    handleText(e.data);          // JSON / text frame
  } else {
    const view = new DataView(e.data);
    handleBinary(view);          // raw bytes
  }
};
  • Blob — best when you'll hand the data straight to an <img>, URL.createObjectURL, or store it; reading needs an async step.
  • ArrayBuffer — best when you parse fields immediately with DataView/typed arrays.

Входящие бинарные сообщения по умолчанию приходят как Blob. В большинстве приложений нужен ArrayBuffer — он читается синхронно и работает напрямую с типизированными массивами:

binary-type.js
const ws = new WebSocket(url);
ws.binaryType = "arraybuffer";   // или "blob" по умолчанию

ws.onmessage = (e) => {
  if (typeof e.data === "string") {
    handleText(e.data);          // JSON / текстовый кадр
  } else {
    const view = new DataView(e.data);
    handleBinary(view);          // сырые байты
  }
};
  • Blob — лучше, когда данные сразу идут в <img>, URL.createObjectURL или на хранение; чтение требует асинхронного шага.
  • ArrayBuffer — лучше, когда вы сразу разбираете поля через DataView/типизированные массивы.

Framing a structured messageФормирование структурного сообщения

Define a fixed layout and read/write it with DataView. Here a position update packs a 1-byte type tag, a 32-bit id and two floats — 13 bytes total instead of ~60 as JSON:

pack.js
const TYPE_MOVE = 0x01;

function packMove(id, x, y) {
  const buf = new ArrayBuffer(13);
  const dv = new DataView(buf);
  dv.setUint8(0, TYPE_MOVE);
  dv.setUint32(1, id);
  dv.setFloat32(5, x);
  dv.setFloat32(9, y);
  return buf;
}

function unpackMove(dv) {
  return {
    id: dv.getUint32(1),
    x:  dv.getFloat32(5),
    y:  dv.getFloat32(9),
  };
}

ws.send(packMove(42, 12.5, -3.0));   // 13 bytes on the wire
Pick one endianness and document it. DataView defaults to big-endian; pass true as the last argument for little-endian, and be consistent on both sides.

Определите фиксированную раскладку и читайте/пишите её через DataView. Здесь обновление позиции упаковывает 1-байтовый тег типа, 32-битный id и два float — всего 13 байт вместо ~60 в JSON:

pack.js
const TYPE_MOVE = 0x01;

function packMove(id, x, y) {
  const buf = new ArrayBuffer(13);
  const dv = new DataView(buf);
  dv.setUint8(0, TYPE_MOVE);
  dv.setUint32(1, id);
  dv.setFloat32(5, x);
  dv.setFloat32(9, y);
  return buf;
}

function unpackMove(dv) {
  return {
    id: dv.getUint32(1),
    x:  dv.getFloat32(5),
    y:  dv.getFloat32(9),
  };
}

ws.send(packMove(42, 12.5, -3.0));   // 13 байт «на проводе»
Выберите один порядок байт и зафиксируйте его. DataView по умолчанию big-endian; передайте true последним аргументом для little-endian и будьте последовательны на обеих сторонах.

Watch bufferedAmountСледите за bufferedAmount

Binary streams are easy to over-send. ws.bufferedAmount reports bytes queued but not yet sent. If it grows without bound, you're producing faster than the network drains — pause until it falls:

backpressure.js
function trySend(ws, chunk) {
  const LIMIT = 1 << 20;          // 1 MiB high-water mark
  if (ws.bufferedAmount > LIMIT) return false; // drop / defer
  ws.send(chunk);
  return true;
}

Бинарные потоки легко «перелить». ws.bufferedAmount сообщает число байт в очереди, ещё не отправленных. Если оно растёт безгранично — вы производите быстрее, чем сеть успевает отдавать; приостановитесь, пока не упадёт:

backpressure.js
function trySend(ws, chunk) {
  const LIMIT = 1 << 20;          // порог 1 МиБ
  if (ws.bufferedAmount > LIMIT) return false; // отложить / отбросить
  ws.send(chunk);
  return true;
}