UDP 프로토콜 실전 활용 | 저지연·DNS·게임·스트리밍과 QUIC 연결

이 글의 핵심

도메인별 UDP 채택 이유와 반례, QUIC·HTTP/3의 내부(연결 ID·TLS 1.3·혼잡 제어)와 TCP 대비 트레이드오프를 정리합니다.

들어가며

UDP(User Datagram Protocol)는 연결 설정 없이 데이터그램을 보내는 경량 전송 프로토콜입니다. TCP가 신뢰성·순서·혼잡 제어를 대신 책임지는 대신, UDP는 “보내기만” 하고 손실·중복·순서 뒤집힘은 애플리케이션(또는 그 위의 QUIC, RTP 등)이 처리합니다. 따라서 DNS, 실시간 게임, VoIP/영상, 모니터링 에이전트처럼 지연 민감이거나 멀티캐스트가 필요한 영역에서 자주 선택됩니다. 이 글은 헤더·포트·체크섬 같은 기본을 짚은 뒤, 재전송·순서·MTU를 실무에서 어떻게 다루는지, 그리고 HTTP/3·QUIC이 UDP를 어떻게 쓰는지까지 연결합니다.


프로토콜 개요

역사 및 개발 배경

UDP는 RFC 768(1980)으로 표준화되었으며, IP 위의 멀티플렉싱(포트)과 최소한의 무결성(체크섬)을 제공하는 단순함이 철학입니다. “인터넷은 원래 불안정하다”는 전제 위에서, 실시간성이나 브로드캐스트/멀티캐스트 같은 요구는 가벼운 UDP에 맞기 쉽습니다.

OSI 7계층에서의 위치

UDP는 4계층(전송 계층)에 속합니다. IP가 호스트까지 배달하면, UDP 포트가 소켓(프로세스)으로 연결됩니다. 연결 상태는 없으며, 각 데이터그램은 독립적입니다.

핵심 특징

특징설명
비연결형송신 전 핸드셰이크가 없음(앱이 필요하면 직접 구현).
비신뢰성네트워크가 드롭·순서 변경을 일으켜도 UDP는 복구하지 않습니다.
데이터그램 경계한 번의 sendto가 한 번의 recvfrom에 그대로 대응합니다. 수신 버퍼가 데이터그램보다 작으면 나머지는 다음 recv로 넘어오지 않고 잘려서 버려집니다(Linux에서는 MSG_TRUNC로 감지).
멀티캐스트 가능성TCP와 달리 일대다 전송에 활용하기 쉬운 모델입니다.
오버헤드 작음8바이트 헤더로 TCP보다 가볍습니다.

동작 원리

헤더 구조(IPv4)

UDP 헤더는 8바이트입니다.

필드길이설명
Source Port16비트송신 포트(선택).
Destination Port16비트수신 포트.
Length16비트UDP 헤더+페이로드 길이.
Checksum16비트IPv4에서는 선택(0이면 미사용), IPv6에서는 의무.

체크섬

IPv4에서는 체크섬이 선택이지만, IPv6 UDP에서는 의무입니다. 체크섬은 의사 헤더 + UDP + 페이로드를 포함해 계산됩니다. 애플리케이션 무결성이 더 중요하면 추가로 암호화·MAC을 씁니다.

포트

0~65535 범위의 포트로 호스트 내 프로세스를 구분합니다. 잘 알려진 포트(예: DNS 53)는 IANA 레지스트리에 등록되어 있습니다.

QUIC과의 관계(2026년 기준)

HTTP/3는 QUIC(UDP 위)을 사용합니다. QUIC은 암호화·혼잡 제어·다중 스트림·연결 마이그레이션을 UDP 데이터그램으로 캡슐화해, “UDP의 단순함”과 “TCP에 가까운 신뢰성” 사이의 현대적 절충을 제공합니다.

flowchart TB
  subgraph app [애플리케이션]
    H3[HTTP/3]
    RTP[RTP/실시간 미디어]
    DNS[DNS 클라이언트]
  end
  subgraph mid [프로토콜 스택 예]
    QUIC[QUIC + TLS]
    UDPR[UDP 소켓]
  end
  subgraph l3 [네트워크]
    IP[IP]
  end
  H3 --> QUIC
  QUIC --> UDPR
  RTP --> UDPR
  DNS --> UDPR
  UDPR --> IP

도메인별 용도와 설계 반례

UDP는 “빠르다”기보다 연결·혼잡 상태 기계를 피하고, 손실·순서·중복을 애플리케이션 정책으로 흡수할 수 있을 때 선택됩니다. 아래는 대표 도메인과 TCP가 더 나은 경우를 구분한 것입니다.

짧은 요청·응답: DNS·DHCP·NTP

DNS는 질의 한 번에 응답 한 번인 경우가 많아 연결 부담이 큰 TCP보다 UDP가 기본입니다. 응답이 크면 TCP 폴백(또는 TCP 우선)으로 전환하는 패턴이 흔합니다. DHCP·NTP도 브로드캐스트/멀티캐스트·상태 없는 트랜잭션에 가깝으며, 시간 동기는 약간의 손실보다 최신 샘플의 도착이 더 중요한 경우가 많습니다.

실시간 미디어: RTP·VoIP·게임

RTP는 보통 UDP 위에서 시퀀스·타임스탬프로 재생 시점을 맞추고, 늦게 도착한 프레임은 버리는 쪽이 낫습니다. VoIP도 유사합니다. 게임은 최신 월드 상태가 우선이므로 과거 패킷 재전송이 이미 stale인 경우가 많아, UDP + 상태 동기화가 자연스럽습니다. 손실은 인코딩·FEC·선택적 재전송으로 흡수하며, 브라우저의 WebRTC 미디어도 이 방식으로 UDP를 씁니다(WebRTC 가이드 참고). 반대로 파일 다운로드·결제·감사 로그처럼 바이트 무결성이 절대적이면 TCP(또는 QUIC)가 맞습니다.

관측·에이전트: Syslog·statsd·샘플링 메트릭

고빈도·소형 이벤트를 유실 허용하며 보낼 때 UDP가 사용됩니다. 유실률은 알림 채널과 집계 로직에서 상쇄합니다. 정확히 한 번 전달이 필요하면 TCP·메시지 큐로 옮겨야 합니다.

멀티캐스트·로컬 탐색

mDNS/Bonjour 등 로컬 링크 탐색은 UDP의 1:N 모델과 잘 맞습니다. WAN 멀티캐스트는 인프라 제약이 커서 애플리케이션 레벨 오버레이가 더 흔합니다.

반례: UDP가 “무조건” 좋지 않은 이유

NAT·방화벽 환경에서 UDP는 막히거나 타임아웃되기 쉽으며, MTU·단편화 이슈가 앱까지 그대로 올라옵니다. 대용량·공정한 대역 공유가 필요하면 혼잡 제어 없이 UDP를 쏘는 것은 인접 TCP 세션을 기아시키고 운영자에게 스로틀을 당하기 쉽습니다.

QUIC·HTTP/3와의 관계 심화

QUIC은 “UDP를 아무렇게나 쓴다”가 아니라, UDP 데이터그램에 상태 있는 연결·암호화·신뢰성·혼잡 제어를 사용자 공간+커널 UDP 조합으로 구현한 전송 프로토콜입니다. HTTP/3는 QUIC 위의 HTTP 의미론입니다.

왜 TCP가 아니라 UDP 위인가

TCP/UDP 이중 스택에서 새 전송 혁신을 넣으려면 OS 커널·중간 박스의 배포 주기가 병목이 됩니다. UDP는 이미 열려 있는 포트(예: 443/UDP)로 암호화된 QUIC 패킷을 실어 보내면, 브라우저·CDN이 빠르게 반복할 수 있는 진화 경로가 됩니다(물론 방화벽이 UDP를 막는 현장도 있어 HTTP/2 폴백이 필요합니다).

연결 ID(Connection ID)와 경로 변경

TCP는 4-tuple(src IP/port, dst IP/port)로 연결을 식별합니다. Wi‑Fi↔셀룰러 전환처럼 IP가 바뀌면 연결이 끊깁니다. QUIC은 연결 ID로 논리적 연결을 유지하려 하여 경로 마이그레이션에 유리합니다(구현·정책에 따라 제한).

TLS 1.3과의 결합

QUIC은 암호화가 프로토콜에 내장되어 있으며, TLS 1.3의 요소(예: 핸드셰이크·키 유도)를 통합합니다. “UDP라서 안전하지 않다”가 아니라, 첫 바이트부터 암호화·인증을 전제로 설계되었습니다. 세부는 TLS 프로토콜 가이드를 참고하세요.

스트림·HOL 블로킹

HTTP/2 over TCP는 한 연결에서 멀티플렉싱하지만, 패킷 손실 시 TCP의 바이트 순서 복구 때문에 다른 스트림까지 지연될 수 있는 TCP HOL 블로킹이 있습니다. QUIC은 스트림마다 순서를 따로 복구하므로, 한 패킷이 손실되면 그 패킷에 실린 스트림만 재전송을 기다리고 다른 스트림의 데이터는 계속 애플리케이션에 전달됩니다. 다만 혼잡 제어 윈도와 연결 수준 흐름 제어는 모든 스트림이 공유합니다.

혼잡 제어와 UDP “무제한 송신”의 차이

QUIC 구현체는 TCP와 유사한 혼잡 제어(예: CUBIC, BBR 계열 등)를 알고리즘으로 포함합니다. 따라서 “UDP=혼잡 무시”는 QUIC에 해당하지 않습니다. 오히려 앱이 직접 UDP 소켓을 열어 무제한 전송할 때가 문제입니다.

TCP·TLS와의 스택 비교(요약)

측면TCP+TLSQUIC(UDP)
연결 식별4-tuple연결 ID + 4-tuple 등
암호화 결합TLS가 상위에서 핸드셰이크TLS 1.3 요소 통합·기본 암호화
혼잡 제어커널 TCP 스택주로 사용자 공간 QUIC 구현체
경로 변경어려움연결 ID로 완화 시도

TCP 심화는 TCP 동작 원리와 짝을 이룹니다.


실전 프로그래밍

C++ (UDP echo 클라이언트 스케치)

// g++ -std=c++17 -O2 udp_client.cpp -o udp_client
#include <arpa/inet.h>
#include <sys/time.h>
#include <cstdint>
#include <cstdio>
#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; }
  timeval tv{3, 0};  // 응답이 없을 수 있으므로 수신 타임아웃 3초
  setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
  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; }  // 타임아웃이면 EAGAIN
  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()

UDP에서는 응답이 오지 않는 것이 정상적인 시나리오이므로, settimeout(C++ 예제에서는 SO_RCVTIMEO)으로 대기 상한을 반드시 둡니다.

JavaScript (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();

에러 처리·재전송 로직(애플리케이션 레벨)

UDP는 ACK가 없으므로, 요청-응답 패턴이면 일정 시간 내 미수신 시 재전송, 요청 ID로 중복 제거, 지수 백오프를 앱에서 구현합니다. QUIC/RTP는 이런 역할을 프로토콜이 대신합니다.


성능 특성

지연 시간(Latency)

핸드셰이크가 없어 첫 데이터그램을 즉시 보낼 수 있습니다(방화벽·NAT 제외). 실시간 게임·음성에서 “첫 패킷까지의 시간”이 중요할 때 유리합니다.

처리량(Throughput)

UDP 자체는 윈도 기반 혼잡 제어가 없어 무제한으로 쏠 수 있지만, 그렇게 하면 네트워크와 다른 사용자에게 피해를 줍니다. 대용량 전송은 애플리케이션 혼잡 제어 또는 QUIC을 고려해야 합니다.

오버헤드

8바이트 UDP 헤더로 TCP보다 작습니다. 다만 애플리케이션 헤더·암호화가 붙으면 차이는 줄어듭니다.

벤치마크 참고

루프백에서 측정한 pps는 커널과 CPU의 한계만 보여 줄 뿐이고, 인터넷 구간에서는 MTU·손실·QoS 때문에 처리량보다 패킷 손실률이 서비스 품질을 가릅니다. 게임/음성은 pps(초당 패킷 수)와 지터가 중요합니다.


최적화 팁

패킷 크기(MTU)

이더넷 MTU 1500 기준으로 IP+UDP+페이로드가 경로 MTU를 넘지 않게 설계합니다. IP 단편화가 일어나면 조각 하나만 잃어도 데이터그램 전체가 버려지고, 단편을 걸러 내는 방화벽도 있어 피하는 편이 좋습니다.

재전송·순서

  • 재전송: RTT 측정, 지터 버퍼, 중복 시퀀스 무시를 함께 설계합니다.
  • 순서: 시퀀스 번호를 두며, 늦게 도착한 패킷은 버리거나 버퍼링할지 정책을 명확히 합니다.

대역폭 공정성

UDP로 무제한 flood하면 ISP·데이터센터에서 스로틀되거나 인접 TCP 세션을 기아 상태로 만들 수 있습니다. 레이트 리밋과 혼잡 제어는 시민 의식 수준을 넘어 실제 서비스 안정성 문제입니다.


흔한 문제와 해결

흔한 실수와 해결

실수결과해결
UDP에 TCP처럼 재전송이 있다고 가정앱이 손실을 복구하지 않음요청-응답이면 타임아웃·재시도·ID 중복 제거 설계
타임아웃 없이 recv 대기스레드·이벤트 루프가 영구 대기settimeout 등으로 상한 설정
큰 페이로드를 한 번에 전송경로 MTU·단편화 이슈앱 단위 분할과 수신 쪽 재조립
UDP만 열어두고 HTTP/3가 안 됨방화벽이 UDP 443 차단인프라 정책과 폴백(HTTP/2) 확인

패킷 손실

Wi‑Fi·모바일·혼잡 링크에서 흔합니다. FEC, 낮은 비트레이트, I-frame 주기 조정, NACK/재전송(지연 허용 시) 등을 선택합니다.

순서 뒤바뀜

멀티패스·라우팅 변화로 발생할 수 있습니다. 시퀀스 번호와 재정렬 버퍼로 해결하거나, 순서 무관 프로토콜로 설계합니다.

NAT·방화벽

UDP는 연결 상태가 없어 NAT가 타임아웃으로 매핑을 지우면 외부에서 역방향 패킷이 실패합니다. 주기적 keepalive, STUN/TURN(WebRTC), QUIC의 연결 유지가 관련됩니다.


프로덕션 소켓·커널·관측 (Linux 중심)

송수신 버퍼

고속 패킷 처리 시 커널 UDP 소켓 버퍼가 부족하면 recv 경로에서 드롭이 늘고, 애플리케이션은 간헐적 손실만 보게 됩니다. sysctl의 net.core.rmem_max·net.core.wmem_max와 소켓별 SO_RCVBUF/SO_SNDBUF를 워크로드에 맞춰 조정합니다. 변경은 부하 테스트와 함께 적용하며, 컨테이너에서는 호스트·네임스페이스 한도를 함께 봅니다.

SO_REUSEPORT와 수평 확장

동일 포트에 여러 프로세스/스레드를 붙일 때 커널이 패킷을 분배해 주는 기능으로, 멀티코어에서 수신 병목을 줄이는 데 유용합니다. 다만 연결 추적(stateful) 설계와는 다른 개념이므로, DNS·게임·메트릭 에이전트처럼 무상태 데이터그램에 맞는지 검토합니다.

관측 지표

  • netstat -su / nstat: UDP 오류·버퍼 스냅샷
  • dropwatch / perf: 커널 드롭 경로
  • 애플리케이션: 시퀀스 갭·지터·RTT 샘플을 메트릭으로 노출

이 지표는 “UDP가 빠르다”가 아니라 “시스템이 감당 가능한 pps/바이트”를 정의하는 데 도움이 됩니다.


같이 보면 좋은 글