[2026] TCP 연결 상태 완벽 가이드 | ESTABLISHED·TIME_WAIT·CLOSE_WAIT 총정리

[2026] TCP 연결 상태 완벽 가이드 | ESTABLISHED·TIME_WAIT·CLOSE_WAIT 총정리

이 글의 핵심

TCP 11가지 연결 상태(LISTEN, SYN_SENT, ESTABLISHED, FIN_WAIT, TIME_WAIT 등)의 동작 원리와 상태 전이 다이어그램. netstat으로 네트워크 디버깅하는 실전 가이드.

들어가며: TCP 상태를 이해해야 하는 이유

네트워크 애플리케이션을 개발하거나 디버깅할 때 TCP 연결 상태를 이해하는 것은 필수입니다. netstat 명령어로 본 ESTABLISHED, TIME_WAIT, CLOSE_WAIT 같은 상태들이 무엇을 의미하는지, 언제 발생하는지 알아야 성능 문제와 버그를 해결할 수 있습니다. 이 글에서 다룰 내용:

  • TCP 11가지 연결 상태
  • 3-Way Handshake와 4-Way Handshake
  • 상태 전이 다이어그램
  • netstat/ss로 상태 확인
  • 실전 디버깅 시나리오

실무에서 마주한 현실

개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.

목차

  1. TCP 연결 상태 개요
  2. 연결 수립: 3-Way Handshake
  3. 연결 종료: 4-Way Handshake
  4. 11가지 TCP 상태 상세 설명
  5. 상태 전이 다이어그램
  6. netstat/ss로 상태 확인
  7. 실전 디버깅 시나리오
  8. 성능 튜닝

1. TCP 연결 상태 개요

TCP 상태 머신

TCP는 상태 기반 프로토콜입니다. 각 소켓은 11가지 상태 중 하나에 있으며, 패킷 송수신에 따라 상태가 전이됩니다.

11가지 TCP 상태

상태설명발생 시점
CLOSED연결 없음초기 상태
LISTEN연결 대기 중서버가 포트를 열고 대기
SYN_SENT연결 요청 전송클라이언트가 SYN 전송 후
SYN_RECEIVED연결 요청 수신서버가 SYN 받고 SYN-ACK 전송
ESTABLISHED연결 수립 완료3-Way Handshake 완료
FIN_WAIT_1종료 요청 전송능동적 종료 시작 (FIN 전송)
FIN_WAIT_2종료 요청 승인 대기FIN에 대한 ACK 수신
CLOSE_WAIT종료 요청 수신상대방이 FIN 전송 (수동적 종료)
CLOSING동시 종료양쪽이 동시에 FIN 전송
LAST_ACK최종 ACK 대기CLOSE_WAIT에서 FIN 전송 후
TIME_WAIT연결 종료 대기능동적 종료 완료, 2MSL 대기

2. 연결 수립: 3-Way Handshake

3-Way Handshake 과정

다음은 mermaid를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

sequenceDiagram
    participant Client
    participant Server
    
    Note over Client: CLOSED
    Note over Server: LISTEN
    
    Client->>Server: 1. SYN (seq=100)
    Note over Client: SYN_SENT
    
    Server->>Client: 2. SYN-ACK (seq=200, ack=101)
    Note over Server: SYN_RECEIVED
    
    Client->>Server: 3. ACK (ack=201)
    Note over Client: ESTABLISHED
    Note over Server: ESTABLISHED
    
    Note over Client,Server: 데이터 전송 가능

상태 전이

클라이언트: CLOSED → SYN_SENT → ESTABLISHED
서버:       CLOSED → LISTEN → SYN_RECEIVED → ESTABLISHED

Python으로 3-Way Handshake 관찰

다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import socket
import time
def client_connect():
    """클라이언트 연결 과정"""
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    print("📍 상태: CLOSED")
    
    # connect() 호출 시 SYN 전송
    print("📤 SYN 전송 중...")
    print("📍 상태: SYN_SENT")
    
    sock.connect(('example.com', 80))
    
    print("✅ 연결 수립")
    print("📍 상태: ESTABLISHED")
    
    return sock
def server_listen():
    """서버 리스닝 과정"""
    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))
    
    print("📍 상태: CLOSED")
    
    server.listen(5)
    print("👂 포트 8080에서 대기 중...")
    print("📍 상태: LISTEN")
    
    client, addr = server.accept()
    print(f"✅ {addr}로부터 연결 수립")
    print("📍 상태: ESTABLISHED")
    
    return client

패킷 레벨 분석

# tcpdump로 3-Way Handshake 캡처
sudo tcpdump -i any -nn 'tcp port 80' -c 3
# 출력:
# 1. 192.168.1.100.54321 > 93.184.216.34.80: Flags [S], seq 100
# 2. 93.184.216.34.80 > 192.168.1.100.54321: Flags [S.], seq 200, ack 101
# 3. 192.168.1.100.54321 > 93.184.216.34.80: Flags [.], ack 201

3. 연결 종료: 4-Way Handshake

정상 종료 (4-Way Handshake)

다음은 mermaid를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

sequenceDiagram
    participant Client as Client\n(능동적 종료)
    participant Server as Server\n(수동적 종료)
    
    Note over Client,Server: ESTABLISHED
    
    Client->>Server: 1. FIN (seq=300)
    Note over Client: FIN_WAIT_1
    
    Server->>Client: 2. ACK (ack=301)
    Note over Client: FIN_WAIT_2
    Note over Server: CLOSE_WAIT
    
    Note over Server: 애플리케이션이\nclose() 호출할 때까지 대기
    
    Server->>Client: 3. FIN (seq=400)
    Note over Server: LAST_ACK
    
    Client->>Server: 4. ACK (ack=401)
    Note over Client: TIME_WAIT
    Note over Server: CLOSED
    
    Note over Client: 2MSL (60초) 대기
    Note over Client: CLOSED

능동적 종료 vs 수동적 종료

다음은 python를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 능동적 종료 (Active Close)
# - close()를 먼저 호출하는 쪽
# - FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED
client_socket.close()  # 클라이언트가 먼저 종료
# 상태: ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED
# 수동적 종료 (Passive Close)
# - 상대방의 FIN을 받는 쪽
# - CLOSE_WAIT → LAST_ACK → CLOSED
# 서버는 클라이언트의 FIN을 받음
# 상태: ESTABLISHED → CLOSE_WAIT
# 애플리케이션이 close() 호출
server_socket.close()
# 상태: CLOSE_WAIT → LAST_ACK → CLOSED

4. 11가지 TCP 상태 상세 설명

1. CLOSED

초기 상태. 연결이 존재하지 않음.

2. LISTEN

서버가 특정 포트에서 연결 요청을 기다리는 상태.

아래 코드는 python를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8080))
server.listen(5)  # 백로그 큐 크기 5
# 이 시점에서 상태: LISTEN
print("Server is LISTEN on port 8080")
# 확인
netstat -an | grep 8080
# tcp4  0  0  *.8080  *.*  LISTEN

3. SYN_SENT

클라이언트가 SYN 패킷을 전송하고 SYN-ACK를 기다리는 상태.

다음은 python를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(False)  # 논블로킹 모드
try:
    sock.connect(('example.com', 80))
except BlockingIOError:
    # 이 시점에서 상태: SYN_SENT
    print("Connection in progress (SYN_SENT)")
    
    # select로 연결 완료 대기
    import select
    _, writable, _ = select.select([], [sock], [], 5)
    
    if writable:
        print("Connection ESTABLISHED")

SYN_SENT가 오래 유지되는 경우:

  • 방화벽이 SYN 패킷 차단
  • 서버가 다운되어 응답 없음
  • 네트워크 지연

4. SYN_RECEIVED

서버가 SYN을 받고 SYN-ACK를 전송한 후, 최종 ACK를 기다리는 상태.

SYN Flood 공격: 아래 코드는 mermaid를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

flowchart LR
    Attacker[공격자] -->|대량 SYN| Server[서버]
    Server -->|SYN-ACK| Void[응답 없음]
    
    Note1["서버의 SYN_RECEIVED\n큐가 가득 참"]
    
    style Attacker fill:#ff6b6b
    style Void fill:#ff6b6b

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# SYN_RECEIVED 상태 확인
netstat -an | grep SYN_RECV
# SYN Flood 방어 (Linux)
sudo sysctl -w net.ipv4.tcp_syncookies=1
sudo sysctl -w net.ipv4.tcp_max_syn_backlog=8192

5. ESTABLISHED

연결이 수립되어 데이터를 주고받을 수 있는 상태.

아래 코드는 python를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# ESTABLISHED 상태에서 데이터 송수신
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('example.com', 80))
# 상태: ESTABLISHED
sock.send(b'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n')
response = sock.recv(4096)
print(response.decode())

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# ESTABLISHED 연결 수 확인
netstat -an | grep ESTABLISHED | wc -l
# 특정 포트의 ESTABLISHED 연결
netstat -an | grep ':80.*ESTABLISHED'

6. FIN_WAIT_1

능동적 종료를 시작하여 FIN을 전송한 상태. ACK를 기다림.

아래 코드는 python를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 클라이언트가 먼저 종료
client_socket.close()  # FIN 전송
# 상태: ESTABLISHED → FIN_WAIT_1
# 서버의 ACK 대기

7. FIN_WAIT_2

FIN에 대한 ACK를 받았지만, 상대방의 FIN을 기다리는 상태.

아래 코드는 mermaid를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

sequenceDiagram
    participant C as Client
    participant S as Server
    
    Note over C,S: ESTABLISHED
    
    C->>S: FIN
    Note over C: FIN_WAIT_1
    
    S->>C: ACK
    Note over C: FIN_WAIT_2
    Note over S: CLOSE_WAIT
    
    Note over S: 서버가 close() 호출할 때까지\nFIN_WAIT_2 유지

FIN_WAIT_2 타임아웃:

# Linux에서 FIN_WAIT_2 타임아웃 설정 (기본 60초)
sudo sysctl -w net.ipv4.tcp_fin_timeout=30

8. CLOSE_WAIT

상대방이 FIN을 보내 연결 종료를 요청한 상태.
애플리케이션이 close()를 호출해야 함.

CLOSE_WAIT 문제 (소켓 리소스 누수): 아래 코드는 python를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# ❌ 잘못된 코드 (CLOSE_WAIT 누적)
def handle_request(sock):
    data = sock.recv(1024)
    process(data)
    # sock.close()를 호출하지 않음!
    # 클라이언트가 연결을 닫으면 CLOSE_WAIT 상태로 남음
# ✅ 올바른 코드
def handle_request(sock):
    try:
        data = sock.recv(1024)
        process(data)
    finally:
        sock.close()  # 반드시 닫기

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# CLOSE_WAIT 상태 확인
netstat -an | grep CLOSE_WAIT
# CLOSE_WAIT가 많다면 애플리케이션 버그!
# 프로세스별 소켓 수 확인
lsof -p <PID> | grep TCP | wc -l

9. CLOSING

양쪽이 동시에 FIN을 전송한 경우 (드물게 발생).

다음은 mermaid를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

sequenceDiagram
    participant C as Client
    participant S as Server
    
    Note over C,S: ESTABLISHED
    
    C->>S: FIN
    Note over C: FIN_WAIT_1
    
    S->>C: FIN (동시 전송)
    Note over S: FIN_WAIT_1
    
    Note over C: CLOSING
    Note over S: CLOSING
    
    C->>S: ACK
    S->>C: ACK
    
    Note over C: TIME_WAIT
    Note over S: TIME_WAIT

10. LAST_ACK

수동적 종료에서 FIN을 전송하고 최종 ACK를 기다리는 상태.

다음은 간단한 python 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 서버가 수동적으로 종료
# 1. 클라이언트가 FIN 전송 → 서버는 CLOSE_WAIT
# 2. 서버가 close() 호출 → FIN 전송, LAST_ACK 상태
# 3. 클라이언트의 ACK 수신 → CLOSED

11. TIME_WAIT

연결 종료 후 2MSL(Maximum Segment Lifetime) 동안 대기.
지연된 패킷 처리 및 포트 재사용 방지.

TIME_WAIT의 목적:

  1. 지연된 패킷 처리: 네트워크에 남아있는 패킷이 새 연결에 영향을 주지 않도록
  2. 안정적인 종료: 마지막 ACK가 손실되면 상대방이 FIN을 재전송할 수 있도록 아래 코드는 python를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
import socket
import time
# 클라이언트가 연결을 닫으면
sock.close()
# 상태: TIME_WAIT (약 60초)
# 이 시간 동안 같은 (src_ip, src_port, dst_ip, dst_port) 튜플 재사용 불가
# 60초 후
# 상태: CLOSED

TIME_WAIT 문제 (포트 고갈): 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# TIME_WAIT 소켓 수 확인
netstat -an | grep TIME_WAIT | wc -l
# 많은 경우 (수천 개):
# - 클라이언트가 짧은 연결을 반복적으로 생성
# - 로드 밸런서, 프록시에서 흔함

5. 상태 전이 다이어그램

완전한 TCP 상태 전이

다음은 mermaid를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

stateDiagram-v2
    [*] --> CLOSED
    
    CLOSED --> LISTEN: passive open\n(server)
    CLOSED --> SYN_SENT: active open\n(client)
    
    LISTEN --> SYN_RECEIVED: recv SYN\nsend SYN-ACK
    
    SYN_SENT --> ESTABLISHED: recv SYN-ACK\nsend ACK
    SYN_SENT --> SYN_RECEIVED: recv SYN\nsend SYN-ACK
    
    SYN_RECEIVED --> ESTABLISHED: recv ACK
    
    ESTABLISHED --> FIN_WAIT_1: close()\nsend FIN
    ESTABLISHED --> CLOSE_WAIT: recv FIN\nsend ACK
    
    FIN_WAIT_1 --> FIN_WAIT_2: recv ACK
    FIN_WAIT_1 --> CLOSING: recv FIN\nsend ACK
    
    FIN_WAIT_2 --> TIME_WAIT: recv FIN\nsend ACK
    
    CLOSE_WAIT --> LAST_ACK: close()\nsend FIN
    
    CLOSING --> TIME_WAIT: recv ACK
    
    LAST_ACK --> CLOSED: recv ACK
    
    TIME_WAIT --> CLOSED: 2MSL timeout
    
    CLOSED --> [*]

클라이언트 vs 서버 상태 흐름

다음은 mermaid를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

flowchart TB
    subgraph Client["클라이언트 (능동적 종료)"]
        C1[CLOSED] --> C2[SYN_SENT]
        C2 --> C3[ESTABLISHED]
        C3 --> C4[FIN_WAIT_1]
        C4 --> C5[FIN_WAIT_2]
        C5 --> C6[TIME_WAIT]
        C6 --> C7[CLOSED]
    end
    
    subgraph Server["서버 (수동적 종료)"]
        S1[CLOSED] --> S2[LISTEN]
        S2 --> S3[SYN_RECEIVED]
        S3 --> S4[ESTABLISHED]
        S4 --> S5[CLOSE_WAIT]
        S5 --> S6[LAST_ACK]
        S6 --> S7[CLOSED]
    end

6. netstat/ss로 상태 확인

netstat 명령어

기본 사용법

아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 모든 TCP 연결 확인
netstat -an | grep tcp
# 특정 상태만 필터링
netstat -an | grep ESTABLISHED
netstat -an | grep TIME_WAIT
netstat -an | grep CLOSE_WAIT
# 프로세스 정보 포함 (Linux)
sudo netstat -anp | grep :80
# 통계 정보
netstat -s | grep -i tcp

상태별 개수 확인

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# Linux/Mac
netstat -an | awk '/tcp/ {print $6}' | sort | uniq -c
# 출력 예시:
#   150 ESTABLISHED
#    50 TIME_WAIT
#     5 CLOSE_WAIT
#     3 LISTEN

ss 명령어 (더 빠름)

아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 모든 TCP 소켓
ss -tan
# ESTABLISHED 상태만
ss -tan state established
# TIME_WAIT 상태만
ss -tan state time-wait
# 프로세스 정보 포함
ss -tanp
# 통계
ss -s

ss 필터 예시

아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 특정 포트
ss -tan '( dport = :80 or sport = :80 )'
# 특정 IP
ss -tan dst 192.168.1.100
# 여러 상태 조합
ss -tan state established state syn-sent
# 수신 큐와 송신 큐 크기 확인
ss -tan | awk '{print $2, $3}'

Windows PowerShell

아래 코드는 powershell를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# TCP 연결 확인
Get-NetTCPConnection
# ESTABLISHED 상태만
Get-NetTCPConnection -State Established
# 특정 포트
Get-NetTCPConnection -LocalPort 80
# 상태별 개수
Get-NetTCPConnection | Group-Object -Property State | Select-Object Name, Count

7. 실전 디버깅 시나리오

시나리오 1: TIME_WAIT 과다 (포트 고갈)

문제 증상

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

$ netstat -an | grep TIME_WAIT | wc -l
5000
# 새 연결 시도 시 에러
# "Cannot assign requested address"

원인

아래 코드는 python를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# ❌ 잘못된 코드: 짧은 연결 반복 생성
for i in range(10000):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(('api.example.com', 80))
    sock.send(b'GET / HTTP/1.1\r\n\r\n')
    sock.recv(1024)
    sock.close()  # TIME_WAIT 상태로 전환
    # 60초 동안 포트 재사용 불가!

해결 방법

다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# ✅ 해결 1: 연결 재사용 (Keep-Alive)
import requests
session = requests.Session()
for i in range(10000):
    response = session.get('http://api.example.com/')
    # 같은 연결 재사용, TIME_WAIT 발생 안 함
# ✅ 해결 2: SO_REUSEADDR 설정
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# ✅ 해결 3: 커널 파라미터 조정 (Linux)
# /etc/sysctl.conf
# net.ipv4.tcp_tw_reuse = 1
# net.ipv4.tcp_fin_timeout = 30

시나리오 2: CLOSE_WAIT 누적 (리소스 누수)

문제 증상

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

$ netstat -an | grep CLOSE_WAIT | wc -l
1000
# 시간이 지나도 줄어들지 않음
# 결국 "Too many open files" 에러

원인

아래 코드는 python를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# ❌ 잘못된 코드: 소켓을 닫지 않음
def handle_client(sock):
    data = sock.recv(1024)
    process(data)
    # sock.close() 누락!
    # 클라이언트가 연결을 닫으면 CLOSE_WAIT 상태로 남음
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8080))
server.listen(5)
while True:
    client, addr = server.accept()
    handle_client(client)  # 소켓 누수!

해결 방법

다음은 python를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# ✅ 해결 1: try-finally로 확실히 닫기
def handle_client(sock):
    try:
        data = sock.recv(1024)
        process(data)
    finally:
        sock.close()  # 반드시 실행
# ✅ 해결 2: Context Manager 사용
def handle_client(sock):
    with sock:
        data = sock.recv(1024)
        process(data)
    # 자동으로 close() 호출
# ✅ 해결 3: 타임아웃 설정
sock.settimeout(30)  # 30초 후 자동 종료

디버깅

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# CLOSE_WAIT 소켓을 가진 프로세스 찾기
lsof -i -n | grep CLOSE_WAIT
# 프로세스별 CLOSE_WAIT 개수
lsof -i -n | grep CLOSE_WAIT | awk '{print $2}' | sort | uniq -c
# 특정 프로세스의 소켓 상태
lsof -p <PID> | grep TCP

시나리오 3: SYN_SENT 타임아웃

문제 증상

아래 코드는 python를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5)
try:
    sock.connect(('unreachable-server.com', 80))
except socket.timeout:
    print("❌ Connection timeout (SYN_SENT)")

원인 및 해결

아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 1. 방화벽 확인
sudo iptables -L -n | grep 80
# 2. 라우팅 확인
traceroute unreachable-server.com
# 3. 서버 상태 확인
ping unreachable-server.com
# 4. 포트 스캔
nmap -p 80 unreachable-server.com

시나리오 4: 대량 ESTABLISHED 연결

문제 증상

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

$ netstat -an | grep ESTABLISHED | wc -l
10000
# 서버 응답 느려짐
# CPU, 메모리 사용량 증가

원인

아래 코드는 python를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# ❌ Keep-Alive로 연결을 무한정 유지
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
# 유휴 연결이 계속 ESTABLISHED 상태로 남음

해결 방법

다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# ✅ 해결 1: 타임아웃 설정
sock.settimeout(60)  # 60초 유휴 시 자동 종료
# ✅ 해결 2: Keep-Alive 파라미터 조정
import socket
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
# Linux
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60)    # 60초 후 첫 probe
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10)   # 10초 간격
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3)      # 3번 시도
# ✅ 해결 3: 연결 풀 크기 제한
from concurrent.futures import ThreadPoolExecutor
max_connections = 1000
executor = ThreadPoolExecutor(max_workers=max_connections)

8. 성능 튜닝

Linux 커널 파라미터

다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# /etc/sysctl.conf
# TIME_WAIT 재사용 허용
net.ipv4.tcp_tw_reuse = 1
# FIN_WAIT_2 타임아웃 (기본 60초)
net.ipv4.tcp_fin_timeout = 30
# SYN 백로그 큐 크기
net.ipv4.tcp_max_syn_backlog = 8192
# 최대 연결 수
net.core.somaxconn = 65535
# TIME_WAIT 소켓 재활용 (주의: NAT 환경에서 문제 가능)
net.ipv4.tcp_tw_recycle = 0  # 비활성화 권장
# Keep-Alive 설정
net.ipv4.tcp_keepalive_time = 60
net.ipv4.tcp_keepalive_intvl = 10
net.ipv4.tcp_keepalive_probes = 3
# 적용
sudo sysctl -p

SO_LINGER 옵션

다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import socket
import struct
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# SO_LINGER 설정
# l_onoff=1, l_linger=0: close() 시 RST 전송 (TIME_WAIT 회피)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
sock.connect(('example.com', 80))
sock.send(b'data')
# close() 호출 시:
# - 정상: FIN 전송 → TIME_WAIT
# - SO_LINGER(0): RST 전송 → 즉시 CLOSED
sock.close()
# ⚠️ 주의: RST는 비정상 종료로 간주됨
# 상대방이 "Connection reset by peer" 에러 발생 가능

연결 풀 패턴

다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import queue
import socket
import threading
class ConnectionPool:
    def __init__(self, host, port, pool_size=10):
        self.host = host
        self.port = port
        self.pool = queue.Queue(maxsize=pool_size)
        
        # 미리 연결 생성
        for _ in range(pool_size):
            sock = self._create_connection()
            self.pool.put(sock)
    
    def _create_connection(self):
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect((self.host, self.port))
        return sock
    
    def get_connection(self, timeout=5):
        """연결 가져오기"""
        try:
            return self.pool.get(timeout=timeout)
        except queue.Empty:
            return self._create_connection()
    
    def return_connection(self, sock):
        """연결 반환"""
        try:
            self.pool.put_nowait(sock)
        except queue.Full:
            sock.close()
    
    def close_all(self):
        """모든 연결 종료"""
        while not self.pool.empty():
            try:
                sock = self.pool.get_nowait()
                sock.close()
            except queue.Empty:
                break
# 사용
pool = ConnectionPool('api.example.com', 80, pool_size=20)
def make_request():
    sock = pool.get_connection()
    try:
        sock.send(b'GET / HTTP/1.1\r\n\r\n')
        response = sock.recv(4096)
        return response
    finally:
        pool.return_connection(sock)  # 재사용
# 여러 스레드에서 사용
for _ in range(1000):
    threading.Thread(target=make_request).start()
# TIME_WAIT 발생 안 함 (연결 재사용)

고급 주제

TCP Half-Open 연결

아래 코드는 python를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# Half-Open: 한쪽은 ESTABLISHED, 다른 쪽은 CLOSED
# 발생 원인: 네트워크 단절, 프로세스 강제 종료
# 감지 방법: Keep-Alive
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
# Keep-Alive probe 전송 → 응답 없으면 연결 종료

TCP Reset (RST)

아래 코드는 python를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# RST 패킷이 전송되는 경우:
# 1. 존재하지 않는 포트로 연결 시도
# 2. SO_LINGER(0)로 close() 호출
# 3. 애플리케이션 강제 종료
# 4. 방화벽이 연결 차단
# RST 수신 시:
# - 즉시 CLOSED 상태로 전환
# - "Connection reset by peer" 에러

TCP Simultaneous Open

다음은 mermaid를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

sequenceDiagram
    participant A as Host A
    participant B as Host B
    
    Note over A: CLOSED
    Note over B: CLOSED
    
    A->>B: SYN
    Note over A: SYN_SENT
    
    B->>A: SYN (동시 전송)
    Note over B: SYN_SENT
    
    Note over A: SYN_RECEIVED
    Note over B: SYN_RECEIVED
    
    A->>B: SYN-ACK
    B->>A: SYN-ACK
    
    Note over A: ESTABLISHED
    Note over B: ESTABLISHED

실전 모니터링 스크립트

Python 모니터링 도구

다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#!/usr/bin/env python3
import subprocess
import time
from collections import Counter
def get_tcp_states():
    """TCP 상태별 개수 조회"""
    try:
        # ss 명령어 사용 (더 빠름)
        result = subprocess.run(
            ['ss', '-tan'],
            capture_output=True,
            text=True
        )
        
        lines = result.stdout.strip().split('\n')[1:]  # 헤더 제외
        states = []
        
        for line in lines:
            parts = line.split()
            if len(parts) > 0:
                state = parts[0]
                states.append(state)
        
        return Counter(states)
    
    except FileNotFoundError:
        # ss가 없으면 netstat 사용
        result = subprocess.run(
            ['netstat', '-an'],
            capture_output=True,
            text=True
        )
        
        states = []
        for line in result.stdout.split('\n'):
            if 'tcp' in line.lower():
                parts = line.split()
                if len(parts) >= 6:
                    states.append(parts[5])
        
        return Counter(states)
def monitor_tcp_states(interval=5):
    """TCP 상태 실시간 모니터링"""
    print("🔍 TCP 연결 상태 모니터링 시작...\n")
    
    try:
        while True:
            states = get_tcp_states()
            
            print(f"\n{'='*50}")
            print(f"⏰ {time.strftime('%Y-%m-%d %H:%M:%S')}")
            print(f"{'='*50}")
            
            for state, count in sorted(states.items()):
                bar = '█' * min(count, 50)
                print(f"{state:15s} {count:5d} {bar}")
            
            total = sum(states.values())
            print(f"{'─'*50}")
            print(f"{'TOTAL':15s} {total:5d}")
            
            # 경고
            if states.get('CLOSE_WAIT', 0) > 100:
                print("\n⚠️  WARNING: Too many CLOSE_WAIT connections!")
                print("   → Check if application is closing sockets properly")
            
            if states.get('TIME_WAIT', 0) > 5000:
                print("\n⚠️  WARNING: Too many TIME_WAIT connections!")
                print("   → Consider using connection pooling")
            
            time.sleep(interval)
    
    except KeyboardInterrupt:
        print("\n\n✅ 모니터링 종료")
if __name__ == '__main__':
    monitor_tcp_states(interval=5)

Bash 원라이너 모음

아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 1. 상태별 개수
netstat -an | awk '/tcp/ {print $6}' | sort | uniq -c | sort -rn
# 2. 특정 포트의 연결 수
netstat -an | grep ':80 ' | wc -l
# 3. IP별 연결 수 (상위 10개)
netstat -an | grep ESTABLISHED | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -10
# 4. TIME_WAIT 비율
echo "scale=2; $(netstat -an | grep TIME_WAIT | wc -l) / $(netstat -an | grep tcp | wc -l) * 100" | bc
# 5. 연결 수 실시간 모니터링
watch -n 1 'netstat -an | grep tcp | awk "{print \$6}" | sort | uniq -c'

프로그래밍 언어별 상태 확인

Python

다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import socket
import psutil
# 현재 프로세스의 연결 상태
connections = psutil.net_connections(kind='tcp')
for conn in connections:
    if conn.status == 'ESTABLISHED':
        print(f"{conn.laddr.ip}:{conn.laddr.port} -> {conn.raddr.ip}:{conn.raddr.port}")
        print(f"  Status: {conn.status}")
        print(f"  PID: {conn.pid}")
# 상태별 개수
from collections import Counter
states = Counter(conn.status for conn in connections)
print(states)

Node.js

다음은 javascript를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const { exec } = require('child_process');
function getTCPStates(callback) {
  exec('netstat -an | grep tcp', (error, stdout) => {
    if (error) {
      callback(error);
      return;
    }
    
    const lines = stdout.trim().split('\n');
    const states = {};
    
    lines.forEach(line => {
      const parts = line.split(/\s+/);
      const state = parts[5];
      states[state] = (states[state] || 0) + 1;
    });
    
    callback(null, states);
  });
}
// 사용
getTCPStates((err, states) => {
  if (!err) {
    console.log('TCP States:', states);
  }
});

Go

다음은 go를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

package main
import (
    "fmt"
    "github.com/shirou/gopsutil/v3/net"
)
func main() {
    // TCP 연결 조회
    connections, err := net.Connections("tcp")
    if err != nil {
        panic(err)
    }
    
    // 상태별 개수
    states := make(map[string]int)
    for _, conn := range connections {
        states[conn.Status]++
    }
    
    // 출력
    for state, count := range states {
        fmt.Printf("%s: %d\n", state, count)
    }
}

실전 팁

1. 로드 밸런서 설정

다음은 nginx를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# Nginx에서 Keep-Alive 설정
upstream backend {
    server backend1.example.com:8080;
    server backend2.example.com:8080;
    
    # Keep-Alive 연결 유지
    keepalive 32;
}
server {
    location / {
        proxy_pass http://backend;
        
        # Keep-Alive 헤더
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        
        # 타임아웃
        proxy_connect_timeout 5s;
        proxy_read_timeout 60s;
    }
}

2. 데이터베이스 연결 풀

다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import psycopg2
from psycopg2 import pool
# 연결 풀 생성
connection_pool = psycopg2.pool.ThreadedConnectionPool(
    minconn=5,      # 최소 연결 수
    maxconn=20,     # 최대 연결 수
    host='localhost',
    database='mydb',
    user='user',
    password='pass'
)
def query_database(sql):
    conn = connection_pool.getconn()
    try:
        cursor = conn.cursor()
        cursor.execute(sql)
        result = cursor.fetchall()
        return result
    finally:
        connection_pool.putconn(conn)  # 연결 반환
# TIME_WAIT 발생 안 함 (연결 재사용)

3. HTTP 클라이언트 최적화

다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
# 세션 재사용 + 재시도 전략
session = requests.Session()
retry_strategy = Retry(
    total=3,
    backoff_factor=1,
    status_forcelist=[429, 500, 502, 503, 504]
)
adapter = HTTPAdapter(
    max_retries=retry_strategy,
    pool_connections=10,  # 연결 풀 크기
    pool_maxsize=20
)
session.mount("http://", adapter)
session.mount("https://", adapter)
# 사용
for i in range(1000):
    response = session.get('http://api.example.com/data')
    # 연결 재사용, TIME_WAIT 최소화

문제 해결 체크리스트

TIME_WAIT 과다

  • HTTP Keep-Alive 사용 확인
  • 연결 풀 구현 확인
  • net.ipv4.tcp_tw_reuse 활성화
  • net.ipv4.tcp_fin_timeout 감소 (30초)
  • 클라이언트 포트 범위 확대
# 클라이언트 포트 범위 확대
sudo sysctl -w net.ipv4.ip_local_port_range="10000 65535"

CLOSE_WAIT 누적

  • 애플리케이션 코드에서 close() 호출 확인
  • try-finally 또는 with 문 사용
  • 타임아웃 설정 확인
  • 예외 처리 확인
  • 프로세스 재시작 (임시 해결) 아래 코드는 python를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# CLOSE_WAIT 디버깅
import traceback
def handle_connection(sock):
    try:
        data = sock.recv(1024)
        process(data)
    except Exception as e:
        print(f"❌ Error: {e}")
        traceback.print_exc()
    finally:
        print("🔒 Closing socket")
        sock.close()  # 반드시 실행

ESTABLISHED 과다

  • 유휴 연결 타임아웃 설정
  • Keep-Alive 파라미터 조정
  • 최대 연결 수 제한
  • 연결 풀 크기 조정
  • 로드 밸런서 설정 확인

정리

TCP 상태 요약

아래 코드는 mermaid를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

flowchart TD
    Start[연결 시작] --> Handshake[3-Way Handshake]
    Handshake --> Est["ESTABLISHED\n데이터 전송"]
    Est --> Close[연결 종료]
    Close --> Active{누가 먼저\n종료?}
    
    Active -->|클라이언트| TimeWait["TIME_WAIT\n60초 대기"]
    Active -->|서버| CloseWait["CLOSE_WAIT\nclose 호출 필요"]
    
    TimeWait --> End[CLOSED]
    CloseWait --> LastAck[LAST_ACK]
    LastAck --> End

핵심 포인트

상태주의사항해결 방법
TIME_WAIT포트 고갈 가능연결 재사용, Keep-Alive
CLOSE_WAIT소켓 누수반드시 close() 호출
SYN_SENT연결 타임아웃타임아웃 설정, 재시도
ESTABLISHED과다 연결연결 풀, 타임아웃

디버깅 명령어 요약

다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 상태 확인
netstat -an | grep tcp
ss -tan
# 상태별 개수
netstat -an | awk '/tcp/ {print $6}' | sort | uniq -c
# 프로세스별 연결
lsof -i -n -P | grep TCP
# 실시간 모니터링
watch -n 1 'ss -s'
# 특정 포트
netstat -an | grep ':80'
ss -tan '( sport = :80 or dport = :80 )'

성능 최적화 요약

다음은 python를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# ✅ 연결 재사용
session = requests.Session()
# ✅ 연결 풀
pool = ConnectionPool(size=20)
# ✅ 타임아웃 설정
sock.settimeout(30)
# ✅ Keep-Alive
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
# ✅ 리소스 정리
try:
    # 작업
    pass
finally:
    sock.close()

참고 자료

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3