본문으로 건너뛰기

안동민 개발노트

본문 시작

TCP의 핵심 특성

TCP의 연결·신뢰성·순서 보장·바이트 스트림과 세그먼트·포트·소켓 구조를 연결해 이해합니다.

지금까지 물리적 신호 전송부터 IP 기반 라우팅까지, 패킷이 네트워크를 횡단하여 목적지 컴퓨터에 도달하는 과정을 살펴보았습니다.

하지만 패킷이 컴퓨터에 도착하는 것만으로는 충분하지 않습니다.

하나의 컴퓨터에서 웹 브라우저, 이메일 클라이언트, 메신저가 동시에 실행되고 있다면, 도착한 패킷을 어떤 프로그램에 전달해야 하는지 결정해야 합니다.

또한 중간에 패킷이 손실되거나 순서가 뒤바뀌는 상황에도 대처해야 합니다.

이 역할을 담당하는 계층이 전송 계층(Transport Layer)이며, 대표적인 프로토콜이 TCP(Transmission Control Protocol)입니다.

TCP는 IP 위에서 동작하면서 애플리케이션에게 연결 지향의 신뢰성 있는 바이트 스트림을 제공합니다.

TCP는 애플리케이션의 바이트를 순서 있는 스트림으로 전달한다

애플리케이션 write 경계와 TCP segment 경계는 같지 않다. TCP가 보장하는 것은 바이트 순서다.

  1. 1
    write A

    bytes 0-499 앱이 한 번 씀

  2. 2
    segment 1

    0-299 TCP가 나눔

  3. 3
    segment 2

    300-499 나머지 전송

  4. 4
    read

    0-499 수신 앱은 연속 바이트로 읽음


연결 지향, 신뢰성, 순서 보장, 바이트 스트림

TCP를 정의하는 네 가지 핵심 특성이 있습니다.

TCP 핵심 특성은 연결, 신뢰, 순서, 바이트 스트림이다

TCP는 연결을 맺고, 손실을 재전송하고, 순서를 복구하지만 메시지 경계는 만들어 주지 않는다.

  1. 1
    연결

    3-way handshake 양쪽 상태 생성

  2. 2
    신뢰

    ACK + retransmit 손실 복구

  3. 3
    순서

    sequence number 재정렬

  4. 4
    흐름

    rwnd 수신 버퍼 보호

  5. 5
    혼잡

    cwnd 네트워크 보호

연결 지향(Connection-Oriented): TCP는 데이터를 주고받기 전에 연결 상태를 만듭니다.

3-way handshake를 통해 양쪽의 초기 순서 번호와 옵션을 맞추고, 이후 세그먼트는 이 연결 상태를 기준으로 해석됩니다.

이 연결은 논리적인 것이며, 회선 교환처럼 물리적 경로를 독점하는 것은 아닙니다.

신뢰성(Reliability): TCP는 ACK, 재전송, 체크섬, 순서 번호를 이용해 애플리케이션에게 손실과 중복을 숨긴 순서 있는 바이트 스트림을 제공하려고 합니다.

다만 네트워크나 상대 호스트가 실패하면 데이터가 반드시 도착하는 것은 아니며, 결국 연결 오류로 드러날 수 있습니다.

순서 보장(Ordered Delivery): IP 패킷은 손실되거나, 중복되거나, 순서가 바뀌어 도착할 수 있습니다.

TCP는 각 바이트에 순서 번호(Sequence Number)를 부여하고, 수신 측이 애플리케이션에 넘기기 전에 원래 순서로 재조립합니다.

바이트 스트림(Byte Stream): TCP는 데이터를 연속된 바이트의 흐름으로 취급합니다.

메시지의 경계를 구분하지 않습니다.

1000바이트를 한 번에 보내든, 100바이트씩 10번 나눠 보내든, 수신 측에서는 단순히 1000바이트의 연속된 스트림을 받습니다.

메시지 경계가 필요하면 애플리케이션이 직접 처리해야 합니다.

TCP write와 segment 송신 경계가 수신 read 경계를 보장하지 않는다

한 번 write한 데이터가 여러 segment로 나뉘거나, 여러 write가 한 번 read로 합쳐질 수 있다.

  1. App write

    hello + world 앱 호출 경계

  2. TCP send

    seg A / seg B MSS, Nagle, buffer 영향

  3. Receive buffer

    연속 byte queue 순서 복원

  4. App read

    원하는 크기만 읽음 메시지 경계는 앱 책임


TCP 세그먼트 구조

TCP 데이터 전송의 단위를 세그먼트(Segment)라고 합니다.

TCP 헤더의 구조를 살펴봅시다.

TCP 헤더는 최소 20바이트이며 옵션으로 확장된다

고정 헤더 뒤에 option이 붙고, Data Offset 값이 실제 header 길이를 알려 준다.

  1. src port

    16b 송신 프로세스

  2. dst port

    16b 수신 프로세스

  3. seq

    32b 첫 바이트 번호

  4. ack

    32b 다음 기대 바이트

  5. flags

    9b SYN/ACK/FIN/RST

  6. window

    16b 수신 가능량

  7. checksum

    16b 오류 검출

  8. options

    0~40B MSS, SACK, timestamp

필드크기설명
Source Port16비트출발지 포트 번호
Destination Port16비트목적지 포트 번호
Sequence Number32비트이 세그먼트의 첫 데이터 바이트 순서 번호
Acknowledgment Number32비트ACK 플래그가 설정됐을 때 다음에 받기를 기대하는 바이트 번호
Data Offset4비트TCP 헤더 길이 (4바이트 단위)
Control Bits8비트CWR, ECE, URG, ACK, PSH, RST, SYN, FIN
Window Size16비트수신 측이 더 받을 수 있는 바이트 수 (흐름 제어)
Checksum16비트pseudo header, TCP 헤더, 데이터에 대한 오류 검출
Urgent Pointer16비트URG 플래그 사용 시 긴급 데이터 위치
Options가변MSS, Window Scale, Timestamp, SACK Permitted 등

핵심 플래그들의 의미를 정리합니다.

플래그의미사용 시점
SYN연결 개시, 순서 번호 동기화3-way handshake
ACK확인 응답, ACK 번호 유효연결 수립 후 대부분의 세그먼트
FIN더 보낼 데이터가 없음을 알리는 정상 종료연결 종료
RST연결 강제 리셋비정상 종료, 거부, 상태 불일치
PSH수신 TCP가 데이터를 지체 없이 넘기도록 요청대화형 통신에서 보일 수 있음
URG긴급 포인터 유효과거 Telnet 등, 현대에는 드묾
ECE/CWRECN 기반 혼잡 신호ECN을 사용하는 연결

포트 번호의 역할

전송 계층이 어떤 프로그램에 데이터를 전달할지 결정하는 데 사용하는 것이 포트 번호(Port Number)입니다.

IP 주소가 특정 호스트의 네트워크 인터페이스를 찾는 데 쓰인다면, 포트 번호는 그 호스트 안에서 어떤 애플리케이션 서비스 또는 소켓으로 전달할지를 정하는 데 쓰입니다.

IP 주소가 건물의 주소라면, 포트 번호는 건물 안의 호실 번호와 비슷합니다.

다만 목적지 포트 하나만으로 항상 수신 대상이 결정되는 것은 아닙니다.

운영체제는 먼저 TCP/UDP 같은 프로토콜을 나누고, 로컬 IP와 로컬 포트로 후보 소켓을 찾습니다.

TCP의 이미 수립된 연결은 여기에 원격 IP와 원격 포트까지 더한 4-tuple로 식별되며, 새 연결 요청은 LISTEN 소켓에서 accept()를 거쳐 별도의 연결 소켓이 됩니다.

포트 번호와 소켓 선택은 프로토콜, 로컬 주소, 연결 상태를 함께 본다

커널은 단순히 port 하나만 보는 것이 아니라 protocol과 local/remote 주소 묶음으로 socket을 찾는다.

  1. Protocol

    TCP/UDP 소켓 테이블 분리

  2. Local IP:port

    받는 주소 listen 후보

  3. Remote tuple

    연결된 상대 established socket

  4. State

    LISTEN/ESTABLISHED 패킷 처리 경로

포트 번호는 16비트 정수로 0~65535 범위를 가지며, 세 그룹으로 나뉩니다.

구분범위설명예시
System / Well-known0~1023표준 서비스용으로 관리되는 낮은 포트HTTP(80), HTTPS(443), SSH(22), DNS(53)
User / Registered1024~49151애플리케이션 서비스 등록에 쓰이는 범위MySQL(3306), PostgreSQL(5432), Redis(6379)
Dynamic / Private49152~65535임시 포트로 쓰도록 예약된 범위클라이언트 출발지 포트

많은 Unix 계열 시스템에서는 1024 미만 포트에 바인드하려면 관리자 권한이 필요하지만, 이것은 운영체제 정책입니다.

또한 실제 임시 포트 범위는 OS 설정에 따라 IANA의 Dynamic/Private 범위와 다를 수 있습니다.


소켓이란 무엇인가

네트워크 프로그래밍에서 자주 등장하는 소켓(Socket)의 정확한 정의를 짚어 두겠습니다.

소켓은 운영체제가 애플리케이션에 제공하는 통신 끝점입니다.

로컬 끝점은 보통 프로토콜 + 로컬 IP + 로컬 포트로 표현하고, 하나의 TCP 연결은 여기에 원격 IP + 원격 포트까지 더한 4-tuple로 식별합니다.

TCP 연결은 양 끝점의 4-tuple로 식별된다

TCP connection key는 source IP/port와 destination IP/port 네 값이다. 상태는 key가 아니라 그 key에 매달린 값이다.

  1. src IP

    10.0.0.5 client host

  2. src port

    53124 client ephemeral

  3. dst IP

    203.0.113.10 server host

  4. dst port

    443 service port

tcp_socket_example.py
import socket

# === TCP 서버 소켓 ===
def start_server():
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(("0.0.0.0", 8080))
    server.listen(5)  # accept 대기 큐 크기 힌트

    print(f"서버 소켓: {server.getsockname()}")
    # 출력: 서버 소켓: ('0.0.0.0', 8080)

    while True:
        client, addr = server.accept()
        print(f"클라이언트 연결: {addr}")
        # 출력: 클라이언트 연결: ('192.168.1.10', 50234)

        # TCP 연결을 식별하는 양 끝점 확인
        print(f"  로컬:  {client.getsockname()}")  # ('10.0.0.1', 8080)
        print(f"  원격:  {client.getpeername()}")   # ('192.168.1.10', 50234)

        data = client.recv(1024)
        client.sendall(b"Hello from server")
        client.close()

# === TCP 클라이언트 소켓 ===
def connect_to_server():
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect(("10.0.0.1", 8080))

    print(f"로컬 소켓:  {client.getsockname()}")
    # 출력: ('192.168.1.10', 50234) ← OS가 임시 포트 자동 할당
    print(f"원격 소켓:  {client.getpeername()}")
    # 출력: ('10.0.0.1', 8080)

    client.sendall(b"Hello from client")
    response = client.recv(1024)
    print(f"응답: {response.decode()}")
    client.close()

실무에서 포트 관련 명령

port_check.sh
#!/bin/bash
# === 포트 사용 현황 확인 ===

echo "=== 열려 있는 TCP 포트 목록 ==="
# Linux
ss -tlnp
# 또는
netstat -tlnp

echo ""
echo "=== 특정 포트 사용 프로세스 확인 ==="
# 8080 포트를 사용하는 프로세스
ss -tlnp | grep :8080
# lsof -i :8080

echo ""
echo "=== TCP 연결 상태별 개수 ==="
ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn
# 출력 예시:
#  1523 ESTAB         ← 활성 연결
#   342 TIME-WAIT     ← 종료 대기
#    15 CLOSE-WAIT    ← 상대방의 FIN 수신 후 대기
#     5 LISTEN        ← 연결 대기 중인 서버 소켓

echo ""
echo "=== ESTABLISHED 연결 상세 ==="
ss -tnp state established

echo ""
echo "=== Windows 포트 확인 ==="
# netstat -ano | findstr :8080
# tasklist /FI "PID eq 12345"
상태의미해석
LISTEN연결 대기 중 (서버)정상
ESTABLISHED활성 연결정상
TIME_WAIT연결 종료 후 지연 세그먼트 대기정상 상태, 급증하면 연결 패턴 확인
CLOSE_WAIT상대방 FIN 수신, 내 FIN 미전송지속 누적이면 close 누락 의심
SYN_SENTSYN 전송, 응답 대기대량이면 서버 도달성 문제 의심
SYN_RECVSYN 수신, SYN-ACK 전송대량이면 backlog 압박이나 SYN Flood 의심

TCP 상태를 확인할 때는 연결 대기인 LISTEN과 통신 중인 ESTABLISHED를 먼저 구분합니다.

TCP 연결 상태는 LISTEN과 ESTABLISHED를 구분해서 본다

서버가 연결을 받을 준비인지, 이미 맺어진 연결인지에 따라 볼 패킷과 원인이 달라진다.

  1. LISTEN

    SYN 대기 서버 port 열림

  2. SYN-RECEIVED

    SYN-ACK 보냄 ACK 대기

  3. ESTABLISHED

    양방향 데이터 정상 연결

  4. FIN/RST

    종료 또는 중단 정리 경로

다음 절에서는 TCP 연결이 실제로 어떻게 수립되고 종료되는지, 3-way handshake4-way handshake를 살펴보겠습니다.