Infra Architecture/Network Fundamentals

Network Fundamentals - 06. TCP·UDP·포트

건물까지 온 편지를 몇 호실로 보낼까 — TCP·UDP와 포트

패킷이 인터넷을 건너 서버에 도착했다. IP 주소가 서버를 찾았고, 라우터들이 패킷을 전달했다. 하지만 — 서버 한 대 안에는 웹 서버(nginx)·데이터베이스(PostgreSQL)·SSH 데몬이 동시에 돌고 있다. 도착한 패킷이 "이건 웹 요청이야, 웹 서버로 줘"인지, "이건 DB 쿼리야, PostgreSQL로 줘"인지를 — 어떻게 구분할까? 답은 포트 번호(port number) 다. 그리고 패킷이 목적지에 안전하게 도착했는지를 보장하느냐(UDP) 아니냐(TCP)에 따라 — 전송 계층(L4)이 두 가지 프로토콜로 나뉜다.

이 글은 TCP(Transmission Control Protocol)UDP(User Datagram Protocol), 그리고 포트 번호를 다룬다. 이전 글에서 본 IP 주소(L3)가 "건물 주소"를 담당했다면, 포트 번호(L4)는 "건물 안의 호실 번호"를 담당한다. TCP의 3-way 핸드셰이크·신뢰성 보장 메커니즘·상태 머신과, UDP의 단순함이 어떻게 서로 다른 용도로 보완 관계를 이루는지를 본다.

포트 번호 — 같은 컴퓨터 안의 어느 앱으로?

포트 번호(port number) 는 16비트(0 ~ 65535) 숫자로, 같은 컴퓨터 안에서 실행 중인 여러 네트워크 앱(서비스)을 구분하는 식별자다. IP 주소가 건물 주소라면, 포트 번호는 그 건물의 호실 번호다.

포트 번호는 세 구간으로 나뉜다:

범위 이름 설명
0 ~ 1023 잘 알려진 포트(well-known) 표준 서비스. root 권한 필요
1024 ~ 49151 등록된 포트(registered) 애플리케이션별 할당
49152 ~ 65535 동적/사설 포트(dynamic) 클라이언트가 임시 사용

자주 보는 포트 번호들:

  • 22 — SSH(원격 접속)
  • 25 — SMTP(이메일 송신)
  • 53 — DNS(이름 해석)
  • 80 — HTTP(웹)
  • 443 — HTTPS(암호화된 웹)
  • 5432 — PostgreSQL
  • 3306 — MySQL
  • 6379 — Redis
  • 8080 — 대체 HTTP(개발 환경)

5-tuple — 하나의 연결을 식별하는 5개 요소

네트워크에서 "하나의 연결(connection)"을 고유하게 식별하는 5개 요소를 5-tuple이라 부른다.

  1. 출발지 IP 주소
  2. 목적지 IP 주소
  3. 프로토콜 번호 (TCP=6, UDP=17)
  4. 출발지 포트 번호
  5. 목적지 포트 번호

이 다섯 개가 같으면 같은 연결, 하나라도 다르면 다른 연결이다. 라우터·방화벽·로드 밸런서가 5-tuple로 트래픽을 식별하고 분류한다. foundations 01에서 본 "5-tuple로 플로우를 식별한다"는 말이 바로 이것이다.

예를 들어 — 192.0.2.10:54321 → 192.0.2.20:443 (TCP)192.0.2.10:54322 → 192.0.2.20:443 (TCP)는 — 출발지 포트가 다르므로(54321 vs 54322) 다른 연결이다. 같은 서버(192.0.2.20:443)에 두 개의 브라우저 탭이 접속하면 각각 다른 출발지 포트를 쓴다.

TCP — 신뢰성을 보장하는 연결형 프로토콜

TCP(Transmission Control Protocol, RFC 793) 는 — 데이터가 순서대로, 누락 없이 도착하는 것을 보장하는 전송 프로토콜이다. "연결형(connection-oriented)" 프로토콜이라 부른다 — 데이터를 보내기 전에 먼저 연결을 설정하고, 보낸 뒤에 연결을 해제한다.

비유하자면 — 전화 통화다. 상대방이 전화를 받고(연결 설정), 대화를 나누고(데이터 전송), 전화를 끊는다(연결 해제). "잘 안 들려, 다시 말해줘"(재전송)를 주고받으며, 말한 순서대로 이해한다(순서 보장).

3-way 핸드셰이크 — 연결 설정

TCP 연결은 3-way 핸드셰이크로 시작된다. 세 단계의 메시지 교환이다.

sequenceDiagram
    participant C as 클라이언트
    participant S as 서버

    C->>S: SYN (seq=100)
    Note over S: SYN_RCVD
    S->>C: SYN-ACK (seq=300, ack=101)
    Note over C: ESTABLISHED
    C->>S: ACK (ack=301)
    Note over S: ESTABLISHED
  1. SYN(Synchronize) — 클라이언트가 "연결하고 싶다"고 요청. 초기 순서 번호(seq=100)를 보낸다.
  2. SYN-ACK — 서버가 "좋아, 연결하자"고 응답. 서버의 초기 순서 번호(seq=300)와 클라이언트의 다음 번호에 대한 확인(ack=101)을 보낸다.
  3. ACK — 클라이언트가 "확인했어"라고 응답(ack=301).

3단계가 끝나면 양쪽 모두 ESTABLISHED 상태가 되고, 데이터 전송이 시작된다. 왜 2단계가 아니라 3단계인가 — 양쪽이 각자의 순서 번호를 교환하고 확인해야 하기 때문이다. 2단계면 한쪽의 확인이 빠진다.

신뢰성 보장 — 어떻게 누락을 막나

TCP가 신뢰성을 보장하는 메커니즘:

  • 순서 번호(sequence number) — 각 바이트에 순서 번호를 부여. 패킷이 순서대로 도착하지 않아도 — 순서 번호로 재조립한다.
  • 확인 응답(ACK, Acknowledgment) — 받은 쪽이 "번호 1000까지 잘 받았어"라고 응답. 보낸 쪽은 ACK가 안 오면 — 재전송한다.
  • 재전송(retransmission) — 일정 시간(재전송 타임아웃, RTO) 내에 ACK가 안 오면 — 패킷을 다시 보낸다. 패킷 손실을 자동으로 복구한다.
  • 흐름 제어(flow control) — 받는 쪽이 처리 못 할 만큼 빨리 보내지 않도록 조절. 슬라이딩 윈도우(sliding window) 로 — "이만큼까지는 보내도 돼"라고 알려준다.
  • 혼잡 제어(congestion control) — 네트워크가 혼잡하면 보내는 속도를 줄인다. 다음 글(혼잡 제어)에서 깊이 다룬다.

TCP 상태 머신 — 연결의 일생

TCP 연결은 상태(state)를 갖는다. 연결 설정부터 해제까지 상태가 변하고 — 이 상태 다이어그램이 TCP의 핵심이다. RFC 793이 정의하며, 가장 흔히 보는 상태들을 정리한다.

상태 의미 언제
LISTEN 서버가 연결 대기 서버가 bind() + listen()
SYN_SENT 클라이언트가 SYN 보냄, 응답 대기 connect() 호출 후
SYN_RCVD 서버가 SYN 받고 SYN-ACK 보냄 SYN 수신 후
ESTABLISHED 연결 완료, 데이터 전송 중 3-way 핸드셰이크 완료 후
FIN_WAIT_1 클라이언트가 연결 끊기 시작 close() 호출 후
FIN_WAIT_2 서버의 FIN 대기 상대의 ACK 수신 후
CLOSE_WAIT 서버가 클라이언트의 FIN 받음 상대가 연결 끊음
LAST_ACK 서버가 FIN 보내고 ACK 대기 자기도 연결 끊음
TIME_WAIT 클라이언트가 마지막 ACK 보내고 대기 늦게 도착는 패킷 방어
CLOSED 연결 완전 종료 모든 과정 끝

TIME_WAIT — 왜 2*MSL을 기다리나

TIME_WAIT는 TCP에서 가장 오해 많은 상태다. 연결을 먼저 끊는 쪽(보통 클라이언트)이 — 마지막 ACK를 보낸 뒤 2*MSL(Maximum Segment Lifetime, 보통 60 ~ 120초) 동안 기다린다. 왜?

두 가지 이유가 있다. 첫째, 늦게 도착는 패킷 방어. 네트워크에 지연돼서 떠돌던 패킷이 — 연결이 완전히 닫힌 뒤에 도착하면, 새 연결이 그 패킷을 잘못 수신할 수 있다. TIME_WAIT 동안 5-tuple을 "사용 중"으로 유지해서 — 새 연결이 같은 5-tuple을 재사용하지 못하게 한다.

둘째, 마지막 ACK의 신뢰성 보장. 클라이언트가 보낸 마지막 ACK가 손실되면 — 서버가 FIN을 재전송한다. TIME_WAIT 상태에서 그 FIN을 받으면 ACK를 다시 보낸다. TIME_WAIT가 없으면 — 서버의 FIN 재전송을 받을 사람이 없어 서버가 영원히 LAST_ACK에 갇힌다.

문제는 — TIME_WAIT가 많이 쌓이면 포트 고갈이 발생한다. 클라이언트가 매우 빠르게 연결을 열고 닫으면(예: 로드 테스트), 사용 가능한 출발지 포트(49152 ~ 65535, 약 16000개)가 TIME_WAIT로 점유돼서 — 새 연결을 못 한다. 해결책 — tcp_tw_reuse(TIME_WAIT 소켓 재사용), 연결 풀(keep-alive), 더 많은 출발지 IP 사용.

4-way 핸드셰이크 — 연결 해제

TCP 연결을 끊을 때는 4-way 핸드셰이크를 쓴다.

sequenceDiagram
    participant C as 클라이언트
    participant S as 서버

    C->>S: FIN
    Note over S: CLOSE_WAIT
    S->>C: ACK
    Note over C: FIN_WAIT_2
    S->>C: FIN
    Note over C: TIME_WAIT
    C->>S: ACK
    Note over S: CLOSED
    Note over C: TIME_WAIT (2*MSL 후 CLOSED)

FIN → ACK → FIN → ACK, 네 단계다. 양쪽이 각자 FIN을 보내야 — 양방향 연결이 완전히 닫힌다.

UDP — 속도를 택한 단순 프로토콜

UDP(User Datagram Protocol, RFC 768) 는 TCP의 반대다. 연결 설정(3-way 핸드셰이크) 없음. 순서 보장 없음. 재전송 없음. 그냥 — 데이터를 보내고 끝. "도착하면 됐고, 안 도착하면 말고."

비유하자면 — 엽서다. 받는 사람이 받았는지 확인 안 하고, 순서가 섞여도 상관 안 하고, 그냥 보낸다. 빠르고 단순하지만 — 신뢰성은 보장되지 않는다.

UDP가 쓰이는 곳:

  • DNS — 이름 해석 요청 하나당 UDP 패킷 하나. TCP처럼 연결 설정할 필요가 없으니 빠름.
  • DHCP — IP 할당. 클라이언트가 아직 IP가 없으니 TCP 연결을 못 함.
  • 비디오 스트리밍(WebRTC) — 약간의 패킷 손실은 화질 저하로 감수. 재전송으로 지연되는 것보다 나음.
  • 게임 — 실시간성이 중요. 늦게 도착한 패킷은 버림.
  • QUIC(HTTP/3) — UDP 위에서 TCP 같은 신뢰성을 직접 구현한 프로토콜. 다음 글에서 맛보기로.
특성 TCP UDP
연결 설정 3-way 핸드셰이크 없음
신뢰성 보장 (재전송, 순서) 보장 안 함
속도 느림 (오버헤드) 빠름 (오버헤드 없음)
헤더 크기 20바이트 이상 8바이트
전송 단위 세그먼트 (byte stream) 데이터그램 (datagram)
혼잡 제어 있음 없음 (앱이 직접)
전형 사례 HTTP, SSH, SMTP, DB DNS, DHCP, WebRTC, 게임

"TCP가 좋은가 UDP가 좋은가"는 잘못된 질문이다. — 용도가 다르다. 신뢰성이 중요하면 TCP, 속도가 중요하면 UDP. 현대의 추세는 — UDP 위에서 TCP의 신뢰성을 앱 수준에서 직접 구현하는 것(QUIC·HTTP/3이 그 사례). TCP의 커널 수준 혼잡 제어가 모든 상황에 최적이 않기 때문이다.

TCP 소켓 버퍼 — 데이터가 머무르는 임시 공간

TCP 연결에서 데이터는 — 송신 버퍼와 수신 버퍼를 거친다. 송신 측은 — 앱이 send()로 데이터를 보내면 송신 버퍼(send buffer) 에 넣고, TCP가 그걸 패킷으로 잘라서 보낸다. 수신 측은 — 패킷을 받아 수신 버퍼(recv buffer) 에 조립하고, 앱이 recv()로 읽어간다.

버퍼 크기가 중요하다. 버퍼가 너무 작으면 — 처리량이 낮아진다(BDP를 못 채우니까). 버퍼가 너무 크면 — 지연이 커진다(버퍼에 데이터가 쌓이니까). Linux의 기본 TCP 버퍼 크기:

# 송신 버퍼 (최소/기본/최대)
sysctl net.ipv4.tcp_wmem
# 수신 버퍼 (최소/기본/최대)
sysctl net.ipv4.tcp_rmem

고대역폭·고지연 네트워크에서는 — 버퍼 크기를 늘려야 처리량이 올라간다. tcp_wmemtcp_rmem의 최대값을 BDP에 맞게 조정하는 튜닝이 — foundations 02에서 본 지연·처리량 품질 속성에 직결된다.

실습 — TCP/UDP 상태 직접 확인

1. 열린 포트와 연결 상태

# 모든 TCP 연결
ss -tan
# 요약 (상태별)
ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn
# 특정 포트만 (예: 80)
ss -tan '( sport = :80 )'

확인할 것: State 열. LISTEN은 대기 중, ESTAB은 연결 중, TIME-WAIT은 종료 후 대기. TIME-WAIT가 수만 개면 포트 고갈 위험.

# 예상 출력 (ss -tan 요약)
   1250 ESTAB         # 정상 연결
    340 TIME-WAIT     # 종료 후 대기
     12 LISTEN        # 서비스 대기

2. TIME_WAIT 수 확인

# TIME_WAIT 상태의 소켓 수
ss -tan state TIME-WAIT | wc -l
# 시스템의 TIME_WAIT 재사용 설정 확인
sysctl net.ipv4.tcp_tw_reuse 2>/dev/null

확인할 것: TIME_WAIT가 수천 개 이상이면 — tcp_tw_reuse=1로 설정해서 소켓을 재사용하게 한다. 또는 앱에서 keep-alive(연결 유지)를 쓴다.

3. 연결 추적 (tcpdump)

# TCP 3-way 핸드셰이크 관찰
sudo tcpdump -i any -n 'tcp[tcpflags] & tcp-syn != 0' -c 10

확인할 것: SYN과 SYN-ACK 패킷의 IP와 포트. Flags [S]가 SYN, Flags [S.]가 SYN-ACK.

# 예상 출력
10:00:01 IP 192.0.2.10.54321 > 192.0.2.20.80: Flags [S], seq 100
10:00:01 IP 192.0.2.20.80 > 192.0.2.10.54321: Flags [S.], seq 300, ack 101

4. UDP 통신 확인

# DNS(UDP 53번) 질의로 UDP 패킷 생성
dig @8.8.8.8 example.com +short
# UDP 소켓 확인
ss -uan | head -10

확인할 것: ss -uan에서 UDP 소켓은 State가 비어있다(연결형이 아니니까). DNS 질의는 UDP 하나로 끝난다.

미검증: ss는 모든 Linux에서 사용 가능. tcpdump는 root 권한 필요. 클라우드 VM에서는 일부 포트가 보이지 않을 수 있음(하이퍼바이저가 필터링).

TCP와 UDP는 "신뢰성 vs 속도"의 트레이드오프다

TCP를 이해하면 — "왜 TIME_WAIT가 쌓여서 포트 고갈이 나는가?", "왜 TCP 연결 설정에 지연이 생기는가?", "왜 패킷 손실 시 응답이 느려지는가?"에 답할 수 있다. UDP를 이해하면 — "왜 DNS가 빠른가?", "왜 비디오 스트리밍이 UDP를 쓰는가?", "왜 QUIC(HTTP/3)가 UDP 위에서 만들어졌는가?"에 답할 수 있다.

다음 글에서는 TCP의 혼잡 제어(congestion control) 를 깊이 다룬다 — "네트워크가 혼잡하면 TCP가 어떻게 속도를 줄이나?", "CUBIC과 BBR의 차이는?", "왜 패킷 손실이 성능에 이렇게 큰 영향을 미치나?"를 보는 것이다.

소켓 버퍼 튜닝은 — 고성능 네트워크 앱(Nginx·HAProxy·DB)에서 성능을 끌어올리는 핵심이다. 기본값이 BDP를 못 채우면 — 대역폭이 낭비된다. tcp_wmem의 최대값을 BDP에 맞게 늘리는 것만으로 — WAN 환경에서 처리량이 크게 향상될 수 있다.

foundations 02에서 본 "측정 가능해야 개선할 수 있다"는 원칙이 — TCP 버퍼 튜닝에서도 적용된다. 먼저 ss -ti로 현재 RTT와 cwnd를 측정하고, BDP를 계산한 뒤 — 버퍼 크기를 조정하는 것이 정답이다. 추측이 아니라 측정으로.


참고

  • RFC 793, Postel, J. "Transmission Control Protocol", 1981, Internet Standard (RFC 9293이 최신판) — TCP 표준. 접근 2026-07-21
  • RFC 768, Postel, J. "User Datagram Protocol", 1980, Internet Standard — UDP 표준. 접근 2026-07-21
  • RFC 9293, Eddy, W. "Transmission Control Protocol (TCP)", 2022 — TCP 최신판. 접근 2026-07-21
  • Kurose, J. F., Ross, K. W. 8th ed, 2021, Ch.3 — TCP/UDP. 접근 2026-07-21
  • Stevens, W. R. <TCP/IP Illustrated, Volume 1> 2nd ed, 2011, Ch.12 ~ Ch.17 — TCP 상세. 접근 2026-07-21
  • Linux man pages, "ss(8)", "tcpdump(8)", "ip(8)" — 공식 매뉴얼. 접근 2026-07-21