포스트

전송 계층과 프로토콜

전송 계층과 프로토콜

전송 계층은 네트워크 통신에서 송신자와 수신자 간의 데이터 전송을 관리하고, 신뢰성과 정확성을 보장하는 역할을 수행합니다.

TCP/IP 4계층 모델과 OSI 7계층 모델에서 공통적으로 정의되며, 데이터가 손실 없이 정확히 전달되도록 다양한 메커니즘을 제공합니다.

신뢰성이 왜 중요할까

  • 신뢰성이란 네트워크 통신에서 데이터를 정확하고 완전하게, 즉 손실없이 순서대로 전달하는 것을 의미합니다.
  • 신뢰성이 보장되지 않는 통신은 응용 프로그램의 사용자 경험을 저하시킬 수 있습니다.

종단 간 원칙(End-toEnd Principle)

초창기 네트워크 통신망을 설계하던 공학자들은 “중간에 있는 네트워크 장비(스위치)를 고성능으로 만들어서, 데이터가 유실되지 않게 통제하자”라고 생각했습니다. 하지만 다른 아키텍처로 발전하게 됩니다.

Dumb Network, Smart Terminals

“네트워크의 뼈대를 이루는 라우터와 통신 선로(망)는 그저 데이터를 목적지를 향해 최대한 빠르게 전송하는 단순한 일(Best-Effort)에만 집중하게 하자. 데이터가 깨졌는지, 순서가 맞는지를 검증하고 복구하는 무겁고 복잡한 책임은 통신의 양 끝단(End), 즉 사용자의 PC나 서버(스마트폰, 웹브라우저 등)에게 전적으로 맡기자”

이것이 바로 종단 간 원칙입니다. 여기서 ‘종단(End)’이란 물리적인 컴퓨터 부품이 아니라, 그 안에서 실행되고 있는 ‘최종 애플리케이션 프로세스(Proceess)’를 의미합니다.

전송 계층에서의 종단 간 통신 구현

네트워크 계층(IP)이 패킷을 수신자 ‘컴퓨터’ 앞까지 배달하는 Hop-by-Hop(라우터 간) 통신을 담당한다면, 전송 계층(TCP/UDP)은 그 컴퓨터 안의 정확한 수신자에게 데이터를 꽂아 넣는 End-to-End 통신을 완성합니다.

식별자 - 포트 번호(Port Number)

IP 주소가 호스트 컴퓨터를 가리킨다면, 포트번호는 종단(실행 중인 프로세스)을 식별하는 16비트(0~65535) 고유 번호입니다.

카카오톡과 크롬 브라우저가 동시에 통신할 수 있는 이유는 패킷 헤더에 도착지 포트 번호가 적혀있기 때문입니다.

다중화(Multiplexing)와 역다중화(Demultiplexing)

여러 프로세스에서 나온 데이터를 모아 각각 알맞은 포트 번호 헤더를 붙인 뒤 말단의 네트워크 계층(IP)로 내보내는 것을 다중화(송신자)라고 합니다.

반대로 네트워크 계층에서 들어온 패킷들을 포트 번호에 맞게 알맞은 프로세스(소켓)로 데이터를 분배하는 것을 역다중화(수신자)라고 합니다. 이 과정이 종단 간 통신의 물리적 실체입니다.

종단 간 원칙은 완벽하지 않으며, 철저한 기회비용을 바탕으로 설계되었습니다.

확장성과 경제성 : 중간 라우터들이 패킷의 상태(State)를 기억하거나 에러를 고칠 필요가 없으므로 장비 비용이 저렴해지며, 네트워크 코어를 건드리지 않고도 양 끝단의 소프트웨어만 업데이트하면 새로운 프로토콜(HTTP/3, QUIC)을 전 세계에 바로 배포할 수 있는 유연성을 가집니다.

단말 기기의 오버헤드 : 양 끝단의 호스트(스마트폰, PC)가 패킷의 순서를 맞추고, 유실된 패킷을 재요청하며, 네트워크 혼잡도를 계산해야 하는 무거운 연산 부담(CPU 및 메모리 오버헤드)을 짊어지게 됩니다. (과거 PC 성능이 낮았을 때 문제가 되었습니다.)

종단 간 원칙이 깨지는 순간들

실제 산업에서는 보안과 IP 고갈 문제로 인해 이 원칙이 의도적으로 훼손되는 경우가 빈번합니다.

NAT(Network Address Translation)의 딜레마

공유기(NAT)는 부족한 IPv4 주소를 아끼기 위해 사설 IP를 공인 IP로 변환합니다. 이때 공유기는 패킷의 IP와 포트 번호 헤더를 임의로 까보고 수정합니다.

이는 “네트워크 중간 장비는 패킷 내용에 관여하지 않는다”는 종단 간 원칙을 위배하는 것입니다. 이 때문에 P2P 통신(토렌트, WebRTC)을 구현할 때, 양 끝단이 서로의 진짜 IP를 알지 못해 통신이 막히는 치명적인 문제가 발생하며, 이를 해결하기 위해 STUN/TURN 같은 복잡한 기술이 추가로 필요해졌습니다.

종단 간 암호화(E2EE - End-to-End Encryption)

비밀 채팅이나 HTTPS가 사용하는 방식입니다. 데이터를 보내는 종단에서 암호화하고, 받는 종단에서만 복호화할 수 있습니다.

중간에 있는 해커는 물론이고, 서비스를 제공하는 통신사나 서버조차 그 내용을 절대 읽을 수 없습니다. 종단 간 원칙을 보안 영역으로 확장한 사례입니다.

MSA 설계 철학(Smart Endpoints, Dumb Pipes)

최근 백엔드 아키텍처의 대세인 마이크로서비스(MSA)에서도 이 철학이 쓰입니다. 서비스 간 통신을 연결하는 메시지 큐(Kafka)나 API Gateway(Dumb Pipes)에는 복잡한 비즈니스 로직을 넣지 않고, 데이터를 소비하는 최종 마이크로서비스(Smart Endpoints)가 로직을 처리하도록 결합도를 낮추는 것이 핵심 설계 패턴입니다.

TCP(Transmission Control Protocol)

웹(HTTP), 이메일(SMTP), 파일 전송(FTP) 등 데이터의 순서가 보장되어야 하고 데이터의 유실이 없는 신뢰성있는 연결이 필요한 서비스에서 사용하는 프로토콜입니다. “속도를 조금 희생하더라도, 데이터는 반드시, 그리고 정확한 순서대로 도착해야 한다”는 명확한 철학을 가지고 있습니다.

  • 연결 지향(Connection-Oriented) : 통신을 시작하기 전과 끝낼 때, 양측이 반드시 논리적인 연결 상태(Session)를 수립하고 해제해야합니다.
  • 상태 유지(Stateful) : 운영체제는 각 TCP 연결마다 소켓(Socket) 버퍼를 할당하고, 현재 어디까지 데이터를 보냈고 어디까지 확인(ACK) 받았는지 그 상태를 메모리에 기록합니다.
  • 바이트 스트림(Byte Stream) : 애플리케이션이 넘겨준 데이터를 연속된 바이트의 흐름으로 보고, 이를 전송하기 좋은 크기(MSS, Maximum Segment Size)로 잘라서(Segment) 전송합니다.

연결의 수립과 해제

TCP가 신뢰성을 확보하는 첫걸음은 상대방이 통신할 준비가 되었는지 확인하는 것입니다.

통신의 시작: 3-Way Handshake

서버와 클라이언트가 서로의 초기 순서 번호(ISN, Initial Sequence Number)를 교환하고 논리적인 연결을 맺는 3단계 과정입니다.

  1. SYN(Synchronize)
    • 송신자(클라이언트)는 연결을 요청하기 위해 수신자(서버)에게 SYN 패킷을 전송합니다.
    • (통신을 위해 SEQ 1000번을 서버에 전송)
    • 내용 : 클라이언트의 초기 시퀀스 번호 SYN
    • 상태 : SYN_SENT
  2. SYN + ACK(Synchronize + Acknowledgment)
    • 수신자(서버)는 연결 요청을 받고, 클라이언트의 요청을 승인하는 ACK와 함께 자신의 연결 요청인 SYN을 포함하여 응답합니다.
    • (1000번을 확인 후 1001번을 요청하며 서버의 SEQ 5000번을 클라이언트에 전송)
    • 내용 : 클라이언트의 시퀀스 번호에 대한 ACK + 서버의 초기 시퀀스 번호 SYN
    • 상태 : SYN_RCVD
  3. ACK(Acknowledgment)
    • 송신자(클라이언트)는 서버의 응답을 확인한 뒤, 최종적으로 ACK 패킷을 보내 연결이 완료되었음을 알립니다.
    • (5000번을 확인 후 5001번 요청을 서버에 전송)
    • 내용 : 서버의 시퀀스 번호에 대한 ACK
    • 상태 : ESTABLISHED - 통신 가능

통신의 종료: 4-Way Handshake

데이터 전송이 끝나면, 자원(버퍼, 포트)을 반환하기 위해 우아하게 연결을 종료(Graceful Teardown)합니다.

  1. FIN(Connection Termination Request)
    • 클라이언트가 연결 종료를 요청하며 FIN 패킷을 전송합니다.
    • 상태 : FIN_WAIT_1
  2. ACK(Acknowledgment for FIN)
    • 서버가 FIN 패킷을 받고 이를 확인했다는 ACK 패킷을 송신자에게 전송하고 처리할 데이터가 남아있는지 확입합니다.
    • 상태 : CLOSE_WAIT
  3. FIN(Receiver’s Termination Request)
    • 남은 데이터 처리가 끝나면 서버도 연결 종료를 요청하며 FIN 패킷을 클라이언트에게 전송합니다.
    • 상태 : LAST_ACK
  4. ACK(Acknowledgment for Receiver’s FIN)
    • 클라이언트가 서버의 FIN 패킷을 받고, 종료를 확인하는 ACK 패킷을 수신자에게 전송합니다.
    • 클라이언트는 혹시 모를 패킷 유실을 대비해 일정 시간(보통 1~2분) 동안 TIME_WAIT 상태에 머문 후 소켓을 완전히 닫습니다.

신뢰성을 보장하는 3대 제어 메커니즘

서버의 네트워크 성능을 튜닝할 때 가장 중요하게 보는 지표들입니다.

오류 제어(Error Control)

패킷이 중간에 유실되거나 손상되었을 때 이를 복구합니다.

수신자는 데이터를 받을 때마다 ACK(수신받은 SEQ)를 응답합니다. 만약 송신자가 설정한 타임아웃(RTO) 시간 내에 ACK가 오지 않거나, 동일한 ACK를 3번 연속으로 받으면(Fast Retransmit), 해당 패킷이 유실되었다고 판단하고 즉시 재전송(Retransmission) 합니다.

흐름 제어(Flow Control)

수신자의 처리 능력을 넘어서는 데이터가 쏟아져 발생하는 버퍼 오버플로우를 막습니다.

Sliding Window : 수신자는 ACK를 보낼 때, 자신의 버퍼에 현재 남은 공간인 수신 윈도우 크기(rwnd)를 TCP 헤더에 담아 송신자에게 알려줍니다. 송신자는 절대 이 rwnd 값보다 많은 데이터를 한 번에 보내지 못합니다.

혼잡 제어(Congestion Control)

수신자가 아니라 ‘네트워크 망 전체’가 트래픽으로 뻗어버리는 붕괴 현상(Congestion Collapse)을 막기 위한 송신자의 제어 방식입니다.

송신자는 네트워크 상태를 짐작하는 혼잡 윈도우(cwnd)를 독자적으로 유지합니다.

  • Slow Start : 처음 통신을 시작할 때는 cwnd를 1부터 시작해 지수 함수적으로(1 → 2 → 4 → 8) 폭발적으로 늘리며 네트워크의 대역폭 한계를 탐색합니다.
  • AIMD(합산 증가, 곱산 감소) : 패킷 유실(혼잡)이 감지되면 데이터 전송량을 절반으로 떨어뜨리고(곱산 감소), 이후에는 선형적으로 조심스럽게 늘립니다(합산 증가).

TCP의 한계

TIME_WAIT 포트 고갈(Port Exhaustion)

4-Way Handshake의 마지막 단계에서 능동적으로 연결을 끊은 쪽(Active Closer, 주로 클라이언트 측이나 로드밸런서)은 반드시 TIME_WAIT 상태에 빠져 약 1~2분간 포트를 쥐고 놓아주지 않습니다. 이는 네트워크상에 지연되어 도착하는 잉여 패킷들을 안전하게 흡수하기 위한 필수 방어 기제입니다.

  • 문제점 : 트래픽이 폭주하는 마이크로서비스 환경에서 짧은 생명주기를 가진 TCP 연결(Short-lived connections)이 무수히 생성되고 끊어지면, 가용 포트(약 6만 개)가 모두 TIME_WAIT 상태로 잠겨버려 더 이상 외부 통신을 할 수 없는 장애가 발생합니다.
  • 해결책 : Connection Pool(Keep-Alive)을 적극적으로 사용하여 TCP 연결을 맺고 끊는 횟수 자체를 줄여야 합니다.

HOLB, Head-of-Line-Blocking

TCP는 반드시 데이터의 ‘순서’를 보장해야 합니다.

  • 문제점 : 여러 개의 데이터 스트림을 하나의 TCP 연결로 보낼 때, 중간에 3번 패킷이 유실되면, 정상적으로 도착한 4, 5, 6번 패킷들은 애플리케이션으로 올라가지 못하고 OS 버퍼에 갇혀 3번 패킷이 재전송될 때까지 하염없이 대기해야 합니다.
  • 해결책 : 이 근본적인 아키텍처 한계를 극복하기 위해, 현대의 HTTP/3는 과감하게 TCP를 버리고 UDP위에서 독자적인 흐름 제어를 수행하는 QUIC 프로토콜을 도입하게 되었습니다.

UDP(User Datagram Protocol)

“네트워크 계층(IP)이 제공하는 기능에 최소한의 것(포트 번호와 오류 검출)만 얹어서, 최대한 빨리 애플리케이션으로 던져주자”

  • 비연결형(Connectionless) : TCP처럼 3-Way Handshake로 사전에 인사를 나누는 과정이 없습니다. 상대방이 살아있는지, 내 패킷을 받을 준비가 되었는지 고려하지 않고 데이터를 즉시 쏘아 보냅니다.
  • 무상태(Stateless) : 운영체제(OS)는 UDP 통신을 위해 복잡한 상태 정보(Sequence Number, Window Size 등)를 메모리에 기억하지 않습니다. 덕분에 서버의 자원 소모가 극단적으로 적습니다.
  • Fire and Forget : 패킷을 보낸 후 확인 응답(ACK)을 기다리지 않으며, 중간에 패킷이 유실되더라도 절대 재전송하지 않습니다.

UDP 헤더 구조

기본적으로 20바이트가 넘는 무거운 TCP 헤더와 달리, UDP 헤더는 단 8바이트로 구성되어 있습니다.

  • Source Port(2byte) : 송신자의 포트 번호
  • Destination Port(2byte) : 수신자의 포트 번호
  • Length(2byte) : 헤더와 데이터를 합친 전체 길이
  • Checksum(2byte) : 패킷이 날아오는 동안 데이터가 망가지지 않았는지(Bit error) 최소한의 검증을 하는 수학적 값(선택 사항)

이 4가지 정보가 끝입니다. 흐름 제어나 혼잡 제어를 위한 복잡한 필드는 모두 과감히 버렸습니다.

기술적 트레이드 오프

장점(압도적인 속도와 유연성)

  • 연결 설정(Handshake)으로 인한 지연 시간(0-RTT)이 전혀 없습니다.
  • 헤더 오버헤드가 작아 네트워크 대역폭을 매우 효율적으로 사용합니다.
  • 멀티캐스트(Multicast)와 브로드캐스트(Broadcast)를 지원합니다. (TCP는 구조상 1:1 통신만 가능하지만, UDP는 한 번에 수만 명에게 똑같은 데이터를 뿌리는 실시간 방송에 최적화되어 있습니다.)

단점(신뢰성 부재)

  • 패킷 유실(Packet Loss) : 라우터 버퍼가 꽉 차서 패킷을 버려도 송신자는 알 길이 없습니다.
  • 순서 역전(Out-of-order) : 1번, 2번, 3번 패킷을 보냈는데 수신자에게는 3번, 1번, 2번 순서로 도착할 수 있습니다. UDP는 이를 조립해 주지 않습니다.
  • 혼잡 제어 부재 : 네트워크가 막혀도 UDP는 계속 데이터를 보내기 때문에, 자칫하면 네트워크 전체를 마비시킬 위험이 있습니다.

HTTP/3와 QUIC

불과 몇 년 전까지만 해도 UDP는 DNS(도메인 해석), 실시간 스트리밍(WebRTC, 영상통화), 그리고 빠른 반응속도가 생명인 온라인 게임(FPS 등)에서만 제한적으로 쓰이는 서브 프로토콜이었습니다. 하지만 현대에 이르러 메인스트림 기술로 부상했습니다.

TCP의 구조적 한계

현대의 웹(HTTP/2)은 성능을 끌어올렸지만, 여전히 밑단에서 TCP를 사용했습니다. TCP는 패킷이 하나만 유실되어도 뒤따라오는 모든 데이터가 멈춰버리는 ‘HOLB’ 라는 치명적인 약점을 가집니다. 게다가 TCP는 전 세계 수십억 대 기기의 OS 커널에 하드코딩되어있어, 이를 뜯어고치는 것은 현실적으로 불가능했습니다.

구글의 UDP 위에 신뢰성 구현

구글의 엔지니어들은 과감한 결단을 내립니다. OS 커널이 건드리지 않는 UDP를 가져와서, 그 위(애플리케이션 계층, 유저 스페이스)에 ‘QUIC(Quick UDP Internet Connections)’ 이라는 새로운 프로토콜을 독자적으로 개발해 얹은 것입니다.

QUIC는 UDP의 빠른 연결 속도를 챙기면서도, 암호화(TLS 1.3)와 흐름 제어/재전송 알고리즘을 최적화하여 구현했습니다. 결과적으로 패킷 손실 시 전체 스트림이 멈추는 TCP의 HOLB 문제를 해결했으며, 이 기술이 표준화된 것이 바로 최신 웹 표준인 HTTP/3입니다.

소켓(Socket)과 연결의 4요소(4-Tuple)

운영체제(OS)는 이 연결을 메모리 상에 소켓(Socket) 이라는 객체로 추상화하여 관리합니다. 하나의 논리적인 TCP 연결(소켓)은 다음 4가지 요소(4-Tuple) 의 조합으로 유일하게 식별됩니다.

  • Source IP (송신자 IP)
  • Source Port (송신자 포트 - 보통 운영체제가 남는 번호를 임의로 부여, Ephemeral Prot)
  • Destination IP (수신자 IP)
  • Destination Port (수신자 포트 - 웹 서버의 80이나 443)

포트 고갈 문제

하나의 웹 서버(하나의 IP, 하나의 443 포트)가 수백만 명의 클라이언트를 동시에 받을 수 있는 이유는, 접속하는 클라이언트들의 IP와 포트 번호가 모두 다르기 때문에 OS가 이를 전부 다른 소켓(연결)으로 식별할 수 있기 때문입니다. 반대로 내 PC 하나에서 특정 서버 1대로 부하 테스트(Load Test)를 걸 때는, 내 PC의 Source Port 개수 한계(약 6만 개)에 부딪혀 통신이 막히는 포트 고갈(Port Exhaustion) 현상을 자주 마주치게 됩니다.

RDT(Reliable Data Transfer, 신뢰적 데이터 전송)

TCP가 완벽한 신뢰성을 제공한다고 설명했습니다. 하지만 그 밑바탕을 이루는 IP(네트워크 계층)는 패킷을 잃어버리거나 순서를 뒤섞는 ‘신뢰할 수 없는 채널(Unreliable Channel)’ 입니다.

RDT는 “신뢰할 수 없는 하위 계층(Unreliable Channel) 위에서 어떻게 신뢰할 수 있는 통신(Reliable Channel)을 구현할 것인가”에 대한 이론적 모델입니다. 네트워크 환경의 신뢰성 여부에 따라 여러 버전으로 나뉘며, 문제를 해결하는 매커니즘을 하나씩 추가하며 발전합니다.

RDT 1.0: 완벽하게 신뢰할 수 있는 채널(비현실적 가정)

하위 채널에서 비트 오류(Bit Error)도 발생하지 않고, 패킷 유실(Packet loss)도 절대 발생하지 않습니다.

  • 에러 제어나 흐름 제어가 전혀 필요 없습니다. 송신자는 그냥 데이터를 보내고 수신자는 주는 대로 받으면 됩니다. (현실에는 존재하지 않는 이상적인 상태)

RDT 2.0 ~ 2.2: 비트 오류가 발생하는 채널

패킷이 도착하긴 하지만, 물리적인 노이즈로 인해 0이 1로, 1이 0으로 바뀌는 비트오류(Bit Error) 가 발생할 수 있습니다.

RDT 2.0

  • 오류 검출(Error Detection) : 패킷에 체크섬(Checksum) 필드를 추가하여 수신자가 데이터의 손상 여부를 판단하게 합니다.
  • 피드백(Feedback) : 수신자는 덷이터를 잘 받았으면 ACK(Acknowledge), 손상되었으면 NAK(Negative Acknowledge)를 송신자에게 보냅니다. (이를 ARQ: Automatic Repeat reQuest 프로토콜이라고 합니다.)
  • 재전송(Retransmission) : 송신자가 NAK를 받으면 해당 패킷을 다시 보냅니다.

RDT 2.1 & 2.2

만약 수신자가 보낸 ACK나 NAK 패킷 자체에 비트 오류가 생기면 송신자는 수신자가 데이터를 제대로 받았는지 알 길이 없어 무작정 재전송을 하게 되고, 수신자 입장에서는 이것이 ‘새로운 패킷’인지 ‘중복된 패킷’인지 구별할 수 없는 중복(Duplicate) 문제가 발생합니다.

  • 해결(RDT 2.1) : 이를 해결하기 위해 패킷에 순서 번호(Sequence Number) 를 도입합니다. RDT 2.1에서는 단순히 0과 1 두 개의 번호만 번갈아 사용하여 중복 여부를 파악합니다.
  • 최적화(RDT 2.2) : NAK를 없애고, 수신자가 ‘가장 마지막으로 정상 수신한 패킷의 순서 번호를 포함한 ACK(Duplicate ACK)’ 만 보내도록 프로토콜을 단순화합니다. (이것이 현대 TCP가 NAK 없이 동작하는 근본 원리입니다.)

RDT 3.0: 비트 오류와 패킷 유실이 모두 발생하는 채널

데이터가 깨지는 것을 넘어, 라우터 버퍼가 터져서 패킷 자체가 통째로 증발(Packet Loss) 해 버릴 수 있는 현실적인 인터넷 환경입니다.

  • 타이머(Countdown Timer) : 패킷이 유실되면 수신자는 패킷이 안 왔으니 아무 반응도 하지 않고, 송신자는 영원히 ACK를 기다리는 데드락(Deadlock)에 빠집니다. 이를 해결하기 위해 송신자는 패킷을 보낼 때마다 타이머를 켭니다.
  • 타임아웃 재전송 : 설정된 시간(Timeout) 내에 ACK가 오지 않으면, 송신자는 패킷이 유실되었다고 간주하고 패킷을 재전송(Retransmission) 합니다. (만약 패킷이 유실된 게 아니라 지연된 것이라면 RDT 2.1에서 도입한 ‘순서 번호’를 통해 수신자가 중복을 걸러냅니다.)

RDT 3.0의 한계: Stop-and-Wait 방식

RDT 3.0은 패킷을 하나 보내고 ACK가 올 때까지 하염없이 기다리는 ‘정지-대기(Stop-and-Wait)’ 방식을 사용합니다.

완벽한 신뢰성은 달성했지만, 네트워크의 물리적 대역폭을 전혀 활용하지 못해 성능(Utilization)이 느리다는 치명적인 단점을 가지게 됩니다.

이 기사는 저작권자의 CC BY-NC 4.0 라이센스를 따릅니다.