단북

프로토콜 계층

TCP가 연결을 맺고, 데이터 순서를 보장하고, 끊는 과정을 tcpdump와 시퀀스 다이어그램으로 정리합니다. 3-way handshake와 TCP 플래그가 핵심입니다.

HTTP 요청 하나를 보내기 전에, 그 아래에서는 TCP가 연결을 먼저 맺습니다. 이 레슨에서는 TCP가 연결을 맺고, 데이터 순서를 보장하고, 끊는 과정을 정리합니다. 이 내용은 “왜 첫 요청이 더 느린가”, “왜 HTTP/2가 연결을 재사용하는가”, “fetch가 왜 타임아웃되는가” 같은 프론트엔드 성능·디버깅 주제와 직접 이어집니다.

계층은 아래에 의존하고, 아래를 숨긴다

네트워크 스택의 각 계층은 바로 아래 계층이 제공하는 보장에 의존하면서, 동시에 아래의 복잡함을 숨겨 위 계층에 단순한 인터페이스를 제공합니다.

  • 웹앱은 HTTP(URL·쿠키·메서드)에 의존합니다. 아래 계층에 문제가 생기면 보통 에러나 높은 지연으로 드러납니다.
  • HTTP는 브라우저와 웹 서버가 구현하며, 이들은 OS가 제공하는 TCP를 사용합니다.
  • TCP는 OS 안에서 IP에 의존해 패킷을 컴퓨터 사이로 전달합니다.
  • IP는 와이파이·이더넷 같은 하드웨어와 드라이버에 의존합니다.

같은 단어가 계층마다 다른 뜻으로 쓰입니다. 와이파이의 “connection”, TCP의 “connection(세션)”, 웹 로그인 유지에 쓰는 “session(쿠키)“은 모두 별개의 개념입니다. 문맥에 따라 어느 계층의 이야기인지 구분해야 합니다.

tcpdump로 패킷 살펴보기

tcpdump는 컴퓨터를 오가는 네트워크 트래픽을 보여주는 도구입니다. 평소 브라우저 DevTools가 HTTP 수준을 보여준다면, tcpdump는 그보다 아래인 패킷 수준을 보여줍니다. ping을 잡아보면 ICMP echo request가 나가고 echo reply가 돌아오는 모습을 볼 수 있습니다.

$ sudo tcpdump -n host 8.8.8.8   # 특정 호스트와의 트래픽
$ sudo tcpdump -n port 53        # DNS 트래픽 (포트 53)

웹 서버와의 통신을 잡으면 보이는 것은 패킷 헤더(메타데이터) 뿐이고, 실제 HTTP 본문은 보이지 않습니다(HTTPS면 암호화되어 더욱 그렇습니다). 여기서 한 가지가 눈에 띕니다. 많은 패킷의 length가 0이라는 점입니다. 실제 데이터를 주고받기 전후로 연결을 세우고(setup) 끊는(teardown) 절차가 따로 필요하기 때문입니다. 이 “연결을 먼저 맺는 비용”이 뒤에서 설명할 첫 요청 지연의 원인입니다.

시퀀스 번호와 ACK

TCP는 단순히 데이터를 보내는 데 그치지 않고, 보낸 데이터를 상대가 받았는지 확인하며 순서를 보장합니다. 핵심 방법은 각 데이터 조각에 시퀀스 번호를 붙이는 것입니다.

  • 받는 쪽은 “여기까지 받았다”는 ACK(acknowledgement, 수신 확인) 를 보냅니다.
  • 패킷이 중간에 사라지면 ACK가 오지 않고, 송신 측은 이를 알아채 해당 데이터를 재전송합니다.
  • 네트워크가 패킷 순서를 뒤섞어 보내더라도(out-of-order), 받는 쪽 OS가 시퀀스 번호를 기준으로 원래 순서대로 재조립합니다.
  • 시퀀스 번호는 무작위 값에서 시작해, 이전 연결이나 다른 연결의 패킷과 섞이는 것을 방지합니다.

이 확인 절차 덕분에 TCP는 “보낸 데이터가 순서대로 빠짐없이 도착한다”는 보장을 제공합니다. 그래서 서버가 데이터를 보내는 동안, 클라이언트는 보낼 본문이 없어도(length 0) ACK 패킷을 계속 되돌려 보냅니다. 애플리케이션 입장에서는 이 모든 과정이 숨겨져, 그냥 “데이터를 쓰면 상대가 받는다”처럼 보입니다.

주요 TCP 플래그

플래그이름의미
SYNSynchronize연결 시작, 시퀀스 번호 동기화
ACKAcknowledge”여기까지 받았다” 확인
FINFinish정상적으로 연결 종료
RSTReset비정상 종료 / “그런 포트 없음”

3-way handshake

TCP 연결을 세우는 과정은 패킷 3개를 주고받는 것으로 이루어지며, 이를 3-way handshake라고 합니다.

Client                          Server
  │  ── SYN ──────────────────▶  │   "연결하고 싶어"
  │  ◀──────────── SYN + ACK ──  │   "좋아, 나도 준비됐어"
  │  ── ACK ──────────────────▶  │   "확인. 시작하자"
  │                              │
  │  ◀──── 이후 데이터 교환 ────▶ │

이 왕복 때문에 새 연결을 맺는 데에는 한 번의 왕복 시간(RTT)이 추가로 든다는 점이 중요합니다. HTTPS라면 그 위에 TLS 핸드셰이크 왕복까지 더해집니다. 그래서 같은 서버에 여러 요청을 보낼 때 매번 새 연결을 맺으면 느려집니다.

이 비용을 줄이려고 HTTP는 한 번 맺은 연결을 여러 요청에 재사용(keep-alive) 합니다. HTTP/2는 한 발 더 나아가 하나의 연결로 여러 요청을 동시에 처리합니다(2편에서 다룬 멀티플렉싱). “사이트의 첫 요청이 유독 느리고 이후 요청은 빠른” 현상의 상당 부분이 이 연결 수립 비용 때문입니다.

브라우저↔웹서버 TCP 세션이 타임아웃되는 원인으로 가장 적절한 것은?

TCP 혼잡 제어 (Congestion Control)

TCP가 데이터를 무조건 최대 속도로 보내지 않는 데에는 이유가 있습니다. 빠른 네트워크 둘 사이에 느린 링크가 끼어 있을 때 한쪽이 전속력으로 데이터를 쏟아부으면, 중간 라우터가 이를 다 처리하지 못해 버퍼가 넘치고 패킷이 버려집니다.

그래서 TCP는 처음에는 천천히 보내다가(slow start), 상대의 ACK로 “받는 쪽이 따라올 수 있다”가 확인되면 점차 속도를 올립니다. 패킷이 버려지면 다시 속도를 낮추고 천천히 회복합니다. 라우터는 링크가 혼잡하면 일부러 패킷을 버려 혼잡 신호를 보내고, 양쪽 끝점은 이를 감지해 속도를 줄입니다. 이 덕분에 수많은 연결이 한 네트워크를 공유해도 전체가 무너지지 않습니다.

여기서 나오는 결론은 패킷 손실이 곧 회선 단절을 뜻하지 않는다는 것입니다. 손실은 상당수가 “속도를 줄이라”는 정상적인 혼잡 신호입니다.

한 서비스가 실패할 때마다 무조건 즉시 여러 번 재시도하도록 만들어, 과부하 상황에서 재시도가 폭주해 장애를 더 키운 사례가 있습니다. 네트워크가 혼잡할수록 무분별한 재시도는 상황을 악화시킵니다. 프론트엔드에서 API 재시도 로직을 넣을 때는 횟수를 제한하고 지수 백오프(재시도 간격을 점점 늘리기) 를 적용하는 것이 좋습니다.

이 레슨을 다 읽으셨나요?

완료 표시는 이 브라우저에 저장됩니다.