Chapter 09

TCP와 UDP

IP는 상자를 목적지 건물까지 “최선을 다해” 보낼 뿐이다. 도중에 상자가 사라지거나, 순서가 뒤바뀌거나, 두 번 도착해도 IP는 책임지지 않는다. 그런데도 우리가 내려받은 파일은 한 바이트도 틀리지 않고, 수십억 명이 같은 길을 나눠 써도 인터넷은 멈추지 않는다. 이 장은 그 마법을 부리는 전송 계층, 특히 인터넷에서 가장 많이 쓰이는 두 배송 규칙인 TCP와 UDP 이야기다.

전송 계층의 일: 포트와 소켓

IP 주소(7장)는 패킷을 컴퓨터까지 데려다준다. 하지만 컴퓨터 안에는 브라우저 탭 여러 개, 메신저, 음악 앱, 백그라운드 업데이트가 동시에 통신하고 있다. 도착한 패킷을 이 중 누구에게 줄지 정하는 것이 전송 계층(3장의 4계층)의 첫 번째 일이다. 그 표시가 포트 번호(port number), 0부터 65535까지의 16비트 숫자다.

건물 주소와 호수

IP 주소가 아파트 건물 주소라면 포트 번호는 호수다. 택배 기사는 건물까지만 책임지고, 관리실(운영체제)이 호수를 보고 각 집(앱)에 나눠 준다. 웹 서버는 “443호”, 메일 서버는 “25호”처럼 모두가 아는 호수에 산다. 반대로 손님(클라이언트)은 그때그때 비어 있는 임시 호수를 받아 답장 받을 곳으로 쓴다.

“IP 주소 + 포트 번호” 한 쌍을 소켓(socket) 주소라 하고, 프로그램이 네트워크와 데이터를 주고받는 창구를 그냥 소켓이라고도 부른다. TCP 연결 하나는 (프로토콜, 보내는 IP, 보내는 포트, 받는 IP, 받는 포트) 다섯 값, 즉 5-튜플로 구분된다. 그래서 서버는 443번 포트 하나로 수만 명의 손님과 동시에 따로따로 대화할 수 있다.

노트북192.0.2.10 탭 1 → 포트 51000 탭 2 → 포트 51001 휴대폰198.51.100.7 앱 → 포트 60123 서버203.0.113.5 포트 443웹 서버 (HTTPS) 포트 22 · SSH 포트 53 · DNS (UDP) ① ② ③ ① TCP 192.0.2.10:51000 ↔ 203.0.113.5:443 ② TCP 192.0.2.10:51001 ↔ 203.0.113.5:443
그림 9-1. 포트와 소켓. 세 연결(①②③; ③은 198.51.100.7:60123 ↔ 203.0.113.5:443) 모두 서버의 443번 포트로 들어오지만, 5-튜플 중 하나라도 다르면 서로 다른 연결이다. 운영체제는 이 다섯 값을 열쇠로 패킷을 알맞은 소켓에 넘긴다.

국제 인터넷 번호 관리 기관(IANA)은 포트를 세 구역으로 나눈다. 0~1023은 잘 알려진 포트(well-known ports)로 대표 서비스가 쓰고, 1024~49151은 등록 포트, 49152~65535는 클라이언트가 잠깐 빌려 쓰는 임시 포트다(리눅스는 기본으로 32768~60999를 쓴다).

포트프로토콜서비스이 책에서
22TCPSSH (원격 접속)22장
25 / 587TCPSMTP (메일 전달 / 메일 제출)10장
53UDP·TCPDNS (이름 → 주소)10장
67 / 68UDPDHCP (주소 자동 배정)7장
80TCPHTTP (암호화 없는 웹)10장
123UDPNTP (시간 맞추기)23장
443TCP · UDPHTTPS (TCP), HTTP/3 (UDP 위 QUIC)이 장, 10장
993TCPIMAPS (메일함 읽기)10장
179TCPBGP (라우터끼리 경로 교환)8장

전송 계층의 두 대표 선수는 성격이 정반대다. UDP(User Datagram Protocol)는 “상자에 호수만 적어서 그냥 보낸다.” TCP(Transmission Control Protocol)는 “연결을 맺고, 번호를 매기고, 받았다는 확인을 받고, 잃어버리면 다시 보내고, 길이 막히면 속도를 줄인다.” 일반 우편과 등기 우편의 차이다.

UDP: 일단 보낸다

UDP 헤더는 겨우 8바이트, 필드는 네 개뿐이다. 보내는 포트, 받는 포트, 길이, 체크섬. 연결을 맺지도, 순서를 챙기지도, 재전송하지도 않는다. 받는 쪽이 데이터를 받았는지조차 모른다. 대신 군더더기가 없다. 첫 패킷부터 바로 데이터를 실을 수 있고, 손실 하나 때문에 뒤따르는 데이터가 멈추지도 않는다.

08162431 비트 UDP8바이트 보내는 포트 (16) 받는 포트 (16) 길이 (16) 체크섬 (16) TCP20바이트+ 옵션(최대 60) 보내는 포트 (16) 받는 포트 (16) 순서 번호 (32) — 이 조각의 첫 바이트가 전체에서 몇 번째인가 확인 응답(ACK) 번호 (32) — 다음에 받고 싶은 바이트 번호 헤더 길이 예약 플래그 SYN·ACK·FIN… 수신 윈도우 (16) 체크섬 (16) 긴급 포인터 (16) 옵션 (가변): MSS, 윈도우 스케일, SACK, 타임스탬프 … 파란 칸 = 순서와 분실을 챙기는 정보, 보라 칸 = 받는 쪽이 감당할 수 있는 양(흐름 제어). UDP에는 이런 칸이 아예 없다. 순서·재전송이 필요하면 앱이 직접 만든다.
그림 9-2. UDP 헤더(8바이트)와 TCP 헤더(최소 20바이트). 한 줄이 32비트다. TCP의 추가 필드는 거의 모두 “순서, 확인, 속도 조절”을 위한 것이다.

그렇다면 UDP는 어디에 쓸까? “조금 잃어도 괜찮지만 늦으면 안 되는” 일, 또는 “신뢰성은 내가 알아서 챙기겠다”는 일이다.

UDP의 함정

UDP에는 혼잡 제어가 없어서, 잘못 만든 앱은 망이 막혀도 계속 퍼붓는다. 또 보내는 주소를 속이기 쉬워, 작은 질문으로 큰 답을 남에게 보내게 만드는 증폭 공격(DNS·NTP 반사 공격, 22장)에 악용되곤 한다. 그래서 UDP 위의 진지한 프로토콜(QUIC, WebRTC)은 모두 스스로 혼잡 제어와 주소 확인을 넣는다.

TCP 연결: 세 번 악수하고 네 번 인사한다

TCP는 데이터를 보내기 전에 먼저 연결을 맺는다. 전선이 생기는 것은 아니고, 양쪽이 “지금부터 이 번호로 시작하는 바이트 흐름을 주고받자”고 약속하고 상태를 기억해 두는 것이다. 이 약속을 3방향 핸드셰이크(three-way handshake)라 한다.

  1. SYN: 클라이언트가 “연결하자. 내 바이트 번호는 1000부터 시작한다.”(seq=1000)
  2. SYN-ACK: 서버가 “좋다. 네 1000번은 받았으니 다음엔 1001번을 기다린다(ack=1001). 내 번호는 5000부터다.”(seq=5000)
  3. ACK: 클라이언트가 “네 5000번 받았다, 다음은 5001번.”(ack=5001) 이제 양쪽 모두 상대의 시작 번호를 안다.

여기서 순서 번호(sequence number)는 패킷 번호가 아니라 바이트 번호다. 100바이트를 seq=1001로 보내면 그 데이터는 1001~1100번 바이트이고, 받는 쪽은 ack=1101(“1100번까지 다 받았다, 다음은 1101”)로 답한다. SYN과 FIN은 데이터가 없어도 번호를 하나 차지한다. 시작 번호를 0이 아니라 무작위로 고르는 이유는, 예전 연결의 늦게 도착한 패킷과 헷갈리지 않고, 공격자가 번호를 추측해 가짜 패킷을 끼워 넣지 못하게 하기 위해서다.

연결 맺기 (3방향) 연결 끊기 (4방향) 클라이언트서버클라이언트서버 SYN seq=1000 SYN-ACK seq=5000 ack=1001 ACK seq=1001 ack=5001 데이터 100B seq=1001 CLOSEDSYN_SENTESTABLISHED LISTENSYN_RCVDESTABLISHED FIN seq=1101 ACK ack=1102 FIN seq=7001 ACK ack=7002 ESTABLISHEDFIN_WAIT_1FIN_WAIT_2TIME_WAIT→ CLOSED CLOSE_WAITLAST_ACKCLOSED (남은 데이터마저 보내기) (2×MSL 대기)
그림 9-3. 연결 맺기와 끊기. 각 세로줄 옆의 대문자는 그 순간의 TCP 상태 이름이다(리눅스의 ss -tan 명령으로 실제 상태를 볼 수 있다). 끊을 때는 양쪽이 각자 “나는 더 보낼 게 없다(FIN)”를 말하고 확인받아야 하므로 네 번이 된다. 서버의 ACK와 FIN은 보낼 데이터가 없으면 하나로 합쳐지기도 한다.

먼저 끊자고 한 쪽은 마지막 ACK를 보낸 뒤에도 바로 사라지지 않고 TIME_WAIT 상태로 잠시(최대 세그먼트 수명의 두 배, 2×MSL. 리눅스는 60초) 기다린다. 마지막 ACK가 사라져 상대가 FIN을 다시 보내면 답해 줘야 하고, 같은 5-튜플로 새 연결을 바로 열었다가 옛 연결의 늦은 패킷이 섞여 들어오는 사고도 막아야 하기 때문이다.

SIMULATOR

TCP 연결의 일생: 한 단계씩 진행하기

클라이언트 상태—
서버 상태—
클라이언트 → 서버 바이트—
서버 → 클라이언트 바이트—
파란 화살표는 클라이언트가, 보라 화살표는 서버가 보낸 세그먼트다. 해볼 것: 각 단계에서 ack 번호가 “상대가 보낸 seq + 받은 바이트 수”인지 직접 더해 확인해 보자. 핸드셰이크에 1 RTT, 끊는 데 또 1~2 RTT가 든다. 실제 시작 번호는 32비트 무작위 수이며, 여기서는 읽기 쉽게 1000과 5000으로 두었다.
SYN 플러드

서버는 SYN을 받으면 SYN_RCVD 상태의 “반쯤 열린 연결”을 기억해 둔다. 공격자가 가짜 주소로 SYN만 수백만 개 보내면 이 기억 공간이 꽉 차 진짜 손님을 못 받는다. 이 공격을 SYN 플러드라 하고, 상태를 저장하는 대신 시작 번호 속에 정보를 암호처럼 숨겨 두는 SYN 쿠키로 막는다(22장).

신뢰성: 확인하고, 기다리고, 다시 보낸다

TCP 신뢰성의 뼈대는 단순하다. 받는 쪽은 누적 ACK(cumulative ACK)로 “여기까지는 빈틈없이 다 받았다”를 알린다. 1~2번, 4~5번을 받고 3번이 빠졌다면 ACK는 계속 “3번을 기다린다”다. 보내는 쪽은 두 가지 신호로 손실을 알아챈다.

왜 하필 세 개일까? 패킷은 가끔 순서만 바뀌어 도착하기도 한다. 중복 ACK 한두 개는 단순한 순서 뒤바뀜일 수 있지만, 세 개가 쌓이면 진짜 손실일 가능성이 높다는 경험칙이다. 요즘 TCP는 여기에 SACK(선택적 ACK, “4~6번은 받았다”를 따로 알림)와 시간 기반 손실 감지(RACK)를 더해 여러 개가 한꺼번에 빠져도 빠르게 복구한다.

SIMULATOR

손실 복구: 빠른 재전송 vs 타임아웃

잃어버릴 세그먼트
 
복구 방법—
중복 ACK—
전체 완료 (RTT 100 ms 가정)—
세그먼트 번호 대신 1~8의 조각 번호를 쓰고, ACK k는 “k번을 기다린다(k−1번까지 다 받았다)”는 뜻이다. RTO는 RTT의 2.5배로 두었다. 해볼 것: 3번을 잃고 빠른 재전송을 켰다 꺼 보자. 그다음 마지막 8번을 잃어 보자. 뒤따르는 조각이 없으니 중복 ACK가 생기지 않아, 빠른 재전송이 켜져 있어도 타임아웃을 기다려야 한다(꼬리 손실). 실제 리눅스는 이 경우를 위해 “꼬리 손실 탐침(TLP)”을 따로 보낸다.

슬라이딩 윈도우: 확인을 기다리며 쉬지 않기

가장 단순한 신뢰성 규칙은 “하나 보내고, ACK가 올 때까지 기다리고, 다음 것을 보낸다”다. 이를 정지-대기(stop-and-wait)라 한다. 문제는 속도다. 서울–미국 서부처럼 RTT가 150 ms인 길에서는 1,500바이트 하나 보내고 150 ms를 논다. 회선이 1 Gbps여도 처리율은 1,500×8 / 0.15 ≈ 80 kbps, 1990년대 전화 모뎀 수준이다.

해법은 ACK를 기다리는 동안에도 여러 개를 연달아 보내는 것이다. “확인받지 않은 채 보낼 수 있는 양”을 슬라이딩 윈도우(sliding window)라 한다. 맨 앞 조각의 ACK가 오면 창이 오른쪽으로 한 칸 미끄러지고, 새로 창 안에 들어온 조각을 보낸다. 한 RTT 동안 창 하나만큼 보낼 수 있으므로 다음 관계가 성립한다.

처리율 ≤ 윈도우 크기 ÷ RTT
예: 윈도우 64 KB, RTT 100 ms → 최대 약 5.2 Mbps. 링크를 꽉 채우려면 윈도우 ≥ 대역폭 × RTT (대역폭-지연 곱, BDP)

대역폭-지연 곱(BDP)은 “길 위에 떠 있을 수 있는 데이터의 양”이다. 1 Gbps × 100 ms = 12.5 MB가 늘 길 위에 떠 있어야 회선이 쉬지 않는다. 원래 TCP 헤더의 윈도우 칸은 16비트라 최대 64 KB였기 때문에, 1992년에 윈도우 스케일 옵션(최대 1 GB)이 추가되었다.

SIMULATOR

슬라이딩 윈도우: 창 크기 · 손실 · RTT

확인 완료보냈고 ACK 대기창 안, 보낼 수 있음재전송
이론 상한 (윈도우/RTT)—
측정 처리율—
링크 이용률 (10 Mbps)—
정지-대기(윈도우 1)라면—
링크 10 Mbps, 패킷 1,500바이트(전송에 1.2 ms)로 두었다. 화면의 한 RTT는 실제 RTT와 관계없이 약 2초로 늘려 보여 준다. 그래서 RTT가 길수록 길 위의 패킷이 작고 촘촘해 보인다(같은 시간 동안 더 많은 패킷이 길 위에 있어야 한다는 뜻). 해볼 것: 윈도우 1로 정지-대기를 본 뒤 키워 보자. RTT 50 ms에서는 윈도우 약 42(= BDP)부터 처리율이 더 늘지 않는다. 손실이 생기면 맨 앞 조각이 막혀 창이 멈추는 모습도 보자. 손실은 중복 ACK 3개 또는 타임아웃(2×RTT)으로 감지해 다시 보낸다.
예측해 보기

윈도우가 64 KB로 고정된 오래된 TCP로 RTT 200 ms인 해외 서버에서 파일을 받는다. 집 회선을 100 Mbps에서 1 Gbps로 바꾸면 다운로드 속도는?

위 시뮬레이터에서 RTT를 200 ms로 두고 윈도우를 바꿔 보자. 처리율 ≤ 윈도우/RTT.

64 KB × 8 / 0.2 s ≈ 2.6 Mbps. 윈도우가 병목이면 회선을 아무리 넓혀도 소용없다. 그래서 요즘 TCP는 윈도우 스케일 옵션과 운영체제의 수신 버퍼 자동 조절로 윈도우를 수 MB~수십 MB까지 키운다.

흐름 제어와 혼잡 제어: 누가 브레이크를 밟나

윈도우를 무작정 키우면 두 곳이 넘친다. 하나는 받는 쪽의 버퍼다. 앱이 데이터를 천천히 읽으면 수신 버퍼가 찬다. 받는 쪽은 매 ACK마다 “지금 내 버퍼에 이만큼 여유가 있다”는 수신 윈도우(rwnd)를 적어 보내고, 보내는 쪽은 그 이상 보내지 않는다. 이것이 흐름 제어(flow control)다.

다른 하나는 중간의 네트워크다. 가장 좁은 구간(병목)의 라우터 큐가 넘치면 패킷이 버려진다. 문제는 아무도 병목이 얼마나 넓은지 알려 주지 않는다는 점이다. 그래서 보내는 쪽이 스스로 추정한 값, 혼잡 윈도우(cwnd, congestion window)를 따로 관리한다. 이것이 혼잡 제어(congestion control)다. 실제로 보낼 수 있는 양은 둘 중 작은 쪽이다.

보내는 쪽min(cwnd, rwnd) 병목 구간 + 라우터 큐 받는 쪽 수신 버퍼 혼잡 제어 (cwnd) 보내는 쪽이 손실·지연을 보고 스스로 추정 흐름 제어 (rwnd): ACK에 “내 버퍼 여유 = 40 KB” 적어 보냄
그림 9-4. 흐름 제어와 혼잡 제어. 흐름 제어는 받는 사람의 창고가 넘치지 않게, 혼잡 제어는 중간 도로가 막히지 않게 한다. 앞의 것은 받는 쪽이 숫자로 알려 주지만, 뒤의 것은 아무도 알려 주지 않아 보내는 쪽이 짐작해야 한다.
고속도로 진입 신호등

도로가 얼마나 비었는지 모르는 운전자 수천 명이 각자 속도를 정한다고 하자. 규칙은 이렇다. “처음엔 조심스럽게 들어가되 빠르게 늘린다. 막힘이 없으면 조금씩 더 낸다. 접촉 사고(손실)가 보이면 즉시 절반으로 줄인다.” 모두가 이 규칙을 지키면, 중앙 관제탑 없이도 도로가 꽉 차되 마비되지는 않는 상태에 저절로 모인다. 1986년 인터넷이 실제로 마비되는 “혼잡 붕괴”를 겪은 뒤, 밴 제이콥슨이 TCP에 넣은 규칙이 바로 이것이다.

TCP의 고전적인 혼잡 제어는 세 단계로 움직인다.

“더할 때는 조금씩(더하기), 줄일 때는 크게(곱하기)” 규칙을 AIMD(additive increase, multiplicative decrease)라 한다. 이 규칙 덕분에 cwnd 그래프는 특유의 톱니 모양이 되고, 여러 흐름이 병목을 나눠 쓸 때 저절로 공평한 몫으로 수렴한다.

SIMULATOR

혼잡 윈도우의 톱니: Tahoe · Reno · CUBIC

흐름 A의 cwnd흐름 B의 cwndssthresh(A)● 중복 ACK 3개 · 타임아웃
알고리즘
흐름 수
 
라운드 · cwnd(A) · ssthresh—
병목 이용률—
평균 큐 지연 (기본 RTT 100 ms)—
손실 횟수 · 최근 몫—
가로축은 RTT 라운드, 세로축은 한 RTT에 보내는 패킷 수(cwnd)다. 기본 RTT 100 ms, 패킷 1,500바이트라면 용량 40은 약 4.8 Mbps에 해당한다. 병목은 RTT마다 “용량”만큼 내보내고 남는 것은 버퍼에 쌓으며, 합이 용량+버퍼를 넘으면 그 라운드에 손실이 난다(두 흐름이면 둘 다 손실을 본다). 이해를 돕기 위해 cwnd는 1부터, 첫 ssthresh는 무한대로 시작했다. 해볼 것: ① Tahoe와 Reno의 톱니 모양 비교 ② 버퍼를 0으로 줄이면 이용률이 떨어지고, 크게 늘리면 이용률은 100%에 가깝지만 큐 지연이 커진다(23장 버퍼블로트) ③ 흐름 2개 모드에서 늦게 들어온 B가 A와 같은 몫으로 수렴하는지 보자.
예측해 보기

Reno, 흐름 1개, 병목 용량 40일 때 라우터 버퍼를 0에서 100으로 늘리면 무슨 일이 생길까?

혼잡 제어 시뮬레이터에서 버퍼 슬라이더를 양 끝으로 옮겨 이용률과 큐 지연을 비교하자.

버퍼가 없으면 cwnd가 절반으로 떨어진 동안 병목이 놀아 이용률이 75% 수준까지 떨어진다. 버퍼가 크면 놀지는 않지만, cwnd가 용량을 넘는 만큼 패킷이 큐에서 기다려 지연이 커진다. 손실 기반 TCP는 큐가 넘칠 때까지 cwnd를 키우기 때문이다. 이것이 버퍼블로트이고, 경험칙은 “버퍼 ≈ BDP” 정도다.
알고리즘손실 신호늘리는 방식줄이는 방식쓰이는 곳
Tahoe (1988)손실슬로 스타트 → +1/RTT모든 손실에 cwnd = 1역사적 원형
Reno / NewReno (1990~)손실슬로 스타트 → +1/RTT중복 ACK 3개: ×½, 타임아웃: 1교과서 표준, 오래된 시스템
CUBIC (2006~)손실마지막 손실 시점의 크기를 향해 3차 곡선으로×0.7리눅스·윈도우·macOS 기본
BBR (2016~)대역폭·RTT 측정측정한 병목 대역폭 × 최소 RTT에 맞춤손실에 크게 반응하지 않음구글·유튜브 등 서버, QUIC 구현

CUBIC은 RTT가 길고 넓은 망에서 Reno의 “RTT마다 +1”이 너무 느리다는 문제를 풀었다. 손실이 났던 크기(Wmax)를 기억해 두고, 그 근처까지는 빠르게 올라갔다가 근처에서는 조심스럽게, 넘어서면 다시 빠르게 탐색하는 3차 함수 모양으로 늘린다. 증가 속도가 RTT가 아니라 실제 시간에 묶여 있어 RTT가 다른 흐름끼리도 덜 불공평하다.

BBR: 손실을 기다리지 않는 혼잡 제어

손실 기반 알고리즘은 큐를 꽉 채워 패킷이 버려져야 비로소 속도를 줄인다. 구글의 BBR(Bottleneck Bandwidth and RTT)은 발상을 바꿨다. 실제로 전달된 속도의 최댓값으로 병목 대역폭을, 관찰된 왕복 시간의 최솟값으로 순수 전파 지연을 측정하고, 둘을 곱한 BDP만큼만 길 위에 띄운다. 큐를 채우지 않으니 지연이 낮고, 무작위 손실이 있는 무선·장거리 망에서도 속도가 잘 유지된다. 대신 다른 알고리즘과 섞였을 때의 공평성 문제로 v2, v3로 개선이 이어지고 있다. 큐가 넘치도록 커진 버퍼가 지연을 망치는 버퍼블로트와 그 측정법은 23장에서 다룬다.

헤드 오브 라인 블로킹과 QUIC

TCP는 앱에 하나의 순서 있는 바이트 흐름을 준다. 이 약속이 때로는 족쇄가 된다. 웹 페이지 하나는 HTML, CSS, 자바스크립트, 이미지 수십 개를 동시에 받는데, HTTP/2(10장)는 이것들을 TCP 연결 하나에 섞어 보낸다. 그러다 패킷 하나가 사라지면, 그 뒤에 도착한 패킷이 전혀 다른 파일의 것이어도 TCP는 빈칸이 채워질 때까지 앱에 넘겨 주지 않는다. 계산대 맨 앞 손님 한 명 때문에 줄 전체가 멈추는 이 현상을 헤드 오브 라인 블로킹(head-of-line blocking, HOL)이라 한다.

SIMULATOR

패킷 하나를 잃으면: TCP 위 HTTP/2 vs QUIC 위 HTTP/3

TCP: 패킷 평균 전달 지연—
QUIC: 패킷 평균 전달 지연—
괜히 기다린 패킷 (TCP / QUIC)—
파일 세 개(HTML·CSS·JS)의 패킷 12개를 번갈아 10 ms 간격으로 보낸다. 칸 = 앱에 넘겨진 시점, 빈 테두리 = 실제로 도착한 시점, 점선 = 도착했지만 앞의 빈칸 때문에 기다린 시간이다. 잃어버린 패킷은 약 1 RTT 뒤에 재전송본이 도착한다고 가정했다. 전달 지연은 패킷을 보낸 순간부터 앱에 넘겨질 때까지의 시간이다. 해볼 것: 잃는 패킷을 앞쪽(1~3번)으로 옮기면 TCP에서는 세 파일이 모두 늦어지지만 QUIC에서는 그 패킷이 속한 파일 하나만 늦어진다.

QUIC은 이 문제와 연결 준비 시간을 함께 풀기 위해 구글이 만들고 IETF가 2021년 표준화한(RFC 9000) 전송 프로토콜이다. 특징은 다음과 같다.

01 RTT2 RTT3 RTT TCP + TLS 1.3 QUIC 첫 연결 QUIC 0-RTT (재방문) 클라이언트서버클라이언트서버클라이언트서버 SYNSYN-ACK ACK + ClientHelloServerHello…Finished Finished + GET응답 Initial: ClientHelloServerHello…Finished Finished + GET응답 ClientHello + 0-RTT GETServerHello… + 응답 첫 응답: 3 RTT첫 응답: 2 RTT첫 응답: 1 RTT 보라 = TCP 연결, 파랑 = 암호 핸드셰이크, 초록 = HTTP 요청·응답
그림 9-5. 첫 응답까지의 왕복 수. TCP는 연결(1 RTT)을 맺은 뒤에야 TLS를 시작한다. QUIC은 둘을 한 번에 하므로 1 RTT를 아끼고, 재방문 때는 0-RTT로 하나 더 아낀다(0-RTT 데이터는 재전송 공격에 취약해 GET처럼 반복돼도 안전한 요청에만 쓴다). TLS 1.2라면 TCP 쪽은 4 RTT가 된다.
CALCULATOR

첫 응답까지 걸리는 시간: RTT가 길수록 왕복 수가 중요하다

예시 RTT
TCP + TLS 1.3—
QUIC 첫 연결—
QUIC 0-RTT—
DNS 조회, 서버 처리 시간, 데이터 전송 시간은 빼고 왕복 횟수만 셌다. 해볼 것: 같은 도시(5 ms)에서는 차이가 수 ms라 체감이 거의 없지만, 위성(600 ms)에서는 TCP+TLS 1.2가 2.4초, QUIC 0-RTT가 0.6초다. 대역폭을 늘려도 이 시간은 줄지 않는다.

2025년 기준 주요 브라우저와 대형 서비스(구글, 유튜브, 메타, 클라우드플레어 등)는 HTTP/3를 기본으로 지원하고, 웹 트래픽의 상당 부분(대략 4분의 1~3분의 1)이 QUIC으로 오간다. 다만 일부 회사망은 UDP 443번을 막기 때문에, 브라우저는 QUIC이 안 되면 조용히 TCP로 되돌아간다. TCP는 사라지지 않는다. 데이터센터 내부, 파일 전송, 수많은 기존 프로토콜은 여전히 TCP 위에 있다.

핵심 정리

  1. 전송 계층은 포트 번호로 한 컴퓨터 안의 앱을 구분한다. 연결은 프로토콜·양쪽 IP·양쪽 포트의 5-튜플로 구분된다.
  2. UDP는 8바이트 헤더만 붙여 그냥 보낸다. DNS, 게임, 음성·영상 통화, 그리고 QUIC의 바탕이 된다.
  3. TCP는 SYN → SYN-ACK → ACK로 연결을 맺고, FIN/ACK를 양쪽이 주고받아 끊는다. 순서 번호와 ACK 번호는 바이트 단위다.
  4. 손실은 타임아웃 또는 중복 ACK 3개(빠른 재전송)로 감지해 다시 보낸다. 꼬리 손실은 중복 ACK가 생기지 않아 느리다.
  5. 슬라이딩 윈도우로 ACK를 기다리는 동안에도 보낸다. 처리율 ≤ 윈도우/RTT이므로 윈도우는 BDP만큼 커야 한다.
  6. 흐름 제어(rwnd)는 받는 쪽 버퍼를, 혼잡 제어(cwnd)는 네트워크를 지킨다. 슬로 스타트와 AIMD가 톱니를 그리고, 오늘날 기본값은 CUBIC, 측정 기반 BBR도 널리 쓰인다.
  7. QUIC은 UDP 위에서 TLS 1.3을 내장해 1-RTT/0-RTT로 연결하고, 독립 스트림으로 헤드 오브 라인 블로킹을 줄이며, 연결 ID로 네트워크가 바뀌어도 이어진다.

확인 퀴즈

클라이언트가 seq=2001로 500바이트를 보냈고 모두 잘 도착했다. 서버가 돌려보낼 ACK 번호는?

2001~2500번 바이트를 받았으니 “다음엔 2501번을 기다린다”가 ACK 번호다. TCP의 번호는 패킷이 아니라 바이트 단위다.

DNS 질의와 온라인 게임이 주로 UDP를 쓰는 가장 큰 이유는?

UDP는 핸드셰이크도 재전송도 없다. 짧은 질문-답이나 “늦은 데이터는 쓸모없는” 실시간 통신에 알맞다. 필요한 신뢰성은 앱이 직접 챙긴다.

보내는 쪽이 같은 ACK 번호를 연달아 네 번(원래 1번 + 중복 3번) 받았다. TCP가 하는 일은?

중복 ACK 3개는 “그 뒤 데이터는 오는데 이것만 빠졌다”는 강한 신호다. 빠른 재전송으로 즉시 보내고, Reno라면 cwnd를 절반으로 줄인다.

RTT 50 ms, 윈도우 32 KB인 TCP 연결의 최대 처리율에 가장 가까운 것은?

32 KB × 8 비트 ÷ 0.05 s ≈ 5.2 Mbps. 처리율 ≤ 윈도우/RTT.

흐름 제어와 혼잡 제어에 대한 설명으로 옳은 것은?

rwnd는 ACK에 실려 오지만 cwnd는 아무도 알려 주지 않아 보내는 쪽이 손실·지연을 보고 추정한다. 보낼 수 있는 양 = min(cwnd, rwnd).

HTTP/2를 TCP 위에서 쓸 때 패킷 하나가 손실되면 생기는 일과, QUIC이 이를 줄이는 방법을 바르게 짝지은 것은?

TCP는 하나의 바이트 흐름이라 빈칸 뒤의 데이터는 다른 파일 것이어도 앱에 넘기지 못한다(헤드 오브 라인 블로킹). QUIC은 스트림별로 순서를 관리해 손실된 스트림만 기다린다.