icon

안동민 개발노트

6장 : TCP

TCP의 핵심 특성


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

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

이 모든 것을 담당하는 것이 전송 계층(Transport Layer)이며, 그 핵심 프로토콜이 TCP(Transmission Control Protocol)입니다.

전송 계층의 위치
┌─────────────────────────────────┐
│   Application Layer             │ HTTP, FTP, SSH, DNS
│   (어떤 서비스를 사용할지)      │
├─────────────────────────────────┤
│   Transport Layer ← 지금 여기   │ TCP, UDP
│   (어떤 프로세스에 전달할지)    │ 포트 번호로 프로세스 식별
│   (신뢰성, 순서, 흐름 제어)     │
├─────────────────────────────────┤
│   Network Layer                 │ IP
│   (어떤 컴퓨터로 보낼지)        │
├─────────────────────────────────┤
│   Data Link / Physical          │ Ethernet, Wi-Fi
│   (같은 네트워크에서 전달)      │
└─────────────────────────────────┘

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

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

TCP 4대 특성
┌──────────────────────────────────────────────────────────┐
│                    TCP의 4대 보장                        │
├────────────────┬─────────────────────────────────────────┤
│ 연결 지향      │ 데이터 전송 전 3-way handshake로 연결   │
│ (Connection)   │ 양쪽이 준비되었음을 확인한 후 통신 시작 │
├────────────────┼─────────────────────────────────────────┤
│     신뢰성     │ 패킷 손실 → 재전송, 손상 → 폐기+재요청  │
│ (Reliability)  │ ACK/타임아웃 기반으로 전달 보장         │
├────────────────┼─────────────────────────────────────────┤
│ 순서 보장      │ Sequence Number로 원래 순서 재조립      │
│ (Ordering)     │ 늦게 도착한 패킷도 올바른 위치에 배치   │
├────────────────┼─────────────────────────────────────────┤
│ 바이트 스트림  │ 메시지 경계 없이 연속된 바이트로 전달   │
│ (Byte Stream)  │ 경계 구분은 애플리케이션의 책임         │
└────────────────┴─────────────────────────────────────────┘

연결 지향(Connection-Oriented): TCP는 데이터를 주고받기 전에 반드시 연결을 수립합니다. 전화를 걸어 상대방이 받아야 대화를 시작하는 것처럼, TCP도 3-way handshake를 통해 연결을 먼저 설정합니다. 이 연결은 논리적인 것이며, 회선 교환처럼 물리적 경로를 독점하는 것은 아닙니다.

신뢰성(Reliability): TCP는 데이터가 반드시 도착하도록 보장합니다. 패킷이 손실되면 재전송하고, 손상되면 폐기 후 재요청합니다. 수신 측이 확인 응답(ACK)을 보내지 않으면 송신 측이 해당 데이터를 다시 보냅니다.

순서 보장(Ordered Delivery): 패킷 교환 방식에서는 각 패킷이 다른 경로를 통해 도착할 수 있으므로, 순서가 뒤바뀌는 것이 자연스럽습니다. TCP는 각 바이트에 순서 번호(Sequence Number)를 부여하여, 수신 측에서 원래 순서대로 재조립합니다.

바이트 스트림(Byte Stream): TCP는 데이터를 연속된 바이트의 흐름으로 취급합니다. 메시지의 경계를 구분하지 않습니다. 1000바이트를 한 번에 보내든, 100바이트씩 10번 나눠 보내든, 수신 측에서는 단순히 1000바이트의 연속된 스트림을 받습니다. 메시지 경계가 필요하면 애플리케이션이 직접 처리해야 합니다.

바이트 스트림의 특성
송신 측 (send 호출)
  send("Hello")  →  5바이트
  send("World")  →  5바이트
  send("!")       →  1바이트

수신 측 (recv 호출) — 아래 중 어느 것이든 가능
  recv(11) → "HelloWorld!"     ← 한 번에 수신
  recv(6)  → "HelloW"          ← 절반만 수신
  recv(3)  → "Hel"             ← 부분 수신
  recv(8)  → "loWorld!"        ← 나머지 수신

→ TCP는 메시지 경계를 보장하지 않음!
→ HTTP: Content-Length나 Transfer-Encoding으로 경계 처리
→ WebSocket: 프레임 헤더에 길이 포함

TCP 세그먼트 구조

TCP 데이터 전송의 단위를 세그먼트(Segment)라고 합니다. TCP 헤더의 구조를 살펴봅시다.

TCP 헤더 구조 (최소 20바이트)
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
│          Source Port          │       Destination Port        │
├───────────────────────────────┼───────────────────────────────┤
│                    Sequence Number                            │
├───────────────────────────────────────────────────────────────┤
│                 Acknowledgment Number                         │
├───────┬───────┬─┬─┬─┬─┬─┬─┬───────────────────────────────────┤
│ Offset│Reserve│U│A│P│R│S│F│ Window Size                       │
│ (4bit)│ (3bit)│R│C│S│S│Y│I│ (16bit)                           │
│       │       │G│K│H│T│N│N│                                   │
├───────┴───────┴─┴─┴─┴─┴─┴─┼───────────────────────────────────┤
│          Checksum           │       Urgent Pointer            │
├─────────────────────────────┴─────────────────────────────────┤
│                    Options (가변 길이)                        │
├───────────────────────────────────────────────────────────────┤
│                    Data (페이로드)                            │
└───────────────────────────────────────────────────────────────┘
필드크기설명
Source Port16비트출발지 포트 번호
Destination Port16비트목적지 포트 번호
Sequence Number32비트이 세그먼트의 첫 바이트 순서 번호
Acknowledgment Number32비트다음에 받기를 기대하는 바이트 번호
Data Offset4비트TCP 헤더 길이 (4바이트 단위)
Flags6비트URG, ACK, PSH, RST, SYN, FIN
Window Size16비트수신 가능한 바이트 수 (흐름 제어)
Checksum16비트헤더+데이터 무결성 검증
Urgent Pointer16비트긴급 데이터 위치 (URG 플래그 사용 시)
Options가변MSS, Window Scale, Timestamp 등

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

플래그의미사용 시점
SYN연결 개시, 순서 번호 동기화3-way handshake
ACK확인 응답, Ack 번호 유효연결 수립 후 거의 모든 패킷
FIN연결 종료 요청4-way handshake
RST연결 강제 리셋비정상 종료, 거부
PSH버퍼링 없이 즉시 애플리케이션에 전달대화형 통신
URG긴급 데이터 존재Telnet 인터럽트 등 (거의 미사용)

포트 번호의 역할

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

IP 주소가 특정 컴퓨터를 식별한다면, 포트 번호는 그 컴퓨터에서 실행 중인 특정 프로세스를 식별합니다. IP 주소가 건물의 주소라면, 포트 번호는 건물 안의 호실 번호와 같습니다.

포트 번호에 의한 프로세스 식별
서버 (93.184.216.34)
┌─────────────────────────────────────┐
│                                     │
│  :22   ← SSH 데몬                   │
│  :80   ← Nginx (HTTP)               │
│  :443  ← Nginx (HTTPS)              │
│  :3306 ← MySQL                      │
│  :6379 ← Redis                      │
│  :8080 ← Spring Boot API            │
│                                     │
│  같은 IP 주소에 여러 서비스가       │
│  포트 번호로 구분되어 동시 실행     │
└─────────────────────────────────────┘

클라이언트 요청
  93.184.216.34:443 → HTTPS 서비스로 연결
  93.184.216.34:22  → SSH 서비스로 연결

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

구분범위설명예시
Well-known0~1023표준 서비스 예약 (관리자 권한 필요)HTTP(80), HTTPS(443), SSH(22), DNS(53)
Registered1024~49151특정 애플리케이션 등록MySQL(3306), PostgreSQL(5432), Redis(6379)
Dynamic/Ephemeral49152~65535OS가 자동 할당하는 임시 포트클라이언트 출발지 포트

소켓이란 무엇인가

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

소켓은 통신의 끝점(Endpoint)을 식별하는 조합으로, IP 주소 + 포트 번호 + 프로토콜(TCP/UDP)로 구성됩니다.

소켓과 TCP 연결 식별
하나의 TCP 연결 = 두 개의 소켓 쌍

클라이언트 소켓                    서버 소켓
(192.168.1.10, 50000, TCP)  ←→  (93.184.216.34, 443, TCP)

연결 식별자 = 4-tuple
  (src IP, src Port, dst IP, dst Port)
  (192.168.1.10, 50000, 93.184.216.34, 443)

같은 서버 포트에 여러 클라이언트 연결
  Client A: (192.168.1.10, 50000) ←→ (93.184.216.34, 443)
  Client B: (192.168.1.10, 50001) ←→ (93.184.216.34, 443)
  Client C: (10.0.0.5,     49200) ←→ (93.184.216.34, 443)

→ 4-tuple이 모두 다르므로 각각 독립적인 연결
→ 서버의 443 포트 하나로 수만 개의 동시 연결 처리 가능
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)  # 백로그 큐 크기

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

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

        # 4-tuple 확인
        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 미전송누적되면 애플리케이션 버그 가능
SYN_SENTSYN 전송, 응답 대기대량이면 서버 접속 불가 의심
SYN_RECVSYN 수신, SYN-ACK 전송대량이면 SYN Flood 공격 의심

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

목차