TCP와 UDP로 알아보는 네트워킹
우리는 도서관에 가서 종이들을 뒤지는 대신 구글에 검색하고 ChatGPT와 말씨름을 벌인다. 친구와 같이 비디오 게임을 하기 위해 오락실으로 갈 이유도 없다. 각자의 집에서 원격으로 접속하면 되지 않는가? 혹자는 작금 인류사상 최고의 발명품으로 꼽는 이 인터넷은, 어떻게 작동하는 것일까? 이 질문의 답변은 너무나 방대하여 차마 이곳에 모두 담을 수 없으나, 당신의 컴퓨터가 인터넷의 웹사이트를 불러오는 과정과, 멀티플레이어 비디오 게임이 동작하는 과정을 따라가며 그 형체를 어렴풋이 느낄 수는 있을 것이다.
TCP/IP
Transmission Control Protocol / IP
TCP/IP (Transmission Control Protocol / IP)
는 인터넷에서 쓰는 약속, 곧 서로 같은 방식으로 대화하기 위한 규칙들을 묶어 놓은 체계다. 인터넷이 가진 그 이전에 존재하던 통신들과 비교하여 가장 큰 차이점이자 강점이 무엇이라고 생각하는가? 통합성이다. 전 세계 그 어떤 컴퓨터나 웹사이트도 모두 같은 규칙을 따른다. 이 규칙들을 역할별로 나누어 정리해 둔 표준의 묶음이 바로 TCP/IP이다.
여기서 이름이 비슷한 두 주인공을 먼저 구분해 두자. TCP/IP는 여러 규칙을 층위로 엮은 큰 체계이고, TCP는 그 가운데 전송 계층의 하나인 개별 규칙이다. 같은 이름처럼 보이지만, 범위가 다르다. 아래에서 차근차근 살펴보자.
flowchart TB A["응용 계층: HTTP/HTTPS, DNS, SMTP, SSH"] --> B["전송 계층: TCP, UDP"] B --> C["인터넷 계층: IP, ICMP, IGMP"] C --> D["네트워크 접근 계층: Ethernet, Wi-Fi, ARP, MAC"]
위 그림을 보면 알 수 있듯 TCP/IP는 4가지의 계층으로 나뉜다. 여기서 “계층”은 역할별 층위를 뜻한다. 각 층은 바로 아래·위 층과만 대화한다. 택배가 포장을 맡는 사람, 배달을 맡는 사람, 고객 응대를 맡는 사람으로 나뉘어 협업하듯, 네트워크도 일을 나눠서 처리한다.
- 네트워크 접근 계층
- 이 계층은 물리적인 인터넷 연결을 제공한다. 인터넷 설정에서 찾아볼 수 있는 MAC 주소가 여기에 속하며, 이는 기기마다 붙은 일종의 주민등록번호 같은 식별 코드다.
- 우리가 흔히 사용하는 이더넷 케이블, 와이파이 같은 기술도 궤를 같이 한다. 스위치나 AP(무선 공유기)도 이 층에서 동작한다.
- 이 층의 데이터 단위는 대개 "프레임"이라 부르며, 같은 네트워크 안에서 바로 옆 기기에게 안전하게 전달하는 역할에 집중한다.
- IP 주소를 실제 하드웨어 주소(MAC)로 바꿔, 이웃 기기를 정확히 찾아가도록 돕는 ARP 같은 규칙도 여기에서 쓰인다.
- 인터넷 계층
- 서로 다른 네트워크 사이를 가로질러 목적지까지 "길 찾기"를 담당한다. 여기서의 주인공은 IP이며, 라우터라는 길 안내 장비가 이 층에서 패킷을 다음 홉으로 보낸다.
- 우리가 말하는 공인/사설 IP, 서브넷, 게이트웨이 같은 개념들이 이 층과 깊게 얽혀 있다.
- 이 층의 데이터 단위는 "패킷"이며, 전송 중 문제가 생기면 ICMP로 신호(예: 목적지 도달 불가)를 보낸다.
- 전송 계층
- 한 컴퓨터 안의 여러 프로그램들(프로세스) 사이에서 데이터를 나눠 전달한다. 포트는 건물의 호수처럼, 같은 IP를 쓰는 여러 프로그램을 구분하는 문 번호다(예: 80, 443, 53 등).
- 신뢰성을 보장하고 순서를 맞춰주는 TCP와, 가볍고 빠르게 보내는 데 집중하는 UDP가 이 층의 양대 산맥이다.
- TCP는 연결을 맺고(핸드셰이크) 흐름/혼잡 제어로 안정성을 챙기며, UDP는 연결을 맺지 않고 바로 보내 속도와 단순함을 취한다.
- 여기서의 데이터 단위는 TCP의 "세그먼트", UDP의 "데이터그램"으로 부른다.
- 응용 계층
- 우리가 일상에서 접하는 대부분의 프로토콜이 여기에 모여 있다. 웹의 HTTP/HTTPS, 주소를 이름으로 바꿔주는 DNS, 메일의 SMTP/IMAP, 원격 접속의 SSH 등.
- 브라우저, 게임 클라이언트, 메신저 앱 같은 프로그램이 바로 이 층에서 동작하며, 아래층들의 도움을 받아 데이터를 보내고 받는다.
- 사용자가 느끼는 기능과 경험은 대부분 이 층에서 만들어지지만, 그 아래의 층들이 있어야만 문제가 없이 목적지에 도달할 수 있다.
헤더로 알아보는 TCP의 동작 원리
컴퓨터는 웹사이트를 불러올 때 무슨 일을 할까?
본격적으로 들어가기 전에 "헤더"부터 짚고 넘어가자. 헤더란 실제 내용물(데이터) 앞에 붙는 주소·라벨·지시문의 묶음이다. 누구에게 보내는지(포트), 지금 몇 번째 조각인지(시퀀스 번호), 어디까지 받았는지(확인 응답), 얼마나 더 받아줄 수 있는지(윈도) 같은 관리 정보를 담아, 네트워크가 데이터를 올바르게 배달하도록 돕는다. 편지의 겉봉투와 우체국 전산 기록을 합쳐 놓은 것에 가깝다.
TCP는 "신뢰성"과 "순서 보장"을 위해 태어난 프로토콜이다. 그 약속을 지켜내는 근거가 바로 헤더에 담긴 정보들이다. 각 필드는 데이터가 어떤 순서로 왔는지, 무엇이 빠졌는지, 얼마나 더 받아도 되는지 등을 세심하게 기록한다.
classDiagram class TCP_Header { +Source Port: 16 bits +Destination Port: 16 bits +Sequence Number: 32 bits +Acknowledgment Number: 32 bits +Data Offset: 4 bits +Reserved: 3 bits +Flags: 9 bits +Window Size: 16 bits +Checksum: 16 bits +Urgent Pointer: 16 bits +Options: 0-40 bytes +Padding: variable }
각 필드들은 다음과 같은 역할을 한다:
- Source/Destination Port: 같은 IP 주소를 쓰는 여러 프로그램을 구분하는 번호(한 집의 여러 호실 구분). 예: 웹 80/443, DNS 53.
- Sequence Number: 보낸 바이트에 붙이는 일련번호(조각 번호). 연결마다 임의의 시작값(ISN)에서 출발하며, 랩어라운드가 일어나도 구간 비교로 식별한다.
- Acknowledgment Number: "다음에 기대하는 바이트 번호"(여기까지 잘 받았다는 표지).
- Flags: SYN/ACK/FIN/RST/PSH/URG(+ ECE/CWR). 연결 수립/유지/종료와 예외 상황을 간단한 비트로 표현한다(신호등처럼 빠르게 의사소통).
- Window Size: 수신 측이 "지금 받아줄 수 있는 양(rwnd)"을 광고한다(상대가 삼킬 수 있는 만큼만 보냄). 실제 전송 가능한 양은 송신자의 혼잡 윈도(cwnd)와의 최소값으로 결정된다.
- Checksum: 전송 중 비트가 뒤틀렸는지를 검출한다(봉투 훼손 감지). IP의 출발지/목적지 등을 더한 의사 헤더까지 포함하여 계산한다.
- Options: MSS(세그먼트 최대 크기 협상), Window Scale(큰 창 확장), SACK(빠진 조각만 선택 재전송) 등.
조금만 더 깊이 들어가 보자.
- MSS: TCP가 한 세그먼트에 실어 보내는 순수 데이터의 최대 크기. 연결 시작(SYN) 때 서로 광고·협상하며, 경로의 MTU에 맞춰 단편화를 피하도록 크기를 정한다.
- Sequence/Ack는 숫자를 크게 키워 바이트 단위로 세어 총합이 매우 커져도(랩어라운드) 문제없이 식별한다.
- SACK이 없던 시절에는 누락이 하나라도 있으면 넓은 구간을 통째로 다시 보내 비효율적이었다. SACK은 빠진 조각만 콕 집어 재전송하게 해준다.
- ECE/CWR은 네트워크가 혼잡하다는 신호를 송수신자가 공유해, 스스로 속도를 늦춰 붕괴를 피하는 데 도움을 준다.
흐름을 아주 간단히 그려보면 이렇다. 먼저 SYN, SYN+ACK, ACK의 세 걸음으로 연결을 맺는다(3-way handshake: 통신 의사 전송 $\to$ 응답 + 준비 의사 $\to$ 응답). 그 뒤 송신자는 윈도 크기 내에서 데이터를 보내고, 수신자는 Ack로 다음에 기대하는 바이트 번호를 알려준다. Ack가 오지 않으면 타임아웃으로 재전송하고, 손실이 감지되면 전송 속도를 낮춰 네트워크의 숨통을 틔운다.
sequenceDiagram participant Client as 클라이언트 participant Server as 서버 Note over Client,Server: TCP 연결 수립(3-way handshake) Client->>Server: SYN (ISN 포함) Server-->>Client: SYN+ACK (서버 ISN, 클라이언트 ISN 확인) Client->>Server: ACK (서버 ISN 확인) Note over Client,Server: 양측 송수신 큐 초기화, MSS/윈도 등 옵션 확정
sequenceDiagram participant Client as 클라이언트 participant Server as 서버 Note over Client,Server: TCP 정상 종료(4-way handshake) Client->>Server: FIN Server-->>Client: ACK (반쪽 종료) Note over Server: 남은 데이터 전송/처리 Server->>Client: FIN Client-->>Server: ACK (TIME_WAIT 진입) Note over Client: TIME_WAIT 대기 후 소켓 완전 종료
TCP의 검증 및 혼잡 제어 알고리즘
조금 더 깊이 들어가 보자
- 오류 검출과 무결성 확인
- 수신 측은 Checksum으로 헤더·데이터 손상을 검사한다. 오류가 있으면 세그먼트를 폐기하고, 연속성이 끊긴 위치 이전까지만 Ack를 올린다.
- 송신 측은 Ack의 정체, 중복 Ack 증가 등을 손실의 간접 신호로 해석한다.
- RTT/RTO 추정과 재전송 타이머
- RTT는 "왕복 지연 시간"이고, RTO는 "얼마나 기다렸다가 재전송할지"를 정하는 타이머다. 핵심은 너무 성급하지도, 너무 느리지도 않게 균형을 잡는 것이다. 이를 위해 RTT 표본을 모아 지연 평균(SRTT)과 변동폭(RTTVAR)을 추정한다. 전형적인 갱신식은 다음과 같다:
$$ SRTT \leftarrow (1-\alpha),SRTT + \alpha,RTT_{sample} $$
$$ RTTVAR \leftarrow (1-\beta),RTTVAR + \beta,|SRTT - RTT_{sample}| $$
$$ RTO \leftarrow SRTT + 4,RTTVAR $$
여기서 $\alpha\approx 1/8$, $\beta\approx 1/4$을 자주 쓴다. 타임아웃이 발생하면 지수 백오프(예: 2배)로 RTO를 일시적으로 늘린다.
칸의 알고리즘(Karn’s Algorithm): 재전송된 세그먼트의 RTT 표본은 어느 전송에 대한 것인지 모호하므로 SRTT 갱신에 사용하지 않는다. 타임아웃 이후에는 지수 백오프만 적용한다(한 번 늦으면 잠시 더 여유를 둠).
- 손실 검출과 빠른 회복
- 빠른 재전송(Fast Retransmit): 동일한 Ack가 3회 이상 연속 도달하면 중간 손실로 판단하고 즉시 재전송한다(타임아웃 대기 생략).
- 빠른 회복(Fast Recovery): 손실 직후 cwnd를 너무 작게 떨어뜨리지 않고, 중복 Ack에 맞춰 임시로 cwnd를 유지/증가시켜 파이프를 비우지 않는다.
- 선택적 확인(SACK/DSACK): 누락 구간을 정확히 특정해 불필요한 재전송을 줄이고, 중복 수신(DSACK)을 감지하여 전송량 조절을 개선한다.
- 혼잡 제어의 핵심 동작
- 슬로우 스타트: 시작 시 cwnd를 1 MSS 부근에서 시작해 Ack마다 cwnd를 빠르게(대략 RTT마다 2배) 키운다. 혼잡 임계치(ssthresh)에 도달하면 혼잡 회피로 전환한다.
- 혼잡 회피(AIMD): Ack마다 cwnd를 조금씩 늘려(대략 1 MSS/RTT) 탐색하고, 손실 신호가 오면 cwnd를 절반으로 줄인다. ssthresh는 손실 직후의 cwnd 절반으로 갱신한다.
- 타임아웃 vs 중복 Ack: 타임아웃은 심각한 혼잡으로 보고 cwnd를 1 MSS 수준으로 대폭 축소한다. 중복 Ack 기반 손실은 부분 혼잡으로 간주해 완화 폭을 더 작게 잡는다.
- 현대적 알고리즘: CUBIC는 시간 기반 3차 함수로 cwnd를 키워 장거리·고대역에서 효율을 높인다. BBR는 대역폭·RTT를 모델링해 병목 용량에 맞게 송신률을 설정한다.
- 흐름 제어(수신 애플리케이션 보호)
- 수신 윈도(rwnd)는 애플리케이션 버퍼 여유를 나타내며, 헤더의 Window로 광고한다. 송신 가능한 양은 항상 min(rwnd, cwnd)로 제한한다.
부가 최적화와 상호작용
- 지연 Ack(Delayed ACK): 수신 측이 Ack를 소폭 늦춰 묶음으로 보내 오버헤드를 줄인다. 과도하면 재전송 타이머와 충돌할 수 있어 균형이 중요하다.
- 네이글(Nagle’s Algorithm): 너무 작은 세그먼트를 묶어 전송한다. 지연 Ack와 함께 쓰일 때 인터랙티브 지연을 유발할 수 있어 필요 시 비활성화한다.
- Ack 클로킹: Ack 도착이 송신 타이밍을 자연스럽게 조절해, 네트워크 파이프를 적절히 채우도록 돕는다.
헤더로 알아보는 UDP의 동작 원리
컴퓨터는 멀티플레이어 게임을 할 때 무슨 일을 할까?
UDP는 반대로 "단순함"과 "낮은 지연"을 선택한다. 연결을 맺지도, 순서를 강제하지도, 재전송을 자동으로 해주지도 않는다. 대신 헤더가 작고 처리 비용이 낮아, 빠르게 보내고 빠르게 받는 데 특화되어 있다. 택배가 아닌 우편엽서처럼, 최대한 가볍게 적어서 바로 우체통에 넣는 방식이라 생각하면 이해가 쉽다.
classDiagram class UDP_Header { +Source Port: 16 bits +Destination Port: 16 bits +Length: 16 bits +Checksum: 16 bits +Data: variable }
핵심은 다음과 같다.
- Port: TCP와 마찬가지로 프로세스를 식별한다.
- Length: 헤더와 데이터를 합친 전체 길이(어디까지가 한 장인지 표시). 수신자가 정확히 잘라 읽을 수 있게 한다(최소 8바이트 헤더 포함).
- Checksum: 손상을 감지한다(전송 중 글자가 번지지 않았는지 확인). IPv4에서는 역사적으로 선택적이었으나, 실제로는 대부분 사용하며 IPv6에서는 필수다. 체크섬은 IP의 출발지/목적지 등을 포함한 "의사 헤더"까지 더해 계산하여 엉뚱한 목적지로의 오류를 더 잘 잡아낸다.
- 신뢰성/순서/흐름 제어: 제공하지 않는다. 필요하다면 응용 계층이 직접 구현하거나, 손실에 민감하지 않은 용도를 택한다(예: 약간의 끊김을 감수해도 되는 음성 통화).
그렇기에 DNS 조회, 음성/영상 스트리밍, 온라인 게임처럼 약간의 손실은 감내하되 지연을 최소화해야 하는 분야에서 널리 쓰인다. 최근의 QUIC도 UDP 위에서 동작하며, 필요한 신뢰성과 암호화를 응용 계층에서 구현해 성능과 유연성을 동시에 노린다.
연결이 없으니 순서도도 단순하다.
flowchart TD u0["전송할 내용 준비"] --> u1["클라이언트: 목적지 주소/포트로 데이터그램 전송"] u1 --> u2["서버: 수신 버퍼에 도착한 순서대로 꺼내 처리"] u2 --> u3["손실/지연 발생 시: 프로토콜 차원의 재전송 없음(응용에서 판단)"]
TCP와 UDP의 공통점과 차이점
두 프로토콜은 모두 IP 위에서 동작하며, 포트로 프로그램을 구분하고, 체크섬으로 손상을 감지한다는 공통점을 가진다. 그러나 철학과 선택지는 뚜렷하게 갈린다.
- 연결 방식: TCP는 연결 지향(핸드셰이크 필요), UDP는 비연결(바로 전송).
- 신뢰성/순서: TCP는 재전송·순서 보장, UDP는 보장하지 않음.
- 흐름/혼잡 제어: TCP는 내장, UDP는 없음(응용이 필요 시 구현).
- 지연/오버헤드: TCP는 상대적으로 큼, UDP는 작고 지연이 낮음.
- 대표 용도: TCP는 웹/메일/파일 전송, UDP는 DNS/스트리밍/게임/QUIC.
요컨대, "한 바이트도 틀리면 곤란한" 전송에는 TCP가, "지금 바로 도착하는 것"이 더 중요한 전송에는 UDP가 어울린다. 실제로 웹사이트 로드, 파일 다운로드와 같이 파일이 망가지면 곤란한 경우 TCP를 사용하며, 비디오 게임처럼 정확도보다 레이턴시가 중요한 경우 UDP를 자주 사용한다.
인터넷은 거대한 약속의 층위 위에서 굴러간다. 네트워크 접근 계층이 신호를 나르고, 인터넷 계층이 길을 찾으며, 전송 계층이 신뢰성과 속도의 균형을 맞춘다. 그 위에서 응용 계층은 우리가 체감하는 모든 서비스를 빚어낸다. 이 글은 그 가운데서도 TCP와 UDP가 어떤 철학으로 움직이는지, 헤더의 필드가 어떤 일을 맡고 있는지, 그리고 연결 수립·종료와 검증·혼잡 제어가 실제로 어떻게 굴러가는지를 하나씩 짚었다.
핵심은 선택이다. 손실이 치명적이고 순서가 중요하면 TCP를, 지연이 더 중요하고 약간의 손실을 감내할 수 있으면 UDP를 택한다. 또한 같은 TCP라도 혼잡 제어 알고리즘과 옵션(MSS, SACK, Window Scale 등)의 조합에 따라 체감 성능은 크게 달라진다. 설계자는 요구 사항과 환경(RTT, 대역폭, 손실률, NAT/방화벽 제약)을 면밀히 살피고, 그에 맞는 전송 방식을 고르고 조율해야 한다.
네트워킹을 깊게 들어가면 이 외에도 개발자들의 흥미로운 아이디어들이 넘쳐난다. 이 글이 그런 아이디어들의 재치를 조금이나마 전달했기를 바랄 따름이다.