UDP in Practice | Low Latency, DNS, Games, Streaming & QUIC
이 글의 핵심
UDP sends datagrams without a connection for minimal delay; reliability and ordering are application (or QUIC) concerns.
Introduction
UDP (User Datagram Protocol) is a lightweight transport that sends datagrams without establishing a connection. TCP provides reliability, ordering, and congestion control; UDP only sends—loss, duplication, and reordering are handled by the application or upper layers (QUIC, RTP, …). That makes UDP common for DNS, realtime games, VoIP/video, and monitoring where latency or multicast matters.
This article covers headers, ports, checksums, how retransmission and MTU work in practice, and how HTTP/3 / QUIC uses UDP.
After reading this post
- Describe the UDP header and IPv4 vs IPv6 checksum rules
- Implement basic datagram clients in C++, Python, and JavaScript with timeouts
- Explain why games and streaming pick UDP from latency and throughput angles
- Relate QUIC and WebRTC at a stack diagram level
Table of contents
- Protocol overview
- How it works
- Hands-on programming
- Performance characteristics
- Real-world use cases
- Optimization tips
- Common problems
- Wrap-up
Protocol overview
History and background
RFC 768 (1980) standardizes UDP—IP multiplexing via ports and optional checksumming with minimal philosophy: the internet is assumed unstable, and realtime needs a thin layer.
OSI placement
UDP is layer 4. IP reaches the host; UDP ports demux to sockets/processes. No connection state—each datagram is independent.
Core properties
| Property | Description |
|---|---|
| Connectionless | No handshake (unless the app adds one). |
| Unreliable | Drops and reordering are not repaired by UDP. |
| Datagram boundaries | Receive size often matches send (OS buffering may split recv). |
| Multicast-friendly | Easier one-to-many patterns than TCP. |
| Low overhead | 8-byte header vs TCP. |
How it works
Header (IPv4)
| Field | Size | Role |
|---|---|---|
| Source port | 16 bits | Sender port (optional). |
| Destination port | 16 bits | Receiver port. |
| Length | 16 bits | UDP header + payload length. |
| Checksum | 16 bits | Optional (0) on IPv4; mandatory on IPv6 UDP. |
Checksum
IPv4 allows checksum disabled (0); IPv6 UDP requires checksums. Uses pseudo-header + UDP + payload. For stronger guarantees, add encryption and MAC at the app or TLS layer.
Ports
0–65535 demuxes processes on a host; well-known ports (e.g. DNS 53) are IANA-registered.
QUIC relationship (2026)
HTTP/3 uses QUIC over UDP. QUIC adds encryption, congestion control, streams, and connection migration—“simple UDP” vs “TCP-like reliability” in one modern stack.
flowchart TB
subgraph app [Applications]
H3[HTTP/3]
RTP[RTP / realtime media]
DNS[DNS client]
end
subgraph mid [Example stack]
QUIC[QUIC + TLS]
UDPR[UDP socket]
end
subgraph l3 [Network]
IP[IP]
end
H3 --> QUIC
QUIC --> UDPR
RTP --> UDPR
DNS --> UDPR
UDPR --> IP
Hands-on programming
C++ (UDP client sketch)
// g++ -std=c++17 -O2 udp_client.cpp -o udp_client
#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
const char* host = argc > 1 ? argv[1] : "127.0.0.1";
const uint16_t port = argc > 2 ? static_cast<uint16_t>(std::stoi(argv[2])) : 5353;
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) { perror("socket"); return 1; }
sockaddr_in peer{};
peer.sin_family = AF_INET;
peer.sin_port = htons(port);
if (inet_pton(AF_INET, host, &peer.sin_addr) != 1) return 1;
const std::string msg = "ping";
if (sendto(fd, msg.data(), msg.size(), 0,
reinterpret_cast<sockaddr*>(&peer), sizeof(peer)) < 0) {
perror("sendto");
close(fd);
return 1;
}
char buf[2048];
socklen_t plen = sizeof(peer);
ssize_t n = recvfrom(fd, buf, sizeof(buf), 0,
reinterpret_cast<sockaddr*>(&peer), &plen);
if (n < 0) { perror("recvfrom"); close(fd); return 1; }
std::cout.write(buf, n);
std::cout << '\n';
close(fd);
return 0;
}
Python 3
#!/usr/bin/env python3
import socket
import sys
def main() -> None:
host = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1"
port = int(sys.argv[2]) if len(sys.argv) > 2 else 5353
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(3.0)
try:
sock.sendto(b"ping", (host, port))
data, addr = sock.recvfrom(4096)
print(f"from {addr}: {data!r}")
except socket.timeout:
print("timeout: no response", file=sys.stderr)
raise SystemExit(1)
finally:
sock.close()
if __name__ == "__main__":
main()
Node.js (dgram)
// node udp_client.mjs
import dgram from "node:dgram";
const host = process.argv[2] ?? "127.0.0.1";
const port = Number(process.argv[3] ?? 5353);
const socket = dgram.createSocket("udp4");
const onMessage = (msg, rinfo) => {
console.log(`from ${rinfo.address}:${rinfo.port}: ${msg.toString()}`);
socket.close();
};
socket.on("message", onMessage);
socket.on("error", (err) => {
console.error(err.message);
socket.close();
process.exitCode = 1;
});
socket.send(Buffer.from("ping"), port, host, (err) => {
if (err) {
console.error(err.message);
process.exitCode = 1;
socket.close();
}
});
setTimeout(() => {
socket.close();
console.error("timeout");
process.exitCode = 1;
}, 3000).unref();
App-level reliability
Without TCP’s ACKs, request–response over UDP needs timeouts, retransmits with IDs, dedup, and backoff—or use QUIC/RTP that embed those concerns.
Performance characteristics
Latency
No handshake—you can send the first datagram immediately (firewalls/NAT aside). Important when time-to-first-packet dominates games and voice.
Throughput
UDP has no built-in windowed congestion control—you can flood, but you should not. Bulk transfer needs app congestion control or QUIC.
Overhead
8-byte UDP header—smaller than TCP until you add app/crypto headers.
Benchmarks
Loopback may hit hundreds of thousands of pps; on the internet, loss and jitter dominate games and voice more than raw Mbps.
Real-world use cases
DNS
Most queries use UDP; large responses may fall back to TCP.
Games
Latest state often beats retransmitting old state—UDP + app synchronization is common.
Realtime streaming
RTP typically runs on UDP; loss is handled with encoding, FEC, NACK, … WebRTC uses UDP—see the WebRTC guide.
Optimization tips
Packet size (MTU)
Design IP + UDP + payload under path MTU. Avoid IP fragmentation—loss costs a whole fragment chain.
Retransmit and ordering
- Retransmit: measure RTT, handle jitter buffers, drop duplicates.
- Ordering: sequence numbers; decide whether late packets are dropped or buffered.
Fairness
Uncontrolled UDP floods can harm neighbors and trigger ISP throttling. Rate limits and congestion control are operational requirements, not niceties.
Common problems
Loss
Common on Wi‑Fi, mobile, congested links—use FEC, lower bitrate, IFrame cadence, NACK/retransmit when delay allows.
Reordering
Multipath routing can reorder—use sequence numbers and reorder buffers, or stateless designs.
NAT and firewalls
UDP is stateless—NAT may expire mappings; keepalives, STUN/TURN (WebRTC), and QUIC keepalive address this.
Wrap-up
Summary
- UDP favors low latency, simplicity, and multicast; reliability lives in the app or upper protocols.
- DNS, games, and realtime media are classic UDP domains; HTTP/3/QUIC adds modern reliability on top.
- MTU, timeouts, and retransmit policy are mandatory design topics.
When to choose UDP
- Short requests, realtime, broadcast-style workloads—or QUIC/RTP. When reliability and ordering come first, start with the TCP guide; for browser P2P realtime, see WebRTC.