TCP vs UDP 판단 기준
전달 신뢰성·순서·지연·메시지 경계의 비용을 비교하고 서비스 요구에 따라 TCP와 UDP 및 보완 전략을 선택합니다.
TCP는 손실 복구와 순서 있는 바이트 전달을 제공하고, UDP 자체는 이를 제공하지 않습니다. 연결이 실패해도 전달 완료를 보장한다는 뜻은 아닙니다.
그렇다면 TCP가 항상 더 좋은 선택일까요?
그렇지 않습니다.
신뢰성이라는 강력한 보장에는 반드시 비용이 따릅니다.
정확히 말하면 TCP는 연결 기반의 신뢰성 있는 순서 보장 바이트 스트림을 제공합니다.
손실 재전송, 순서 재조립, 흐름 제어, 혼잡 제어를 전송 계층이 맡습니다.
UDP는 최소 기능의 데이터그램입니다.
연결 설정이 없고 메시지 경계는 유지되지만, 전달 여부·순서·중복 방지는 애플리케이션이 필요에 따라 직접 처리해야 합니다.
사용 사례별 비교
구체적인 사용 사례를 통해 어떤 프로토콜이 적합한지 살펴보겠습니다.
| 사용 사례 | 선택 예와 이유 |
|---|---|
| 웹 (HTTP/1.1, HTTP/2) | 프로토콜: TCP; 이유: 문서·자산을 순서 있는 바이트 스트림으로 전달 |
| 웹 (HTTP/3) | 프로토콜: QUIC (UDP 기반); 이유: UDP 위 다중 스트림 + TLS 1.3, 연결 단위 HOL 완화 |
| 온라인 게임 (위치 동기화) | 프로토콜: UDP; 이유: 0.5초 전 위치보다 최신 위치가 중요 |
| 온라인 게임 (아이템 구매) | 프로토콜: TCP; 이유: 전송 오류와 거래 결과를 별도로 처리해야 함 |
| 영상 스트리밍 (실시간) | 프로토콜: UDP (RTP); 이유: 재생 기한 내 복구와 늦은 데이터 폐기를 선택 |
| 영상 스트리밍 (VOD) | 프로토콜: HTTP 기반 (TCP/QUIC); 이유: 버퍼링 가능, 완전한 파일 전달 필요 |
| DNS 조회 | 프로토콜: UDP; 이유: 작은 질의·응답에 흔함; TCP도 지원 |
| DNS 영역 전송 | 프로토콜: TCP; 이유: 대량 데이터, 완전성 필수 |
| VoIP (음성 통화) | 프로토콜: UDP (RTP); 이유: 재생 기한과 손실 복구 비용을 함께 고려 |
| 파일 전송 (FTP, SFTP) | 프로토콜: TCP; 이유: 1바이트 누락 = 파일 손상 |
| IoT 센서 데이터 | 프로토콜: UDP (CoAP); 이유: 작은 메시지; 확인형 메시지로 재전송도 가능 |
| 이메일 (SMTP) | 프로토콜: TCP; 이유: 메일 내용 완전 전달 필수 |
| NTP (시간 동기화) | 프로토콜: UDP; 이유: 작은 패킷, 연결 불필요 |
| DHCP | 프로토콜: UDP; 이유: DHCPv4 초기 탐색에서 미설정 주소와 브로드캐스트 사용 |
DNS는 “조회는 UDP, 영역 전송은 TCP” 정도로 외우면 입문 단계에서는 편하지만, 실제 DNS 구현은 TCP 지원도 중요합니다.
응답이 잘리거나 DNSSEC처럼 응답 크기가 커지는 경우, 또는 영역 전송처럼 완전성이 필요한 경우 TCP가 사용됩니다.
지연 민감도 vs 신뢰성 트레이드오프
패턴이 보이시나요?
핵심은 지연 민감도(Latency Sensitivity)와 신뢰성 요구 수준의 트레이드오프입니다.
TCP의 재전송은 손실 복구를 지원하지만, 복구를 기다리는 지연이 발생합니다.
더 큰 문제는 Head-of-Line(HOL) Blocking입니다.
TCP는 하나의 순서 있는 바이트 스트림이기 때문에, 중간에 하나의 세그먼트가 손실되면 그 뒤에 도착한 데이터도 애플리케이션에 바로 전달되지 못하고 대기해야 합니다.
실시간 서비스에서는 이 HOL Blocking이 치명적입니다.
한 프레임의 손실 때문에 이후의 모든 프레임이 지연되는 것보다, 손실된 프레임을 건너뛰고 다음 프레임을 표시하는 것이 사용자 경험에 훨씬 유리합니다.
판단 기준을 정리하면 이렇습니다.
-
데이터 완전성이 필수이면 → TCP
-
실시간성이 완전성보다 중요하면 → UDP
-
작은 요청-응답이고 재시도가 단순하면 → UDP (연결 오버헤드 제거)
-
순서와 완전성이 필요한 스트림이면 → TCP 또는 QUIC
-
프레임 단위 최신성이 더 중요하면 → UDP 또는 애플리케이션 레벨 UDP
-
방화벽·프록시·기업망에서 UDP가 제한될 수 있으면 → TCP/HTTPS 기반 fallback도 함께 설계
애플리케이션 레벨에서 신뢰성 보완하기
UDP를 선택했지만 어느 정도의 신뢰성이 필요한 경우가 많습니다.
메시지별로 재시도 횟수와 도착 기한을 다르게 둘 수 있습니다.
이때는 애플리케이션 레벨에서 필요한 만큼만 신뢰성을 구현합니다.
import socket
import struct
import time
import threading
class ReliableUDP:
"""부분적 신뢰성을 가진 UDP 구현"""
# 메시지 타입
UNRELIABLE = 0 # ACK 불필요 (위치 업데이트 등)
RELIABLE = 1 # ACK 필요 (아이템 획득 등)
ACK = 2 # 확인 응답
def __init__(self, port):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.bind(("0.0.0.0", port))
self.seq = 0
self.pending_acks = {} # seq → (data, addr, timestamp, retries)
self.max_retries = 3
self.retry_interval = 0.5 # 500ms
def send_unreliable(self, data, addr):
"""신뢰성 불필요 메시지 (위치, 이펙트 등)"""
# 헤더: [type(1B)][seq(4B)]
packet = struct.pack("!BI", self.UNRELIABLE, self.seq) + data
self.sock.sendto(packet, addr)
self.seq += 1
def send_reliable(self, data, addr):
"""신뢰성 필요 메시지 (아이템, 채팅 등)"""
seq = self.seq
packet = struct.pack("!BI", self.RELIABLE, seq) + data
self.sock.sendto(packet, addr)
self.pending_acks[seq] = (packet, addr, time.time(), 0)
self.seq += 1
return seq
def send_ack(self, seq, addr):
"""ACK 전송"""
packet = struct.pack("!BI", self.ACK, seq)
self.sock.sendto(packet, addr)
def check_retransmits(self):
"""재전송 확인 (타이머 스레드에서 호출)"""
now = time.time()
to_remove = []
for seq, (packet, addr, timestamp, retries) in self.pending_acks.items():
if now - timestamp > self.retry_interval:
if retries >= self.max_retries:
print(f"[WARN] seq={seq} 전달 실패 (재시도 {retries}회)")
to_remove.append(seq)
else:
self.sock.sendto(packet, addr)
self.pending_acks[seq] = (packet, addr, now, retries + 1)
print(f"[RETRY] seq={seq} 재전송 ({retries + 1}회)")
for seq in to_remove:
del self.pending_acks[seq]
def receive(self):
"""메시지 수신 및 처리"""
data, addr = self.sock.recvfrom(65535)
msg_type, seq = struct.unpack("!BI", data[:5])
payload = data[5:]
if msg_type == self.ACK:
if seq in self.pending_acks:
del self.pending_acks[seq]
return None, None, addr
if msg_type == self.RELIABLE:
self.send_ack(seq, addr)
return msg_type, payload, addr
# 사용 예시:
# server = ReliableUDP(9000)
#
# 위치 업데이트 (손실 허용):
# server.send_unreliable(position_data, player_addr)
#
# 아이템 획득 (손실 불가):
# server.send_reliable(item_data, player_addr)재전송 예제가 처리하는 것과 남기는 것의 비교 기준과 해석 범위입니다.
| 코드 경로 | 현재 동작 | 보장하지 않는 것 |
|---|---|---|
| send_reliable | 보낸 패킷을 pending_acks에 저장 | 최대 재시도 뒤에도 전달되지 않을 수 있음 |
| receive: 데이터 | RELIABLE이면 ACK를 보내고 payload 반환 | 같은 seq 재수신도 다시 반환; 중복 실행 방지 없음 |
| receive: ACK | 같은 seq의 대기 항목 삭제 | 보낸 상대 주소를 대조하지 않음; 업무 처리 완료 증거 아님 |
| 타이머·수신 루프 | 호출자가 check_retransmits/receive를 실행해야 함 | dict 동시 변경 보호·수신 기한·혼잡 제어 없음 |
- send_reliable
- 현재 동작: 보낸 패킷을 pending_acks에 저장보장하지 않는 것: 최대 재시도 뒤에도 전달되지 않을 수 있음
- receive: 데이터
- 현재 동작: RELIABLE이면 ACK를 보내고 payload 반환보장하지 않는 것: 같은 seq 재수신도 다시 반환; 중복 실행 방지 없음
- receive: ACK
- 현재 동작: 같은 seq의 대기 항목 삭제보장하지 않는 것: 보낸 상대 주소를 대조하지 않음; 업무 처리 완료 증거 아님
- 타이머·수신 루프
- 현재 동작: 호출자가 check_retransmits/receive를 실행해야 함보장하지 않는 것: dict 동시 변경 보호·수신 기한·혼잡 제어 없음
코드 검토에 따른 범위입니다. 패킷 손실·재전송·중복 처리를 실행해 관측한 결과는 아닙니다.
이 코드는 ACK와 제한된 재전송의 구조만 보여주는 학습용 예제입니다. 전체 프로토콜 구현은 아닙니다.
위치 업데이트는 ACK 없이 보내고(어차피 곧 새 위치가 올 것이므로), 스킬 사용이나 아이템 획득 같은 이벤트는 ACK를 요구합니다.
이런 부분적 신뢰성을 제공하는 라이브러리(ENet, RakNet 등)도 존재합니다.
필요한 보장과 기한을 정한 뒤 검증된 전송 라이브러리의 동작과 대조해야 합니다.
TCP vs UDP 면접 정리
| 질문 | 핵심 답변 |
|---|---|
| TCP와 UDP의 차이는? | TCP는 연결/신뢰/순서 보장, UDP는 비연결/비신뢰/최소 오버헤드 |
| UDP를 왜 사용하나요? | 실시간성 > 신뢰성인 경우 (게임, VoIP, 스트리밍) |
| TCP의 HOL Blocking이란? | 하나의 손실이 전체 스트림을 지연시키는 현상 |
| 게임에서 TCP/UDP 동시 사용? | 위치/이펙트는 UDP, 로그인/아이템은 TCP |
| DNS가 UDP를 쓰는 이유? | 작은 질의-응답, 연결 오버헤드가 데이터보다 큼 |
| UDP에서 신뢰성 구현? | 애플리케이션 레벨 ACK/재전송 (부분적 신뢰성) |
| QUIC은 TCP인가 UDP인가? | UDP 위에서 TLS 1.3, 다중 스트림, 흐름/혼잡 제어를 제공하는 전송 프로토콜 |
다음 절에서는 UDP 기반이면서도 TCP의 장점까지 통합한 차세대 프로토콜 QUIC을 살펴보겠습니다.