C.W.K.
Stream
Lesson 07 of 07 · published

Message Ordering, Size & ACKs

~13 min · protocol, ordering, ack, asyncapi

Level 0Poller
0 XP0/60 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

WebSocket guarantees in-order delivery

Within a single connection, WebSocket frames arrive in send order — that is a TCP guarantee that flows up. There is no reordering and no protocol-level deduplication. If you reconnect, that guarantee resets: the new connection is independent.

Frame size limits

The protocol allows enormous frames in theory (2^63 bytes), but every implementation caps them. Default limits in production are usually 64KB-1MB. For larger payloads, chunk explicitly: send file.start with metadata, then a stream of binary chunks, then file.end. Never assume one logical message = one frame.

Application-level ACKs

If a message must be delivered (a chat message, a payment confirmation), build acknowledgement at the application layer: client sends with a sequence number, server echoes back ack with that number, client retransmits if no ack arrives in time. This is independent of the underlying WebSocket — it survives reconnects.

AsyncAPI for documentation

AsyncAPI is OpenAPI for event-driven and message-based APIs. Define your channels, operations, and message schemas in YAML; generate docs, mocks, even client code. Worth it once your protocol has more than a handful of types.

Code

Chunked file upload over WebSocket·javascript
async function uploadFile(ws, file) {
  const CHUNK = 64 * 1024;
  const total = Math.ceil(file.size / CHUNK);
  ws.send(JSON.stringify({
    type: 'file.start',
    data: { name: file.name, size: file.size, chunks: total },
  }));
  for (let i = 0; i < total; i++) {
    const slice = file.slice(i * CHUNK, (i + 1) * CHUNK);
    const buf = await slice.arrayBuffer();
    ws.send(buf);
  }
  ws.send(JSON.stringify({ type: 'file.end' }));
}
Application-level ACK with retry·javascript
const unacked = new Map();
let nextSeq = 0;

function sendReliable(ws, type, data) {
  const seq = ++nextSeq;
  const msg = { seq, type, data };
  unacked.set(seq, { msg, attempts: 1 });
  ws.send(JSON.stringify(msg));
  setTimeout(() => retryIfStillUnacked(ws, seq), 5_000);
}

function retryIfStillUnacked(ws, seq) {
  const entry = unacked.get(seq);
  if (!entry) return;
  if (entry.attempts >= 3) {
    unacked.delete(seq);
    notifyUserOfFailure(seq);
    return;
  }
  entry.attempts += 1;
  ws.send(JSON.stringify(entry.msg));
  setTimeout(() => retryIfStillUnacked(ws, seq), 5_000);
}

ws.onmessage = (e) => {
  const m = JSON.parse(e.data);
  if (m.type === 'ack') unacked.delete(m.seq);
};

External links

Exercise

Implement chunked file upload over WebSocket. Pick a 5MB file, split into 64KB chunks. Server reassembles and saves to disk. Verify size matches input. Now mid-upload, drop the connection — observe that the server has a partial file. Add a file.resume message that lets the client resume from the next chunk index.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.