본문 바로가기

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는 하나의 순서 있는 바이트 스트림이기 때문에, 중간에 하나의 세그먼트가 손실되면 그 뒤에 도착한 데이터도 애플리케이션에 바로 전달되지 못하고 대기해야 합니다.

재생·처리 기한이 짧은 실시간 서비스에서는 손실 복구 대기가 그 기한을 넘기는지 판단해야 합니다.

한 프레임의 손실 때문에 이후의 모든 프레임이 지연되는 것보다, 손실된 프레임을 건너뛰고 다음 프레임을 표시하는 것이 사용자 경험에 훨씬 유리합니다.

판단 기준을 정리하면 이렇습니다.

  • 데이터 완전성이 필수이면 → TCP

  • 실시간성이 완전성보다 중요하면 → UDP

  • 작은 요청-응답이고 재시도가 단순하면 → UDP (연결 오버헤드 제거)

  • 순서와 완전성이 필요한 스트림이면 → TCP 또는 QUIC

  • 프레임 단위 최신성이 더 중요하면 → UDP 또는 애플리케이션 레벨 UDP

  • 방화벽·프록시·기업망에서 UDP가 제한될 수 있으면 → TCP/HTTPS 기반 fallback도 함께 설계


애플리케이션 레벨에서 신뢰성 보완하기

UDP를 선택했지만 어느 정도의 신뢰성이 필요한 경우가 많습니다.

메시지별로 재시도 횟수와 도착 기한을 다르게 둘 수 있습니다.

이때는 애플리케이션 레벨에서 필요한 만큼만 신뢰성을 구현합니다.

reliable_udp.py
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)

재전송 예제가 처리하는 것과 남기는 것

순번 7을 받아 ACK하고 payload를 반환합니다. 뒤늦게 ACK를 받으면 대기 항목을 제거합니다. 각 단계의 값과 전환 이유를 비교합니다.

UDP · 학습용 재전송

ACK와 재시도만으로 중복 실행이나 업무 완료가 보장되지는 않는다

본문 코드는 ACK와 제한된 재전송의 구조를 보여줍니다. 패킷 재수신·상대 검증·동시 접근·기한·혼잡 제어의 책임은 별도로 남아 있습니다.

ACK 유실 뒤 같은 payload가 다시 반환되는 원 코드같은seq7데이터가도착하여ACK를보내지만첫ACK만유실된다. 송신쪽대기항목이남아타이머검사뒤같은데이터를재전송한다. 원코드는중복seq를억제하지않아payload를두번반환한다. ACK는업무처리완료를뜻하지않는다.RELIABLEseq=7ACK 유실같은 데이터seq=7ACK 도착송신자수신자payload반환payload반환재시도 검사0.5초 초과
ACK 유실 뒤 같은 payload가 다시 반환되는 원 코드같은seq7데이터가도착하여ACK를보내지만첫ACK만유실된다. 송신쪽대기항목이남아타이머검사뒤같은데이터를재전송한다. 원코드는중복seq를억제하지않아payload를두번반환한다. ACK는업무처리완료를뜻하지않는다.RELIABLEseq=7ACK 유실같은 데이터seq=7ACK 도착송신자수신자payload반환payload반환재시도 검사0.5초 초과
재전송 예제가 처리하는 것과 남기는 것
코드 경로현재 동작보장하지 않는 것
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 수신까지 진행합니다.
관계도의 보충 설명
전체 도표의 표기·관계·조건
구분전체 도표의 내용
관계 설명같은 seq 7 데이터가 도착하여 ACK를 보내지만 첫 ACK만 유실됩니다. 송신 쪽 대기 항목이 남아 타이머 검사 뒤 같은 데이터를 재전송합니다. 원 코드는 중복 seq를 억제하지 않아 payload를 두 번 반환합니다. ACK는 업무 처리 완료를 뜻하지 않습니다.
표의 값send_reliable — 현재 동작: 보낸 패킷을 pending_acks에 저장; 보장하지 않는 것: 최대 재시도 뒤에도 전달되지 않을 수 있음. receive: 데이터 — 현재 동작: RELIABLE이면 ACK를 보내고 payload 반환; 보장하지 않는 것: 같은 seq 재수신도 다시 반환; 중복 실행 방지 없음. receive: ACK — 현재 동작: 같은 seq의 대기 항목 삭제; 보장하지 않는 것: 보낸 상대 주소를 대조하지 않음; 업무 처리 완료 증거 아님. 타이머 / 수신 루프 — 현재 동작: 호출자가 check_retransmits와 receive를 실행해야 함; 보장하지 않는 것: dict 동시 변경 보호·수신 기한·혼잡 제어 없음.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
첫 전달

순번 7을 받아 ACK하고 payload를 반환합니다.

  • 순번·수신값 1: seq7 pending
  • 순번·수신값 2: payload 반환 1
  • ACK 송신은 payload 반환보다 먼저; 업무 완료 확인은 아님
ACK 유실

첫 ACK가 유실돼 송신 대기 항목이 남습니다.

  • 순번·수신값 1: pending 7
  • 순번·수신값 2: 첫 ACK 손실
  • 순번 7을 pending으로 유지
재시도 검사

호출자가 재시도 간격 초과를 검사합니다.

  • 순번·수신값 1: elapsed>0.5s
  • 순번·수신값 2: seq 7 동일
  • elapsed > 0.5초일 때 같은 순번 재전송; 정시 자동 실행은 아님
중복 수신

예제 코드는 순번 중복을 억제하지 않습니다.

  • 순번·수신값 1: payload 반환 2
  • 순번·수신값 2: ACK 재전송
  • ACK를 다시 송신한 뒤 payload를 두 번째로 반환; 호출자가 항목을 두 번 추가할 수 있음
ACK 수신

뒤늦게 ACK를 받으면 대기 항목을 제거합니다.

  • 순번·수신값 1: pending 7 없음
  • 상대 주소 검증은 없음; 재시도 3번 소진 시 총 4회 전송으로 종료

재전송 예제가 처리하는 것과 남기는 것

순번 7을 받아 ACK하고 payload를 반환합니다. 뒤늦게 ACK를 받으면 대기 항목을 제거합니다. 각 단계의 값과 전환 이유를 비교합니다.

UDP · 학습용 재전송

ACK와 재시도만으로 중복 실행이나 업무 완료가 보장되지는 않는다

본문 코드는 ACK와 제한된 재전송의 구조를 보여줍니다. 패킷 재수신·상대 검증·동시 접근·기한·혼잡 제어의 책임은 별도로 남아 있습니다.

ACK 유실 뒤 같은 payload가 다시 반환되는 원 코드같은seq7데이터가도착하여ACK를보내지만첫ACK만유실된다. 송신쪽대기항목이남아타이머검사뒤같은데이터를재전송한다. 원코드는중복seq를억제하지않아payload를두번반환한다. ACK는업무처리완료를뜻하지않는다.RELIABLEseq=7ACK 유실같은 데이터seq=7ACK 도착송신자수신자payload반환payload반환재시도 검사0.5초 초과
ACK 유실 뒤 같은 payload가 다시 반환되는 원 코드같은seq7데이터가도착하여ACK를보내지만첫ACK만유실된다. 송신쪽대기항목이남아타이머검사뒤같은데이터를재전송한다. 원코드는중복seq를억제하지않아payload를두번반환한다. ACK는업무처리완료를뜻하지않는다.RELIABLEseq=7ACK 유실같은 데이터seq=7ACK 도착송신자수신자payload반환payload반환재시도 검사0.5초 초과
재전송 예제가 처리하는 것과 남기는 것
코드 경로현재 동작보장하지 않는 것
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 수신까지 진행합니다.

관계도의 보충 설명

전체 도표의 표기·관계·조건
구분전체 도표의 내용
관계 설명같은 seq 7 데이터가 도착하여 ACK를 보내지만 첫 ACK만 유실됩니다. 송신 쪽 대기 항목이 남아 타이머 검사 뒤 같은 데이터를 재전송합니다. 원 코드는 중복 seq를 억제하지 않아 payload를 두 번 반환합니다. ACK는 업무 처리 완료를 뜻하지 않습니다.
표의 값send_reliable — 현재 동작: 보낸 패킷을 pending_acks에 저장; 보장하지 않는 것: 최대 재시도 뒤에도 전달되지 않을 수 있음. receive: 데이터 — 현재 동작: RELIABLE이면 ACK를 보내고 payload 반환; 보장하지 않는 것: 같은 seq 재수신도 다시 반환; 중복 실행 방지 없음. receive: ACK — 현재 동작: 같은 seq의 대기 항목 삭제; 보장하지 않는 것: 보낸 상대 주소를 대조하지 않음; 업무 처리 완료 증거 아님. 타이머 / 수신 루프 — 현재 동작: 호출자가 check_retransmits와 receive를 실행해야 함; 보장하지 않는 것: dict 동시 변경 보호·수신 기한·혼잡 제어 없음.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
첫 전달

순번 7을 받아 ACK하고 payload를 반환합니다.

  • 순번·수신값 1: seq7 pending
  • 순번·수신값 2: payload 반환 1
  • ACK 송신은 payload 반환보다 먼저; 업무 완료 확인은 아님
ACK 유실

첫 ACK가 유실돼 송신 대기 항목이 남습니다.

  • 순번·수신값 1: pending 7
  • 순번·수신값 2: 첫 ACK 손실
  • 순번 7을 pending으로 유지
재시도 검사

호출자가 재시도 간격 초과를 검사합니다.

  • 순번·수신값 1: elapsed>0.5s
  • 순번·수신값 2: seq 7 동일
  • elapsed > 0.5초일 때 같은 순번 재전송; 정시 자동 실행은 아님
중복 수신

예제 코드는 순번 중복을 억제하지 않습니다.

  • 순번·수신값 1: payload 반환 2
  • 순번·수신값 2: ACK 재전송
  • ACK를 다시 송신한 뒤 payload를 두 번째로 반환; 호출자가 항목을 두 번 추가할 수 있음
ACK 수신

뒤늦게 ACK를 받으면 대기 항목을 제거합니다.

  • 순번·수신값 1: pending 7 없음
  • 상대 주소 검증은 없음; 재시도 3번 소진 시 총 4회 전송으로 종료

이 코드는 ACK와 제한된 재전송의 구조만 보여주는 학습용 예제입니다. 전체 프로토콜 구현은 아닙니다.

위치 업데이트는 ACK 없이 보내고(어차피 곧 새 위치가 올 것이므로), 스킬 사용이나 아이템 획득 같은 이벤트는 ACK를 요구합니다.

이런 부분적 신뢰성을 제공하는 라이브러리(ENet, RakNet 등)도 존재합니다.

원 코드에서 ACK가 없어지는 경우: seq=7의 RELIABLE 데이터가 수신됐고 receive()가 ACK를 보냈지만 그 ACK만 손실됐다고 합시다. 송신 측에는 대기 항목이 남습니다. 호출자가 나중에 check_retransmits()를 실행해 0.5초 초과를 확인하면 같은 seq=7을 다시 보내며, 수신 측은 그 payload를 다시 반환합니다. 이미 받은 seq를 기억해 중복을 억제하는 처리가 없기 때문입니다. ACK는 receive() 안에서 payload 반환보다 먼저 전송하므로 업무 로직이 성공했다고 확인하는 응답도 아닙니다.

독립 추적: 수신 측 호출자가 반환된 payload마다 아이템을 하나 추가했습니다. 위 조건에서 최초 수신과 재전송을 모두 처리하면 몇 개가 추가될까요? 이어 ACK 없이 첫 전송 뒤 재시도 세 번까지 모두 소진한 원 코드에서 총 전송 횟수와 마지막 대기 항목의 처리를 설명하세요. 호출자는 타이머 검사를 계속 실행하며 다른 동시 변경은 없습니다.

재시도와 중복 실행을 따로 세기

같은 payload가 두 번 반환되므로 그 호출자라면 아이템도 두 번 추가합니다. 재시도는 원래 전송과 별개로 최대 세 번이어서 총 전송은 네 번입니다. 세 번째 재시도 뒤에도 ACK가 없고 다음 검사에서 간격을 초과하면 경고하고 대기 항목을 삭제하며 다섯 번째 전송은 하지 않습니다. 원문의 “손실 불가” 주석은 요구를 나타낼 뿐 이 코드가 최종 전달이나 정확히 한 번의 업무 실행을 보장한다는 뜻은 아닙니다. 요청 식별자와 업무 결과의 중복 처리 규칙을 별도로 설계해야 합니다.

원 조건식은 >이므로 시간 차가 정확히 0.5초일 때는 재시도하지 않습니다. 호출 주기와 스케줄링도 있어 “0.5초마다 정확히 자동 실행”하는 코드가 아닙니다. ACK의 상대 주소를 검증하지 않고 5바이트 미만 입력도 검사하지 않는 등 표의 책임이 남아 있으므로 이 조각을 완성된 운영용 전송 계층으로 사용하지 않습니다.

필요한 보장과 기한을 정한 뒤 검증된 전송 라이브러리의 동작과 대조해야 합니다.


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을 살펴보겠습니다.