패킷은 정말 사라지는데, 파일은 멀쩡히 도착합니다

네트워크 미니랩 네 번째. 데이터 조각이나 ACK이 사라졌을 때 TCP가 기다리고 다시 보내는 복구 과정을 살펴봅니다.

인터넷에서 패킷은 정말로 사라집니다. 그런데도 내려받은 파일은 한 바이트도 깨지지 않습니다. 비결은 택배와 같습니다.

상자마다 수령 문자를 기다리고, 문자가 안 오면 그 상자만 다시 보낸다.

이 데모는 그 약속 하나만 보여줍니다.

세 가지 약속

  • 받은 범위를 알린다 — 서버는 "여기까지 받았어요"라는 확인 응답(ACK)을 보냅니다. 실제 TCP의 ACK은 다음에 기대하는 바이트 번호라 앞부분을 한꺼번에 확인할 수도 있습니다.
  • 답이 없으면 다시 보낸다 — 보낸 쪽은 아직 확인되지 않은 데이터를 위해 재전송 타이머를 관리합니다. 시간이 지나면 가장 앞의 미확인 조각부터 다시 보냅니다.
  • 번호로 중복을 거른다 — 답장이 사라져서 같은 조각이 두 번 도착해도, 받는 쪽은 번호를 보고 이미 받은 범위를 버립니다. 같은 바이트가 데이터 흐름에 두 번 반영되지 않습니다.

이때 쓰는 번호가 3-way handshake에서 교환한 그 번호입니다.

패킷을 일부러 잃어 보세요

아래 파란 버튼만 누르세요

사라진 뒤 다시 도착하는 5장면

시나리오
  1. 1조각 보내기
  2. 2조각 유실
  3. 3답 없음
  4. 4다시 보내기
  5. 5복구 완료
준비
‘조각이 사라질 때’ 실험을 시작할 준비가 됐어요

아래 ‘실험 시작’을 누르면 지금 무슨 일이 생겼는지 한 장면씩 보여드려요.

대기 중
내 컴퓨터192.168.0.10
조각 1대기조각 2대기
타이머 꺼짐
Redis 서버192.168.0.20 : 6379
?아직?아직
번호로 중복을 걸러요
시나리오: 조각이 사라질 때보내기 시작을 누르면 데이터 두 조각이 어떤 일을 겪는지 한 단계씩 보여드려요. 시나리오를 바꿔가며 비교해 보세요.

약속 세 가지만

패킷이 사라져도 데이터가 완성되는 이유

01받은 범위를 알려줘요.

서버는 “여기까지 받았다”고 ACK으로 알려줘요. 실제 TCP에서는 여러 바이트를 한 번의 누적 ACK으로 확인할 수도 있어요.

02답이 없으면 다시 보내요.

보낸 쪽은 아직 확인되지 않은 데이터를 위해 타이머를 관리해요. 시간이 지나면 가장 앞의 미확인 조각부터 다시 보내요.

03번호로 중복을 빼요.

답장이 사라져서 같은 조각이 두 번 도착해도, 받는 쪽은 번호를 보고 이미 받은 범위를 알아봐요. 같은 바이트를 받은 데이터에 두 번 넣지 않아요.

보내고 타이머 켜기ACK 기다리기안 오면 재전송빠짐없이 완성
규격 기반 시뮬레이션입니다.

이 페이지는 실제 패킷 캡처가 아니라 TCP의 확인 응답과 재전송 원리를 따라 만든 교육용 시뮬레이션입니다. 이해를 돕기 위해 조각 번호(1, 2)를 썼지만 실제 TCP는 바이트 단위의 누적 ACK을 쓰며 ACK을 잠시 늦춰 보낼 수도 있습니다. RTO는 왕복 시간을 바탕으로 계산하고, 만료되면 가장 오래 확인되지 않은 세그먼트를 재전송합니다. 빠른 재전송, 선택적 확인(SACK), 혼잡 제어는 다루지 않습니다.

참고: RFC 9293 (TCP) · RFC 6298 (Retransmission Timer)

데모만 따로 보려면 전체 화면으로 열기.

시나리오를 하나 고르면 결과까지 다섯 장면만 보여줍니다.

  1. 손실 없음 — 보내고, 답 받고, 다음 조각. 기본 리듬입니다.
  2. 조각이 사라질 때 — ❌ 유실 → ⏰ 타임아웃 → 재전송.
  3. 답장이 사라질 때 — 서버가 번호로 중복을 걸러내는 순간.

실험 시작을 누르고, 화면 위의 현재 장면과 클라이언트 카드의 타이머만 눈여겨보세요. 기술 용어를 몰라도 흐름을 이해할 수 있습니다.

타이머를 기다리지 않는 더 빠른 길

타임아웃은 확실하지만 느립니다. 답이 없다는 걸 확인하려면 정해둔 시간을 그대로 흘려보내야 하기 때문입니다. 그래서 실제 TCP에는 지름길이 하나 더 있습니다.

2번 조각만 사라지고 3·4·5번이 계속 도착했다고 해봅시다. 받는 쪽은 그때마다 "아직 2번이 필요해요"라는 같은 ACK을 반복해서 보냅니다. 보낸 쪽이 이 중복 ACK을 세 번 받으면, 타이머가 끝나기를 기다리지 않고 곧바로 2번을 다시 보냅니다. 이것이 빠른 재전송입니다.

여기에 SACK을 쓰면 받는 쪽이 "2번은 아직인데 3~5번은 이미 받았어요"라고 구체적 으로 알려줄 수 있습니다. 그러면 보낸 쪽은 정말 빠진 조각만 골라 보냅니다. SACK이 없던 시절에는 뒷부분을 통째로 다시 보내는 낭비가 있었습니다.

손실은 속도로 나타납니다

여기서 중요한 점 하나. TCP에서 손실은 오류가 아니라 지연으로 드러납니다. 파일은 멀쩡히 다 받아지는데 이상하게 느린 상황의 흔한 원인입니다.

게다가 TCP는 손실을 "회선이 붐빈다"는 신호로 읽고 보내는 속도를 스스로 줄입니다 (혼잡 제어). 그래서 손실률이 조금만 올라가도 체감 속도는 그보다 크게 떨어집니다.

# 재전송이 실제로 얼마나 일어났는지 (리눅스)
netstat -s | grep -i retrans

# 특정 연결의 RTT와 재전송 횟수
ss -ti

ss -ti 출력의 rtt는 측정된 왕복 시간, retrans는 재전송 횟수입니다. 느리다는 느낌만으로 회선을 탓하기 전에 이 숫자를 먼저 보면 판단이 빨라집니다.

실제로 재 봤더니 손실은 0%였습니다

이 글을 쓰는 노트북(Wi-Fi 연결)에서 1.1.1.1로 100번 보내 봤습니다. 결과가 의외였습니다.

100 packets transmitted, 100 received, 0% packet loss
rtt min/avg/max/mdev = 2.702/12.170/48.677/11.208 ms

하나도 안 잃었습니다. 그런데 오른쪽 줄을 보세요. 가장 빠른 응답이 2.7ms, 가장 느린 응답이 48.7ms입니다. 18배 차이입니다.

이게 이 글에서 가장 하고 싶은 이야기입니다. 흔히 "인터넷이 느리다"를 손실 탓으로 돌리지만, 정상적인 회선에서 실제로 문제가 되는 건 손실이 아니라 흔들림(지터)인 경우가 많습니다.

거리에 따른 차이도 같이 재 봤습니다. 각 30회입니다.

목적지최소 RTT평균최대손실
1.1.1.12.8ms11.5ms54.9ms0%
8.8.8.832.4ms41.8ms74.8ms0%
www.rfc-editor.org3.0ms12.0ms34.3ms0%

8.8.8.8만 최소값이 32ms입니다. 이건 흔들림이 아니라 물리적으로 더 먼 곳에 있다는 뜻입니다. 최소 RTT는 거의 순수한 거리이고, 최대와 최소의 차이가 혼잡· 무선 재전송 같은 변동입니다. 둘을 나눠 보면 "멀어서 느린 것"과 "불안정해서 느린 것"을 구분할 수 있습니다.

이 흔들림이 재전송과 직접 연결됩니다

TCP는 "언제까지 답이 없으면 다시 보낼지"(RTO)를 측정한 RTT의 평균과 흔들림으로 계산합니다. 표준이 정한 식의 모양은 이렇습니다.

RTO = 평균 RTT + 4 × (RTT 흔들림)

위 측정값(평균 12.2ms, 흔들림 11.2ms)을 그대로 넣으면 약 57ms가 나옵니다. 만약 흔들림이 1ms로 잔잔했다면 16ms였을 겁니다.

즉 흔들리는 회선은 손실이 없어도 손해를 봅니다. 진짜로 조각이 사라졌을 때 다시 보내기까지 더 오래 기다리게 되니까요. 무선이 유선보다 답답하게 느껴지는 이유가 여기에 상당 부분 있습니다.

실제로 이 노트북에서 열려 있는 연결을 ss -ti로 들여다보니 이런 값들이 잡혔습니다.

rtt:15.083/12.377   retrans:0/1
rtt:52.032/18.304   cwnd:10

rtt:15.083/12.377은 평균 15ms에 흔들림 12ms라는 뜻입니다. 위 계산과 같은 상황이 지금 이 순간에도 벌어지고 있는 것입니다.

그래서 파일이 멀쩡한 이유

받는 쪽이 ACK 으로 "여기까지 받았다"를 계속 알려 주고, 보낸 쪽은 답이 없으면 가장 앞의 미확인 조각부터 다시 보냅니다. 같은 조각이 두 번 도착해도 번호를 보고 걸러 내므로 파일에는 중복이 남지 않습니다.

눈에 보이는 증상이 "끊김"이 아니라 "느려짐"인 게 이 때문입니다. 잃어버린 조각은 복구되지만, 복구하는 동안 기다린 시간은 사라지지 않으니까요.

손실 없이도 느려지는 경우가 있습니다

손실이 생기면 재전송 때문에 느려진다는 것까지는 자연스럽습니다. 그런데 손실이 전혀 없는데도 체감 속도가 나쁜 경우가 있고, 원인이 다른 곳에 있습니다.

보내는 쪽은 답을 받지 않은 상태로 무한정 보낼 수 없습니다. 한 번에 회선 위에 띄워 둘 수 있는 양에 상한이 있고, 그 양을 다 보내면 답이 올 때까지 기다립니다. 그래서 한 번 오가는 데 걸리는 시간이 길면, 회선의 대역폭이 아무리 넓어도 그 기다림 때문에 실제 속도가 눌립니다.

거리가 먼 서버에서 파일을 받을 때 회선을 바꿔도 별로 빨라지지 않는 상황이 이 경우입니다. 병목이 대역폭이 아니라 왕복 시간과 한 번에 띄울 수 있는 양의 곱에 걸려 있기 때문입니다.

그래서 느린 원인을 찾을 때 손실률과 왕복 시간을 함께 봐야 합니다. 손실률이 0 인데 느리다면 재전송을 아무리 들여다봐도 답이 나오지 않습니다.

확인한 자료

이해를 돕기 위해 조각 번호(1, 2)와 즉시 오는 ACK을 썼지만 실제 TCP는 바이트 단위의 누적 ACK을 사용하며 ACK을 잠시 늦춰 보낼 수도 있습니다. RTO는 측정한 왕복 시간을 바탕으로 계산하고, 만료되면 가장 오래 확인되지 않은 세그먼트를 재전송합니다. 위에서 설명한 빠른 재전송·SACK·혼잡 제어는 글에서만 다루고 데모 화면에는 넣지 않았습니다. 데모는 타임아웃 재전송 한 가지에 집중합니다.

그런데 빠짐없이 배달해도 내용을 도청당하면 소용없겠죠? 암호화된 연결이 만들어지는 과정은 TLS 핸드셰이크 랩에서 이어집니다.

확인한 자료


관련 글