Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Full-Duplex Communication

After the handshake, WebSocket communication happens through frames—small packets that can carry text, binary data, or control messages.

Frame Format

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
│F│R│R│R│  Opcode │M│         Payload Length                   │
│I│S│S│S│  (4)    │A│             (7)                          │
│N│V│V│V│         │S│                                          │
│ │1│2│3│         │K│                                          │
├─┴─┴─┴─┴─────────┴─┴───────────────────────────────────────────┤
│     Extended payload length (16/64 bits, if needed)          │
├───────────────────────────────────────────────────────────────┤
│     Masking key (32 bits, if MASK=1)                         │
├───────────────────────────────────────────────────────────────┤
│     Payload Data                                             │
└───────────────────────────────────────────────────────────────┘

Minimum frame: 2 bytes (header only)
Typical small message: 6-8 bytes overhead

Frame Fields

FieldBitsDescription
FIN1Final fragment of message
RSV1-33Reserved for extensions
Opcode4Frame type
MASK1Payload is masked (required from client)
Payload Length7+Size of payload

Opcodes

0x0  Continuation   Part of fragmented message
0x1  Text          UTF-8 text data
0x2  Binary        Binary data
0x8  Close         Connection close request
0x9  Ping          Heartbeat request
0xA  Pong          Heartbeat response

Message Types

Text Messages

// Send
ws.send("Hello, World!");

// Frame created:
// FIN=1, Opcode=0x1 (text), MASK=1, Payload="Hello, World!"

// Receive
ws.onmessage = (event) => {
  console.log(event.data);  // "Hello, World!" (string)
};

Binary Messages

// Send ArrayBuffer
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.setFloat64(0, 3.14159);
ws.send(buffer);

// Send Blob
const blob = new Blob(['Binary data'], {type: 'application/octet-stream'});
ws.send(blob);

// Receive
ws.binaryType = 'arraybuffer';  // or 'blob'
ws.onmessage = (event) => {
  const data = event.data;  // ArrayBuffer or Blob
};

Control Frames

Ping/Pong (Heartbeat)

Ping: "Are you still there?"
Pong: "Yes, I'm here."

Server sends:  Ping (opcode 0x9)
Client sends:  Pong (opcode 0xA) with same payload

Purpose:
  - Detect dead connections
  - Keep NAT mappings alive
  - Measure latency

Client browser handles Pong automatically.

Close Frame

Graceful shutdown:

1. Initiator sends Close frame
   - Opcode 0x8
   - Optional: status code (2 bytes) + reason (text)

2. Recipient sends Close frame back

3. TCP connection closed

Status codes:
  1000  Normal closure
  1001  Endpoint going away
  1002  Protocol error
  1003  Unsupported data type
  1006  Abnormal closure (no close frame)
  1011  Server error

Masking

Client-to-server frames must be masked:

Why masking?
  Cache poisoning attack prevention.
  Proxies might cache WebSocket data as HTTP.
  Masking makes data look random, prevents caching.

Masking process:
  1. Generate random 32-bit masking key
  2. XOR each byte of payload with key (rotating)

  masked[i] = payload[i] XOR key[i % 4]

Server-to-client: No masking required.

Fragmentation

Large messages can be split into fragments:

Large message (1MB):

Fragment 1: FIN=0, Opcode=0x1 (text), data[0:64KB]
Fragment 2: FIN=0, Opcode=0x0 (continuation), data[64KB:128KB]
Fragment 3: FIN=0, Opcode=0x0 (continuation), data[128KB:192KB]
...
Fragment N: FIN=1, Opcode=0x0 (continuation), data[last portion]

Receiver reassembles before delivering to application.
Allows interleaving with control frames (Ping/Pong).

Full-Duplex in Action

┌─────────────────────────────────────────────────────────────────────┐
│                    Simultaneous Communication                       │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  Client                                            Server           │
│     │                                                 │             │
│     │──── "Hello" ────────────────────────────────────│             │
│     │────────────────────────────── "World" ──────────│             │
│     │                        X                        │             │
│     │──── "How are you?" ─────────────────────────────│             │
│     │────────────────────── "Message for you" ────────│             │
│     │                                                 │             │
│     │ Messages cross "in flight"                      │             │
│     │ No waiting for response                         │             │
│     │ Both sides send whenever ready                  │             │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Implementation Patterns

Message Protocol

// Define message format
const message = {
  type: 'chat',
  payload: {
    user: 'alice',
    text: 'Hello everyone!'
  },
  timestamp: Date.now()
};

ws.send(JSON.stringify(message));

// Receiver
ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  switch (msg.type) {
    case 'chat':
      displayChat(msg.payload);
      break;
    case 'notification':
      showNotification(msg.payload);
      break;
  }
};

Reconnection Logic

function connect() {
  const ws = new WebSocket('wss://example.com/socket');

  ws.onopen = () => {
    console.log('Connected');
    reconnectAttempts = 0;
  };

  ws.onclose = (event) => {
    if (event.code !== 1000) {  // Not normal close
      // Exponential backoff
      const delay = Math.min(1000 * 2 ** reconnectAttempts, 30000);
      reconnectAttempts++;
      setTimeout(connect, delay);
    }
  };

  return ws;
}

Summary

WebSocket communication features:

AspectDetails
Frame overhead2-14 bytes (vs 100+ for HTTP)
Message typesText (UTF-8), Binary
Control framesPing, Pong, Close
DirectionFull-duplex (simultaneous both ways)
FragmentationLarge messages split across frames
MaskingRequired for client→server