TCP vs UDP — 면접에서 3-way handshake 너머의 질문들
"TCP는 연결 지향이고 신뢰성 있고, UDP는 비연결이고 빠릅니다" — 여기서 한 발 더 들어가면, 왜 handshake가 3번인지, TIME_WAIT는 왜 필요한지가 보입니다.
TCP의 신뢰성은 공짜가 아닙니다. handshake, 흐름 제어, 혼잡 제어 — 각각이 어떤 문제를 해결하는지 인과 흐름으로 정리합니다.
TCP vs UDP 한눈에
| TCP | UDP | |
|---|---|---|
| 연결 | 연결 지향 (handshake) | 비연결 |
| 신뢰성 | 재전송, 순서 보장 | 보장 안 함 |
| 속도 | 상대적으로 느림 | 빠름 |
| 헤더 크기 | 20~60바이트 | 8바이트 |
| 흐름/혼잡 제어 | 있음 | 없음 |
| 용도 | HTTP, SSH, FTP, 이메일 | DNS, 스트리밍, 게임, VoIP |
3-Way Handshake
TCP 연결을 수립하는 과정입니다.
클라이언트 서버
│ │
│──── SYN (seq=x) ────────→│ 1단계: "연결하고 싶어요"
│ │
│←── SYN+ACK (seq=y, ack=x+1) ──│ 2단계: "좋아요, 나도 연결할게요"
│ │
│──── ACK (ack=y+1) ──────→│ 3단계: "확인, 시작합시다"
│ │
왜 3번인가? 2번이면 안 되나?
2번이면 **서버가 클라이언트의 수신 능력을 확인할 수 없습니다 **.
시나리오를 생각해보면:
- 클라이언트가 SYN 전송 (오래된 요청이 네트워크에서 지연됨)
- 서버가 SYN+ACK 응답
- → 2-way면 여기서 연결 수립 → 근데 클라이언트는 이미 그 연결을 안 원함
3번째 ACK가 있어야 클라이언트가 "네, 저 진짜 연결 원해요"를 확인시켜줍니다.
또한, 양쪽 모두 ** 시퀀스 번호를 교환 **해야 하는데, SYN으로 자기 시퀀스 번호를 알리고 ACK로 상대의 시퀀스 번호를 확인합니다. 양방향으로 이걸 하려면 최소 3번이 필요합니다.
4-Way Handshake (연결 종료)
클라이언트 서버
│ │
│──── FIN ────────────────→│ 1: "나 보낼 거 다 보냈어"
│ │
│←── ACK ─────────────────│ 2: "알겠어, 근데 나는 아직 보낼 게 있어"
│ │
│←── FIN ─────────────────│ 3: "나도 다 보냈어"
│ │
│──── ACK ────────────────→│ 4: "알겠어, 끊자"
│ │
│ TIME_WAIT (2MSL) │
왜 4번인가?
종료는 한쪽이 먼저 끊을 수 있으니까 ** 반이중(half-close)**을 지원합니다. 클라이언트가 FIN을 보내도 서버는 아직 보낼 데이터가 있을 수 있습니다. 그래서 ACK와 FIN이 분리되어 4번이 됩니다.
TIME_WAIT은 왜 필요한가
마지막 ACK가 유실되면 서버가 FIN을 재전송합니다. TIME_WAIT 상태에서 이 재전송을 받아서 다시 ACK를 보낼 수 있습니다. 보통 2MSL(Maximum Segment Lifetime, 약 60초) 동안 유지됩니다.
서버에서 TIME_WAIT이 많이 쌓이면 포트 고갈이 발생할 수 있습니다. 이건 실무에서 꽤 자주 겪는 문제입니다.
흐름 제어 (Flow Control)
수신자가 처리할 수 있는 속도에 맞춰 송신자의 전송 속도를 조절하는 메커니즘입니다.
슬라이딩 윈도우
수신자가 ** 윈도우 크기(receive window)**를 알려줍니다. "나 지금 버퍼에 이만큼 여유 있어"라는 뜻입니다.
송신자: 윈도우 크기만큼 ACK 없이 연속 전송 가능
수신자: 처리한 만큼 윈도우를 슬라이드하여 새로운 공간 알림
[전송 완료 | 전송 중 (윈도우) | 전송 대기]
윈도우 크기가 0이 되면 송신자는 전송을 멈춥니다.
혼잡 제어 (Congestion Control)
흐름 제어가 수신자 보호라면, 혼잡 제어는 ** 네트워크 보호 **입니다.
Slow Start
처음에 윈도우 크기를 1에서 시작해서 ACK를 받을 때마다 2배씩 늘립니다. 지수적으로 증가하다가 임계값(ssthresh)에 도달하면 선형 증가로 전환합니다.
윈도우: 1 → 2 → 4 → 8 → 16 (ssthresh) → 17 → 18 → 19 ...
[지수 증가] [선형 증가 (혼잡 회피)]
패킷 유실이 감지되면 윈도우를 줄입니다. 어떻게 줄이느냐에 따라 알고리즘이 다릅니다 (Tahoe, Reno, CUBIC 등).
심화 개념
"HTTP는 TCP 위에서 동작하는데, HTTP/3은 왜 UDP를 쓰나요?"
HTTP/3은 QUIC 프로토콜 위에서 동작하는데, QUIC는 UDP 기반입니다.
TCP의 문제점 때문입니다:
- Head-of-Line Blocking: TCP는 패킷 순서를 보장하니까, 하나가 유실되면 뒤의 모든 패킷이 대기
- **handshake 오버헤드 **: TCP 3-way + TLS 1.3 handshake = 최소 2 RTT
QUIC는 UDP 위에서 신뢰성과 흐름 제어를 ** 직접 구현 **하되, 스트림별로 독립적으로 관리해서 HOL Blocking을 해결합니다. 그리고 연결 수립과 TLS를 합쳐서 1 RTT 로 줄입니다.
"TCP keepalive와 HTTP keepalive의 차이는?"
- TCP keepalive: 연결이 살아있는지 주기적으로 빈 패킷을 보내서 확인 (OS 레벨)
- HTTP keepalive (persistent connection): 하나의 TCP 연결로 여러 HTTP 요청/응답을 주고받음
HTTP/1.0은 요청마다 TCP 연결을 새로 맺었는데, HTTP/1.1부터 Connection: keep-alive가 기본이 되어서 연결을 재사용합니다.
"DNS는 왜 UDP를 쓰나요?"
DNS 쿼리는 보통 하나의 작은 패킷으로 끝납니다. TCP의 3-way handshake 오버헤드가 필요 없죠. 빠르게 질의하고 빠르게 응답받으면 됩니다.
다만, 응답이 512바이트를 초과하거나 zone transfer(영역 전송)를 할 때는 TCP를 사용 합니다.
파생되는 개념들
- HTTP/1.1 vs HTTP/2 vs HTTP/3 — 프로토콜 진화
- TLS/SSL 핸드셰이크 — HTTPS의 동작 원리
- OSI 7계층 / TCP/IP 4계층 — 네트워크 계층 모델
- NAT, 서브넷, CIDR — IP 주소 관리
- ** 로드 밸런싱** — L4 vs L7 로드 밸런서의 차이