전송 계층: TCP와 UDP, 혼잡 제어
TCP와 UDP의 차이, 3-way/4-way 핸드셰이크, 흐름 제어와 혼잡 제어를 백엔드 운영 관점에서 정리합니다.
Contents
같은 IP 위에서도 TCP는 신뢰성을, UDP는 낮은 지연을 택합니다. 그 선택이 핸드셰이크와 혼잡 제어에서 갈립니다.
전송 계층이 맡는 일
전송 계층(Transport Layer)은 종단 간(end-to-end) 통신을 책임집니다. IP가 패킷을 목적지 호스트까지 옮긴다면, 전송 계층은 그 호스트 안의 특정 프로세스까지 데이터를 연결합니다. 이 계층에서 동작하는 두 축이 TCP와 UDP입니다.
TCP(Transmission Control Protocol)는 연결 지향의 신뢰성 있는 바이트 스트림을 제공합니다. 최신 규격은 기존 RFC 793을 통합한 RFC 9293입니다. UDP(User Datagram Protocol)는 RFC 768이 정의한 최소 데이터그램 전달만 제공하며, 순서나 재전송을 보장하지 않습니다.
두 프로토콜의 차이는 신뢰성과 지연의 교환으로 요약됩니다. 무엇을 보장할지가 헤더 크기와 연결 절차, 혼잡 제어 유무로 이어집니다.
TCP와 UDP의 교환 관계
다음 표는 두 프로토콜을 운영 관점에서 비교한 것입니다. 헤더 크기는 각 RFC가 규정한 값입니다.
| 항목 | TCP | UDP |
|---|---|---|
| 연결 방식 | 연결형 | 비연결형 |
| 신뢰성 | 순서·재전송·흐름/혼잡 제어 보장 | 미보장 |
| 헤더 크기 | 20~60바이트 | 8바이트 |
| 전송 단위 | 바이트 스트림 | 메시지(데이터그램) |
| 대표 사용처 | HTTP, HTTPS, SSH | DNS, 스트리밍, 게임, QUIC |
UDP가 빠르다는 말은 절반만 맞습니다. UDP 자체가 빠른 것이 아니라, 연결 설정 비용이 없고 순서·재전송을 앱이 선택해 구현할 수 있다는 뜻입니다. 그래서 RFC 8085는 UDP 위에 프로토콜을 설계할 때 혼잡 제어와 메시지 크기를 직접 다루라고 권고합니다.
TCP의 신뢰성은 확인 응답(ACK), 재전송 타이머, 순서 제어로 구현됩니다. 재전송 타이머(RTO, Retransmission Timeout)의 계산 방식은 RFC 6298이 표준화했습니다. 이 보장을 켜기 위해 TCP는 데이터를 보내기 전에 연결을 먼저 맺습니다.
연결을 여닫는 핸드셰이크
TCP는 데이터 전에 3-way handshake로 연결을 수립합니다. 양쪽이 서로의 시퀀스 번호를 교환하고 확인해, 양방향 통신 준비가 끝났음을 보장하기 위해서입니다. 종료는 4-way handshake로 진행됩니다.
연결 수립이 3단계, 종료가 4단계로 단계 수가 다른 이유가 있습니다. 클라이언트가 종료를 요청해도 서버에는 아직 보낼 데이터가 남아 있을 수 있습니다. 그래서 서버의 ACK와 FIN이 한 번에 묶이지 않고 따로 나뉩니다.
마지막 ACK를 보낸 쪽은 곧바로 닫지 않고 TIME_WAIT 상태로 들어갑니다. 이는 지연된 패킷이 다음 연결에 섞이는 것을 막고, 마지막 ACK가 유실됐을 때 재전송에 응답하기 위한 정상 상태입니다.
TIME_WAIT 길이는 2 x MSL입니다. MSL(Maximum Segment Lifetime)은 세그먼트가 망에서 살아 있는 최대 시간이며, RFC 793은 이를 2분으로 정의합니다. 이론값은 최대 240초지만 리눅스 커널은 이 값을 60초로 고정해 둡니다.
흐름 제어와 혼잡 제어
두 제어는 막으려는 대상이 다릅니다. 흐름 제어(flow control)는 수신측 버퍼가 넘치지 않게 막고, 혼잡 제어(congestion control)는 네트워크 경로가 막히지 않게 조절합니다. 둘을 같은 것으로 묶으면 진단이 어긋납니다.
흐름 제어의 핵심은 슬라이딩 윈도우입니다. 수신측이 광고하는 RWND(Receive Window)만큼만 ACK 없이 연속해서 보낼 수 있습니다. 혼잡 제어는 CWND(Congestion Window)를 따로 두고, 실제 전송량은 두 윈도우의 작은 쪽인 min(RWND, CWND)으로 정해집니다.
혼잡 제어의 표준 묶음은 RFC 5681이 정의한 네 가지입니다. 윈도우 단위는 MSS(Maximum Segment Size), 즉 한 세그먼트에 담을 수 있는 최대 바이트입니다.
| 알고리즘 | 동작 | 진입 조건 |
|---|---|---|
| Slow Start | 윈도우 1 MSS에서 RTT마다 2배 | 연결 시작·타임아웃 후 |
| Congestion Avoidance | RTT마다 1 MSS씩 선형 증가 | ssthresh 도달 후 |
| Fast Retransmit | 타임아웃 대기 없이 즉시 재전송 | 중복 ACK 3개 수신 |
| Fast Recovery | 윈도우를 1이 아닌 절반으로 축소 | Fast Retransmit 직후 |
상태 사이의 전환을 그림으로 보면 다음과 같습니다. RTT(Round Trip Time)는 왕복 시간이고, ssthresh는 지수 증가에서 선형 증가로 바꾸는 임계점입니다.
손실 신호의 강도에 따라 반응이 다른 점이 핵심입니다. 중복 ACK 3개는 경로가 살아 있다는 약한 손실 신호라 윈도우를 절반만 줄입니다. 타임아웃은 강한 신호라 윈도우를 1로 되돌리고 Slow Start로 다시 들어갑니다. 리눅스의 기본 혼잡 제어 알고리즘은 CUBIC입니다.
UDP를 고르는 기준과 QUIC
UDP는 손실보다 지연이 더 아픈 곳에서 쓰입니다. 짧은 요청·응답이거나 실시간성이 정확성보다 중요한 경우입니다. 아래는 대표적인 선택 근거입니다.
| 사용처 | 고르는 이유 |
|---|---|
| DNS(53) | 짧은 요청/응답, 속도 우선 |
| 실시간 게임 | 낮은 지연, 약간의 손실 허용 |
| 영상·음성 스트리밍 | 실시간성이 신뢰성보다 우선 |
| QUIC(443) | UDP 위에 신뢰성을 직접 구현 |
QUIC는 UDP가 신뢰성을 보장하지 않는 특성을 설계 여지로 활용합니다. TCP는 OS 커널에 구현돼 수정이 더디지만, UDP 위에 올린 QUIC는 사용자 공간에서 혼잡 제어와 재전송을 다룰 수 있습니다. HTTP/3가 이 QUIC 위에서 동작하며, 패킷 손실의 영향을 스트림 단위로 격리하는 것을 설계 목표로 둡니다.
따라서 UDP를 고른다고 신뢰성을 포기하는 것은 아닙니다. 필요한 만큼의 보장을 앱이 직접 설계한다는 선택입니다. QUIC가 그 선택을 표준화한 사례입니다.
운영에서 마주치는 신호
서버를 재시작할 때 'Address already in use' 오류를 자주 봅니다. 직전 연결이 TIME_WAIT에 머물러 같은 포트로 bind를 막기 때문입니다. SO_REUSEADDR 옵션을 켜면 TIME_WAIT 상태의 포트를 재사용해 즉시 다시 띄울 수 있습니다.
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('0.0.0.0', 9000))
server.listen()TIME_WAIT 자체는 제거 대상이 아닙니다. 정상 종료의 흔적이므로, 수가 많아졌다면 왜 짧은 연결이 늘었는지를 먼저 봅니다. 연결 문제가 반복되면 애플리케이션보다 프록시의 keep-alive와 커넥션 풀 설정을 먼저 점검하는 편이 빠릅니다.
재전송이 잦다면 망이 느린 탓으로 넘기지 않고 손실과 혼잡의 신호로 읽습니다. QUIC나 HTTP/3로 옮겨도 패킷 손실, 인증서, DNS 같은 변수는 그대로 남습니다.
정리
전송 계층의 두 축은 신뢰성과 지연을 맞바꿉니다. TCP는 3-way handshake로 연결을 열고 4-way handshake로 닫으며, 그 신뢰성을 ACK와 재전송, 흐름 제어, 혼잡 제어로 떠받칩니다. UDP는 그 보장을 비워 두고, 필요한 만큼을 앱이 채우게 합니다.
혼잡 제어는 손실 신호의 강도에 따라 다르게 반응합니다. 중복 ACK 3개에는 윈도우를 절반으로, 타임아웃에는 1로 되돌리는 구분이 RFC 5681의 핵심입니다. 흐름 제어의 RWND와 혼잡 제어의 CWND를 나눠 보면 병목의 위치를 더 정확히 짚을 수 있습니다.
운영에서는 TIME_WAIT, SO_REUSEADDR, 재전송이 같은 그림의 신호로 묶입니다. 이 신호들을 정상 동작의 일부로 읽으면 불필요한 튜닝을 줄일 수 있습니다. QUIC와 HTTP/3는 이 전송 특성을 사용자 공간으로 끌어와 다시 설계한 방식입니다.