안녕하세요, 자바파커입니다.
"TCP는 신뢰성 있고, UDP는 신뢰성 없다." 면접 준비하면서 다들 이렇게 외우셨을 겁니다. 그런데 이상하지 않으세요? 그렇게 좋은 TCP를 두고, 영상통화나 게임은 왜 UDP를 쓸까요? 다 도착하는 쪽이 더 좋은 거 아닌가요?
결론부터 말씀드리면 — TCP는 "전부, 순서대로"를 약속하고, 그 대가로 기다립니다. UDP는 아무것도 약속하지 않는 대신 기다리지 않습니다. 영상통화에서는 0.4초 멈추는 것보다 한 프레임 깨지는 게 낫기 때문에 UDP를 고릅니다.
말로만 하면 감이 안 와서, 제 PC에서 컨테이너 두 개 사이에 **패킷 손실 5%**를 걸고 TCP와 UDP로 메시지 1,000개씩 보내 봤습니다. TCP는 1,000개가 전부 도착했지만 가끔 최대 0.4초 멈췄고, UDP는 50개쯤 잃었지만 멈추지 않았습니다. 이 장면을 47초 영상으로도 정리했습니다.
TCP UDP 차이 한눈에 보기
| TCP | UDP | |
|---|---|---|
| 약속 | 전부, 순서대로 도착 | 약속 없음 |
| 빠진 데이터 | 다시 보낸다(재전송) | 버린다 |
| 순서가 섞이면 | 번호대로 다시 맞춰서 넘긴다 | 온 순서대로 넘긴다 |
| 연결 | 먼저 연결을 맺는다(3-way handshake) | 연결 없이 바로 보낸다 |
| 헤더 크기 | 최소 20바이트 | 8바이트 |
| 잘 맞는 곳 | 파일, 결제, 다운로드, 대부분의 웹 | 영상통화, 게임, 생중계, DNS 조회 |
| 한 줄로 | 전부 | 지금 |
"신뢰성 있다/없다"는 틀린 말은 아니지만, 이렇게 외우면 "그럼 UDP는 왜 써?" 라는 질문에 답이 막힙니다. 더 정확한 기준은 "빠지면 안 되는 데이터인가, 늦으면 안 되는 데이터인가" 입니다.
TCP는 왜 다 도착했는데 멈출까 — 순서 보장과 재전송
택배 창구를 떠올려 보세요. 이 창구는 번호 순서대로만 물건을 내줍니다.
- #55, #56이 도착해서 바로 손님(앱)에게 넘어갑니다.
- 그런데 #57이 오는 길에 사라졌습니다.
- #58, #59, #60은 멀쩡히 도착했습니다. 그래도 창구 앞에 세워 둡니다. #57이 아직 없으니까요.
- 보낸 쪽이 "#57이 안 갔구나"를 알아채고 다시 보냅니다.
- #57이 도착하는 순간, #57~#60이 한꺼번에 넘어갑니다.
TCP가 "신뢰성 있다"는 건 이 과정 덕분입니다. 하나도 안 빠지고, 순서도 안 섞입니다. 대신 3번 단계에서 앱은 아무것도 받지 못합니다. 데이터가 이미 와 있는데도요. 이걸 head-of-line blocking(줄 맨 앞이 막혀서 뒤가 다 기다리는 현상)이라고 부릅니다.
파일을 받을 때는 이게 정답입니다. 파일 중간이 빠지면 그 파일은 못 씁니다. 그런데 영상통화라면 어떨까요? 0.4초 전의 화면 한 장을 기다리느라 지금 화면까지 멈추는 셈입니다.
UDP는 왜 버릴까 — 늦은 데이터는 쓸모가 없다
UDP는 생중계 중계차에 가깝습니다. #57이 사라지면 그냥 넘어갑니다. #58, #59, #60은 도착하는 대로 바로 화면에 나갑니다. 화면이 한 프레임 깨질 수는 있지만 멈추지는 않습니다.
실시간 통화나 게임에서는 0.4초 늦게 도착한 데이터는 이미 쓸모가 없습니다. 그 사이에 상대방은 다음 말을 했고, 캐릭터는 다른 곳으로 움직였거든요. 그래서 "다시 받아서 순서를 맞추는" 비용을 내지 않는 UDP가 유리합니다.
물론 UDP를 쓰는 앱도 아무 대책이 없는 건 아닙니다. 필요한 만큼만 앱이 직접 챙깁니다. 예를 들어 음성 코덱은 빠진 소리를 앞뒤로 메우고, 게임은 다음 위치를 예측해서 그립니다.
TCP UDP 차이를 직접 재 봤습니다
실험 방법
- 컨테이너 두 개(보내는 쪽, 받는 쪽)를 같은 Docker 네트워크에 띄웁니다.
- 보내는 쪽에
tc netem으로 지연 40ms, 손실 5% 를 겁니다. 나쁜 와이파이를 흉내 낸 겁니다. - 200바이트 메시지 1,000개를 20ms 간격(초당 50개, 음성 통화 프레임과 비슷한 속도)으로 보냅니다.
- 메시지마다 보낸 시각을 담고, 받는 쪽에서 도착 시각과 비교해 지연을 잽니다. 두 컨테이너는 같은 호스트 커널의 시계를 쓰니 그대로 뺄 수 있습니다.
- TCP와 UDP를 번갈아 3회씩 돌렸습니다.
# 보내는 컨테이너 안에서 — 나가는 패킷에 지연 40ms, 손실 5%
tc qdisc add dev eth0 root netem delay 40ms loss 5%# bench.py 핵심 — 메시지마다 [번호, 보낸 시각]을 담아 20ms 간격으로 보낸다
def pack(i): return struct.pack('!Id', i, time.time()).ljust(200, b'x')
if mode == 'udp':
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for i in range(1000): s.sendto(pack(i), (host, 9000)); time.sleep(0.02)
else:
s = socket.create_connection((host, 9000))
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) # 모아 보내기(Nagle) 끄기
for i in range(1000): s.sendall(pack(i)); time.sleep(0.02)결과 — 평소엔 같고, 잃어버렸을 때 갈린다
| 회차 | 방식 | 도착 | 중앙값 | p95 | p99 | 최대 |
|---|---|---|---|---|---|---|
| 1 | TCP | 1,000 / 1,000 | 40.3ms | 100.8ms | 140.8ms | 208.5ms |
| 1 | UDP | 951 / 1,000 | 40.8ms | 41.4ms | 42.5ms | 81.1ms |
| 2 | TCP | 1,000 / 1,000 | 40.3ms | 100.4ms | 115.3ms | 173.1ms |
| 2 | UDP | 953 / 1,000 | 40.8ms | 41.5ms | 42.6ms | 81.0ms |
| 3 | TCP | 1,000 / 1,000 | 40.3ms | 100.6ms | 270.6ms | 412.2ms |
| 3 | UDP | 958 / 1,000 | 40.8ms | 41.4ms | 42.3ms | 81.0ms |
표에서 눈여겨볼 곳은 세 군데입니다.
- 중앙값은 둘이 똑같습니다(약 40ms). 걸어 둔 지연 40ms 그대로입니다. 평소에는 TCP가 느리지 않습니다.
- TCP는 p95부터 튑니다. 100번 중 5번꼴로 100ms가 넘었고, 최대는 회차에 따라 0.17~0.41초였습니다. 손실 5%와 비율이 비슷한 걸 보면, 앞 조각이 빠졌을 때 재전송을 기다린 메시지들로 보입니다. 최대값이 회차마다 크게 다른 건 재전송이 한 번 더 늦어진 경우가 섞였기 때문으로 추정합니다.
- UDP는 4.2~4.9%를 잃었지만 99%가 43ms 안에 왔습니다. 기다리는 일이 없으니 지연이 평평합니다.
같은 PC 안이라서 순서가 섞인 경우(out-of-order)는 두 방식 모두 0건이었습니다. 실제 인터넷에서는 경로가 길고 복잡해서, 손실과 섞임이 더 다양하게 일어납니다.
그럼 언제 TCP, 언제 UDP를 쓰나
| 데이터 | 빠지면? | 늦으면? | 고를 것 |
|---|---|---|---|
| 파일 다운로드, 결제, API 응답 | 못 씀 | 조금 기다려도 됨 | TCP |
| 영상통화, 음성통화 | 한 프레임 깨짐 | 대화가 안 됨 | UDP |
| 온라인 게임 위치 정보 | 다음 값으로 덮어씀 | 렉 | UDP |
| DNS 조회 | 다시 물어보면 됨 | 페이지가 늦게 뜸 | 보통 UDP(응답이 크면 TCP) |
판단 기준은 하나입니다. "이 데이터는 빠지면 안 되나, 늦으면 안 되나?"
HTTP/3(QUIC)는 UDP 위에서 돈다 — "UDP = 불안정"이라는 오해
"웹은 TCP"라고 외우셨다면 반만 맞습니다. HTTP/3는 UDP 위에서 동작하는 QUIC를 씁니다. QUIC는 재전송과 순서 보장을 UDP 위에 직접 얹었습니다.
왜 그렇게 했을까요? 위에서 본 head-of-line blocking 때문입니다. TCP 연결 하나에 여러 파일(이미지, CSS, JS)을 실어 보내면, 이미지 조각 하나가 빠졌을 때 CSS까지 같이 기다립니다. QUIC는 파일마다 줄을 따로 세워서 빠진 파일만 기다리게 합니다.
그래서 UDP를 "불안정한 TCP"로 보면 정확하지 않습니다. UDP는 필요한 기능을 앱이 골라 얹을 수 있게 비워 둔 바닥에 가깝습니다. 영상통화(WebRTC)도 보통 UDP로 보내되, 회사 방화벽처럼 UDP가 막힌 곳에서는 TCP로 우회하기도 합니다.
면접에서 TCP UDP 차이를 답하는 법
한 줄 답변
"TCP는 전부, 순서대로 도착하도록 재전송과 순서 보장을 해 주고, 그 대가로 손실이 나면 기다립니다. UDP는 그런 보장이 없는 대신 기다리지 않아서, 늦으면 쓸모없는 실시간 데이터에 씁니다."
꼬리 질문에 대비할 세 가지
- "UDP는 왜 쓰나요?" → 영상통화·게임처럼 늦은 데이터가 쓸모없는 경우. 기다리는 비용(head-of-line blocking)을 피하려고 씁니다.
- "TCP가 항상 느린가요?" → 아닙니다. 손실이 없으면 지연은 비슷합니다. 차이는 손실이 났을 때 납니다.
- "HTTP/3는요?" → UDP 위의 QUIC가 재전송과 순서 보장을 스트림별로 직접 합니다. 그래서 한 파일의 손실이 다른 파일을 막지 않습니다.
자주 묻는 것
UDP는 데이터가 빠지면 끝인가요?
UDP 자체는 다시 보내지 않습니다. 대신 앱이 필요한 만큼 챙깁니다. 통화 앱은 빠진 소리를 메우고, 게임은 다음 위치로 덮어쓰고, QUIC는 아예 재전송을 직접 구현합니다.
TCP로 영상통화를 하면 안 되나요?
됩니다. UDP가 막힌 네트워크에서는 실제로 TCP로 우회합니다. 다만 손실이 생기면 위 실측처럼 가끔 크게 멈춥니다. 그래서 UDP가 되는 곳에서는 UDP를 먼저 씁니다.
실측의 손실 5%는 현실적인 값인가요?
일부러 크게 잡은 값입니다. 유선 네트워크에서는 대개 이보다 훨씬 적고, 혼잡한 와이파이나 이동 중인 모바일에서 순간적으로 높아질 수 있습니다. 차이를 눈으로 보기 위해 크게 걸었다고 보시면 됩니다.
정리
- TCP = 전부, 순서대로. 빠진 조각을 다시 받을 때까지 뒤 조각도 기다린다.
- UDP = 지금, 바로. 빠진 조각은 버리고 온 것부터 넘긴다.
- 실측(손실 5%, 1,000개, 3회): 평소 지연은 둘 다 약 40ms로 같고, 손실이 났을 때만 TCP가 최대 0.17
0.41초 멈췄다. UDP는 45%를 잃었지만 99%가 43ms 안에 왔다. - 고르는 기준은 "빠지면 안 되나, 늦으면 안 되나".
- HTTP/3(QUIC)는 UDP 위에 필요한 보장을 직접 얹었다. UDP는 나쁜 TCP가 아니다.
네트워크 기초를 이어서 보시려면 HTTPS 핸드셰이크 완전 정리도 함께 읽어 보세요.
여러분이 만든 서비스에서는 "빠지면 안 되는 데이터"와 "늦으면 안 되는 데이터"를 어떻게 나누고 계신가요? 댓글로 들려주세요.
