LOW-LEVEL NETWORKING SERIES
Read on Dev.to
Principal Systems Engineering Deep Dive

Implementing a Reliable UDP-Based Protocol (ARQ) for High-Throughput Systems

A low-level systems analysis of custom packet structures, Selective Repeat sliding window mechanisms, and congestion-throttling over UDP sockets.

EA
Ebenezer AkinseindeSoftware Developer & AI Automations Engineer
Published Jun 202627 min readWeb Engineering

01.Interactive Network ARQ Simulator

Experience Selective Repeat vs. Go-Back-N error recovery protocols in real time. Adjust packet drop rates and link round-trip time (RTT), then click **"Transmit"** to watch the sliding window resend dropped frames.

ARQ PROTOCOL LAB

Reliable Frame Transfer simulator

PACKET LOSS RATE:20%
ROUND TRIP DELAY (RTT):400ms
ARQ PROTOCOL STRATEGY:
SENDER TRANSMIT WINDOW (Size: 4)Sliding Base Window
Click "Transmit" to fill buffer
RECEIVER REASSEMBLY BUFFER:
Waiting for incoming packets...
SIMULATOR LOG MONITOR:

02.Why RUDP? The Limits of TCP & UDP

In networking, we traditionally choose between two standard transport layer protocols:

  • TCP (Transmission Control Protocol): Guarantees in-order, error-free delivery. However, it suffers from heavy connection handshake overhead, head-of-line blocking, and complex congestion algorithms that can throttle performance on high-latency links.
  • UDP (User Datagram Protocol): A lightweight, connectionless protocol with minimal packet overhead. It sends packets immediately without waiting, but it does not guarantee delivery, packet order, or duplicate prevention.

For high-throughput, real-time applications (such as multiplayer game servers or streaming systems), we need both speed and reliability. Reliable UDP (RUDP) fills this gap. It runs on top of raw UDP sockets but implements reliability features in user space, allowing developers to choose which packets require guaranteed delivery and avoid head-of-line blocking for time-sensitive updates.

03.RUDP Packet Header Structs

To enforce reliability over a connectionless UDP socket, we must append a custom protocol header to every payload.

This custom header typically contains the following fields:

  • Sequence Number (32 bits): Identifies the exact order of the packet so the receiver can reassemble them correctly.
  • Acknowledgment Number (32 bits): Confirms the sequence numbers of successfully received packets.
  • Flags (8 bits): Controls packet types, such as connection setup (`SYN`), shutdown (`FIN`), data payloads (`DAT`), or confirmations (`ACK`/`NACK`).
  • Checksum (16 bits): Validates packet integrity to detect data corruption during transit.

04.Sliding Window & Flow Controls

Sending one packet and waiting for its ACK before sending the next (Stop-and-Wait) is very slow. To maximize bandwidth, we use a Sliding Window.

The sender maintains a "window" of allowed, unacknowledged packets (e.g. sequence numbers 0 to 3). As long as the window isn't full, the sender transmits packets continuously.

When the receiver ACKs the oldest packet in the window (the base), the window slides forward, allowing new packets to be sent. This keeps the network link saturated and increases overall data throughput.

05.Selective Repeat vs. Go-Back-N

When a packet is lost in transit, the protocol must decide how to retransmit it. There are two main strategies:

Go-Back-N (GBN)

In Go-Back-N, the receiver only accepts packets in strict, sequential order. If packet 2 is lost, the receiver discards all subsequent packets (3, 4, 5), even if they arrive safely.

The sender's timeout is triggered for packet 2, and the sender must retransmit packet 2 and all following packets in the window (2, 3, 4, 5). This is simple to implement but wastes bandwidth on lossy networks.

Selective Repeat (SR)

In Selective Repeat, the receiver accepts out-of-order packets and buffers them. If packet 2 is lost but 3, 4, and 5 arrive, the receiver keeps them and sends ACKs for them.

The sender detects that only packet 2 is missing and retransmits *only* packet 2. Once packet 2 arrives, the receiver merges it with the buffered packets and delivers them in order to the application, minimizing retransmission traffic.

06.TypeScript Reliable UDP Source Code

Below is a clean, modular TypeScript interface mapping sliding window bounds, Selective Repeat buffers, and packet serialization:

reliable-udp-engine.ts
export interface UDPPacket {
  seq: number;
  flags: { SYN: boolean; ACK: boolean; DAT: boolean };
  checksum: number;
  payload: string;
}

export class SlidingWindowSender {
  private windowSize: number = 4;
  private nextSeqNum: number = 0;
  private base: number = 0;
  private sendBuffer: Map<number, UDPPacket> = new Map();
  private ackedPackets: Set<number> = new Set();
  private timers: Map<number, NodeJS.Timeout> = new Map();
  private readonly TIMEOUT_MS = 1500;

  constructor(private socketSend: (packet: UDPPacket) => void) {}

  /**
   * Appends payload to buffer and attempts transmission.
   */
  public send(payload: string): void {
    const packet: UDPPacket = {
      seq: this.nextSeqNum,
      flags: { SYN: false, ACK: false, DAT: true },
      checksum: this.calculateChecksum(payload),
      payload
    };

    this.sendBuffer.set(this.nextSeqNum, packet);
    this.nextSeqNum++;

    this.tryTransmit();
  }

  private tryTransmit(): void {
    // Transmit packets falling within sliding window limits
    while (this.nextSeqNum < this.base + this.windowSize && this.sendBuffer.has(this.nextSeqNum)) {
      const packet = this.sendBuffer.get(this.nextSeqNum)!;
      this.transmitPacket(packet);
    }
  }

  private transmitPacket(packet: UDPPacket): void {
    this.socketSend(packet);
    this.startTimer(packet.seq);
  }

  private startTimer(seq: number): void {
    if (this.timers.has(seq)) return;

    const timer = setTimeout(() => {
      // Timeout triggered: retransmit packet
      const packet = this.sendBuffer.get(seq);
      if (packet && !this.ackedPackets.has(seq)) {
        this.transmitPacket(packet);
      }
    }, this.TIMEOUT_MS);

    this.timers.set(seq, timer);
  }

  /**
   * Receiver notifies sender of an Acknowledgment (ACK)
   */
  public handleAck(ackSeq: number): void {
    this.ackedPackets.add(ackSeq);

    // Stop timer
    if (this.timers.has(ackSeq)) {
      clearTimeout(this.timers.get(ackSeq)!);
      this.timers.delete(ackSeq);
    }

    // Slide window base forward if the oldest packet in the window was ACKed
    if (ackSeq === this.base) {
      while (this.ackedPackets.has(this.base)) {
        this.base++;
      }
      this.tryTransmit();
    }
  }

  private calculateChecksum(data: string): number {
    let sum = 0;
    for (let i = 0; i < data.length; i++) {
      sum += data.charCodeAt(i);
    }
    return sum % 65535;
  }
}

07.Production Takeaways

RUDP is the foundation of modern high-performance protocols like **QUIC** (which powers HTTP/3) and **WebRTC**.

  • Minimize connection latency: Moving reliability logic to user space bypasses OS-level TCP handshakes, enabling near-instantaneous connections.
  • Prevent head-of-line blocking: Multiplexing streams over UDP ensures that a single dropped packet doesn't block unrelated data streams.
  • Custom congestion controls: RUDP allows developers to design custom congestion throttling tailored to specific network conditions.
EA

Ebenezer Akinseinde

I engineer highly secure distributed consensus state machines, horizontal WebSocket relay networks, and performant AI vector databases. Let's build resilient systems together.