Designing Binary WebSocket Protocols for Multiplayer Games

Real-time multiplayer games demand high update frequencies, minimal latency, and low bandwidth. When building browser-based games using WebSockets, developers often default to JSON-based communication, which introduces significant performance bottlenecks.

Text serialization consumes CPU cycles, increases payload sizes, and generates garbage collection (GC) pressure due to constant string allocations. For a game aiming for a stable 60Hz tick rate, JSON overhead degrades client framerates and saturates player bandwidth.

To overcome these limitations, engineering teams must design custom binary protocols. By operating directly on raw bytes using binary WebSockets, game clients and servers can achieve optimal serialization speeds and minimize packet sizes. This technical guide explores the architectural principles, serialization strategies, and performance optimizations required to build a production-grade binary protocol for real-time multiplayer games.

1. The Anatomy of a Custom Binary Frame

Unlike text-based protocols where the boundary of a message is often marked by a newline or delimiter, binary protocols rely on fixed-size headers and structured offsets. Every byte in a binary frame represents a specific piece of information. A typical game packet is divided into two primary sections: the header and the payload.

The header contains metadata essential for routing, decoding, and validating the packet. A minimal header for a multiplayer game might include:

  • Opcode (1 byte): Identifies the message type (e.g., PlayerMove, SpawnEntity, Shoot, SyncState). This allows for up to 256 distinct message types.
  • Sequence Number (2 bytes): A monotonically increasing identifier to detect packet loss, verify order, and handle late arrivals.
  • Timestamp (4 bytes): High-resolution timestamp for interpolating and extrapolating entity coordinates.

Following the header is the payload, whose layout is determined by the Opcode. By omitting field names and structural markers, the binary payload only transmits raw values, reducing bandwidth consumption by 70% to 90% compared to JSON equivalents.

2. Leveraging ArrayBuffer and DataView in JavaScript

In web-based game clients, binary data is manipulated using the ArrayBuffer interface, which represents a generic, fixed-length raw binary data buffer. Because you cannot directly manipulate the contents of an ArrayBuffer, JavaScript provides TypedArrays (e.g., Uint8Array, Float32Array) and DataView to read and write data with specific byte-level interpretations.

While TypedArrays are fast, they require strict alignment to their underlying byte offset. For structured protocols with mixed data types (such as an 8-bit integer followed by a 32-bit float), DataView is the preferred choice because it allows unaligned reads and writes at arbitrary byte offsets. Furthermore, DataView lets developers explicitly control endianness (byte ordering), which is critical since game clients and servers might run on different hardware architectures.

The following example demonstrates how to serialize a player movement update into a binary packet using a DataView:

// Define Opcodes
const OP_PLAYER_MOVE = 1;

function serializePlayerMove(sequence, x, y, angle) {
  // 1 byte Opcode + 2 bytes Sequence + 4 bytes Timestamp + 4 bytes Float32 X + 4 bytes Float32 Y + 2 bytes Int16 Angle (quantized)
  const packetSize = 1 + 2 + 4 + 4 + 4 + 2;
  const buffer = new ArrayBuffer(packetSize);
  const view = new DataView(buffer);
  
  let offset = 0;
  
  // Write Header
  view.setUint8(offset, OP_PLAYER_MOVE);
  offset += 1;
  
  view.setUint16(offset, sequence, true); // Little-endian
  offset += 2;
  
  view.setUint32(offset, performance.now(), true);
  offset += 4;
  
  // Write Payload
  view.setFloat32(offset, x, true);
  offset += 4;
  
  view.setFloat32(offset, y, true);
  offset += 4;
  
  // Quantize float angle [-Math.PI, Math.PI] to Int16 [-32768, 32767]
  const quantizedAngle = Math.round((angle / Math.PI) * 32767);
  view.setInt16(offset, quantizedAngle, true);
  
  return buffer;
}

3. Mitigating Garbage Collection Pressure

A major pitfall of binary protocols in JavaScript is the instantiations of new ArrayBuffer and DataView instances for every outgoing and incoming packet. Creating objects at a rate of 60 packets per second per client triggers frequent Garbage Collection (GC) sweeps. When GC runs, it blocks the main thread (known as a "stop-the-world" pause), resulting in micro-stutters and frame drops that ruin the game experience.

To eliminate GC pressure, engineers implement Buffer Pooling. Instead of allocating a new buffer for every message, a pool of pre-allocated buffers is maintained in memory. By operating directly on raw bytes using binary WebSockets, game clients can implement robust WebSocket reconnection strategies to handle transient dropouts.

When a packet is sent, a buffer is checked out from the pool, populated, transmitted via the socket, and returned for reuse. On the receiving end, incoming packets should be parsed within a recycled context view. To eliminate GC pressure, engineers must implement Buffer Pooling, which is a key technique in a high-performance real-time synchronization engine.

4. Bandwidth Optimization via Quantization and Delta Encoding

To squeeze the maximum efficiency out of a binary protocol, data must be compressed before transmission. Two highly effective techniques are quantization and delta encoding:

  • Quantization: Floating-point numbers represent coordinates and physics states with high precision. However, sending a 32-bit float for a player's rotation is often wasteful. By scaling and mapping a float to a smaller integer range (such as mapping 0.0–360.0 degrees to a single unsigned byte 0–255), you reduce bandwidth without sacrificing noticeable accuracy.
  • Delta Encoding: Instead of transmitting the entire state of the game world every tick, the server only transmits the differences (deltas) since the last acknowledged client tick. If an entity hasn't moved, its data is omitted from the payload entirely.

5. Deploying and Scaling Real-Time Multiplayer at the Edge with Bramsley

Designing a high-performance binary WebSocket protocol is only half the battle; scaling the infrastructure to handle thousands of concurrent connections globally is the real challenge. Traditional centralized game servers introduce crippling latency for players located far from the host data center. At Bramsley, we help engineering teams deploy ultra-low latency WebSockets on edge workers that terminate client connections near the user.

Real-Time Multiplayer Optimization at the Edge with Bramsley

Delivering sub-10ms latency for global multiplayer games requires moving protocol logic and connection termination to the network edge. Bramsley Digital Studio builds specialized, edge-native communication layers that bypass core server bottlenecks.

Edge Broker Architecture: We deploy Rust-based WebAssembly handlers that terminate socket connections at the point of presence nearest to the player. This processes binary packets instantly, reducing round-trip times and offloading state synchronization. This edge-synchronized state layer allows you to replicate game rooms and coordinates globally, leveraging distributed conflict-free replicated data types (CRDTs).

Bramsley Digital Studio

Enterprise Digital Architecture

We engineer digital infrastructure that drives measurable B2B growth. Experts in Legacy System Migration and High-Performance Frontends.

Architecture Specs & Case Studies

Scale Your Operations

  • Legacy System Migration
  • Scalable Infrastructure
  • High-Performance Frontends
  • Global Edge Deployment