UDP Protocol

User Datagram Protocol is a connectionless, stateless transport protocol. No handshake, no ordering, no retransmission, no flow control. Each datagram is independent — fire and forget.

Why It Matters

UDP is the backbone of real-time protocols: DNS, gaming, VoIP, video streaming, and IoT. When retransmitting stale data is worse than dropping it (a voice packet from 200ms ago is useless), UDP wins. It’s also the foundation of modern protocols like QUIC (HTTP/3).

UDP Header (8 bytes total)

 0      7 8     15 16    23 24    31
+--------+--------+--------+--------+
| Source Port     | Dest Port       |
+--------+--------+--------+--------+
| Length          | Checksum        |
+--------+--------+--------+--------+
| Data ...                          |
+-----------------------------------+

Compare: TCP header is 20-60 bytes. UDP’s 8-byte header means minimal overhead per packet.

When to Use UDP

Use CaseWhy UDP?
DNSSingle query-response, no connection overhead
GamingLow latency critical, stale positions are useless
VoIP/VideoReal-time, tolerate drops, can’t wait for retransmit
IoT/SensorsTiny packets, constrained devices, broadcast/multicast
QUIC (HTTP/3)Builds its own reliability on top of UDP, avoids TCP head-of-line blocking
DHCPClient doesn’t have an IP yet, needs broadcast

TCP vs UDP

AspectTCPUDP
Connection3-way handshakeNone
ReliabilityGuaranteed delivery + retransmitBest-effort
OrderingSequence numbers maintain orderNo ordering
Flow controlSliding windowNone
Congestion controlAIMD, slow startNone (app must handle)
Header size20-60 bytes8 bytes
LatencyHigher (handshake + retransmit waits)Lower
Head-of-line blockingYes (one lost segment blocks stream)No

C Socket Example

Sender

#include <arpa/inet.h>
#include <unistd.h>
 
int main(void) {
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    struct sockaddr_in dest = {
        .sin_family = AF_INET,
        .sin_port = htons(9999),
    };
    inet_pton(AF_INET, "127.0.0.1", &dest.sin_addr);
 
    const char *msg = "hello";
    sendto(sock, msg, 5, 0, (struct sockaddr *)&dest, sizeof(dest));
    close(sock);
    return 0;
}

Receiver

#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
 
int main(void) {
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    struct sockaddr_in addr = {
        .sin_family = AF_INET,
        .sin_port = htons(9999),
        .sin_addr.s_addr = INADDR_ANY,
    };
    bind(sock, (struct sockaddr *)&addr, sizeof(addr));
 
    char buf[1024];
    struct sockaddr_in from;
    socklen_t fromlen = sizeof(from);
    ssize_t n = recvfrom(sock, buf, sizeof(buf), 0,
                         (struct sockaddr *)&from, &fromlen);
    buf[n] = '\0';
    printf("got: %s from %s:%d\n", buf,
           inet_ntoa(from.sin_addr), ntohs(from.sin_port));
    close(sock);
    return 0;
}

Multicast

UDP supports one-to-many with multicast groups (224.0.0.0/4):

struct ip_mreq mreq = {
    .imr_multiaddr.s_addr = inet_addr("239.0.0.1"),
    .imr_interface.s_addr = INADDR_ANY,
};
setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
// Now recvfrom() receives multicast traffic on 239.0.0.1

Used for service discovery (mDNS), streaming, and cluster coordination.

QUIC: Reliability on Top of UDP

QUIC (used by HTTP/3) builds TCP-like reliability over UDP:

  • Multiplexed streams without head-of-line blocking
  • Built-in TLS 1.3 (0-RTT connection setup)
  • Connection migration (survives IP changes, e.g., WiFi→cellular)

This avoids TCP’s limitations while keeping the benefits of UDP’s simplicity at the OS level.