이 문서의 과거 버전 (r1)을 보고 있습니다. 수정일: 2026.02.12 06:23
TCP는 연결 지향적이고 신뢰성 있는 전송 계층 프로토콜이다. 두 호스트 간에 데이터를 안정적으로 전송하기 위해, TCP는 연결의 수립, 데이터 전송, 연결 종료라는 세 가지 주요 단계를 거친다. 이 과정에서 통신 양단의 소켓은 정해진 일련의 상태를 순차적으로 변화시키며, 이를 시각적으로 표현한 것이 TCP 상태 변화도이다.
TCP 상태 변화도는 TCP/IP 통신의 핵심 메커니즘을 이해하는 데 필수적인 도구이다. 이 다이어그램은 클라이언트와 서버가 각각 어떤 조건에서 상태를 전이하는지, 그리고 그 과정에서 교환되는 SYN, ACK, FIN 같은 제어 패킷의 흐름을 보여준다. 상태 변화를 통해 통신 중 발생할 수 있는 문제, 예를 들어 연결 실패나 비정상 종료의 원인을 추적하고 진단할 수 있다.
주요 상태에는 연결을 준비하는 LISTEN, 연결 요청을 보낸 후 응답을 기다리는 SYN_SENT, 데이터를 주고받을 수 있는 ESTABLISHED, 그리고 연결 종료 과정의 TIME_WAIT 등이 있다. 각 상태는 시스템이 내부적으로 소켓과 관련 리소스를 어떻게 관리하는지를 반영한다. 따라서 네트워크 프로그래밍이나 성능 튜닝, 장애 해결 시 이 상태 변화에 대한 이해는 매우 중요하다.
TCP 연결 수립은 신뢰성 있는 양방향 통신 채널을 설정하기 위한 과정이며, 일반적으로 3-Way Handshake라고 불린다. 이 과정은 클라이언트와 서버가 서로의 통신 준비 상태와 초기 순서 번호를 교환하여 확인하는 절차이다.
핵심 단계는 세 개의 패킷 교환으로 이루어진다. 먼저, 연결을 요청하는 클라이언트는 SYN 플래그가 설정된 패킷을 서버로 보낸다. 이 패킷에는 클라이언트의 초기 시퀀스 번호가 포함된다. 서버는 이 요청을 받고, SYN과 ACK 플래그가 모두 설정된 패킷으로 응답한다. 이 패킷에는 서버의 초기 시퀀스 번호와 클라이언트의 시퀀스 번호에 1을 더한 확인 응답 번호가 담긴다. 마지막으로 클라이언트는 서버의 응답을 확인하는 ACK 패킷을 보내며, 이로써 연결 수립이 완료된다.
이 패킷 교환에 따라 TCP 상태는 다음과 같이 변화한다. 서버는 특정 포트에서 연결 요청을 대기하는 LISTEN 상태에 있다. 클라이언트가 SYN을 보내면 SYN_SENT 상태로 전환된다. 서버는 SYN을 받고 SYN_RECEIVED 상태가 되며, SYN-ACK를 회신한다. 마지막으로 클라이언트의 ACK를 서버가 수신하면, 양측 모두 ESTABLISHED 상태가 되어 데이터 전송을 시작할 수 있다.
역할 | 전송 패킷 | 상태 변화 (발신 측) | 상태 변화 (수신 측) |
|---|---|---|---|
클라이언트 | SYN | LISTEN → SYN_SENT | - |
서버 | SYN-ACK | SYN_SENT → (대기) | LISTEN → SYN_RECEIVED |
클라이언트 | ACK | SYN_SENT → ESTABLISHED | SYN_RECEIVED → ESTABLISHED |
이 과정은 신뢰성 있는 연결의 기초를 마련한다. 초기 시퀀스 번호의 교환은 이후 전송되는 데이터의 순서와 무결성을 보장하는 데 사용된다. 또한, 양측이 서로의 수신 및 송신 능력을 확인함으로써 전이중 통신 채널이 성공적으로 열렸음을 보장한다.
TCP 연결 수립은 신뢰성 있는 양방향 통신 채널을 생성하기 위한 과정이다. 이 과정은 3-Way Handshake라고 불리며, 총 세 번의 패킷 교환을 통해 이루어진다.
첫 번째 단계에서 연결을 요청하는 클라이언트는 SYN 플래그가 설정된 패킷을 서버로 보낸다. 이 패킷에는 클라이언트의 초기 시퀀스 번호가 포함되어 있으며, 클라이언트의 상태는 SYN_SENT로 변경된다. 서버는 해당 포트에서 LISTEN 상태로 대기 중이어야 한다.
서버가 SYN 패킷을 수신하면 두 번째 단계가 시작된다. 서버는 이 요청을 수락한다는 의미로 SYN과 ACK 플래그가 모두 설정된 패킷으로 응답한다. 이 패킷에는 서버의 자신의 초기 시퀀스 번호와 클라이언트의 시퀀스 번호에 1을 더한 확인 응답 번호가 담긴다. 서버의 상태는 SYN_RECEIVED로 전환된다.
마지막으로 클라이언트는 서버의 응답을 확인하는 ACK 패킷을 보낸다. 이 패킷의 확인 응답 번호는 서버의 시퀀스 번호에 1을 더한 값이다. 이 ACK 패킷의 전송과 함께 양측의 연결은 ESTABLISHED 상태가 되어 실제 데이터 교환을 시작할 수 있게 된다. 이 세 번의 핸드셰이크는 양측이 서로의 송수신 능력을 확인하고 초기 시퀀스 번호를 동기화하는 핵심 메커니즘이다.
TCP 연결 수립 과정에서 클라이언트와 서버는 일련의 내부 상태를 거쳐 ESTABLISHED 상태에 도달한다. 이 과정은 3-Way Handshake라는 패킷 교환 절차와 정확히 대응된다.
서버 애플리케이션이 특정 포트에서 연결을 기다리기 시작하면, 해당 소켓은 LISTEN 상태가 된다. 이 상태는 연결 요청을 수신할 준비가 되었음을 의미한다. 클라이언트가 SYN 패킷을 보내 연결을 시도하면, 서버는 그 패킷을 받고 자신의 상태를 SYN_RECEIVED로 변경한다. 동시에 클라이언트는 SYN 패킷을 보낸 직후 자신의 상태를 SYN_SENT로 변경한다. 이 상태는 클라이언트가 연결 요청을 보내고 서버의 응답을 기다리고 있음을 나타낸다.
서버는 SYN_RECEIVED 상태에서 SYN-ACK 패킷으로 응답한다. 클라이언트가 이 SYN-ACK 패킷을 수신하면, 최종 ACK 패킷을 서버로 보내고 자신의 상태를 ESTABLISHED로 변경한다. 마지막으로 서버가 클라이언트의 ACK 패킷을 수신하면, 서버도 자신의 상태를 SYN_RECEIVED에서 ESTABLISHED로 변경한다. 이로써 양측 모두 데이터 전송이 가능한 연결 확립 상태가 된다.
이 상태 변화의 핵심 순서는 다음과 같이 요약할 수 있다.
역할 | 초기 상태 | 패킷 송수신 후 상태 | 최종 상태 |
|---|---|---|---|
서버 | LISTEN | SYN 수신 → SYN_RECEIVED | ACK 수신 → ESTABLISHED |
클라이언트 | CLOSED | SYN 송신 → SYN_SENT | SYN-ACK 수신 및 ACK 송신 → ESTABLISHED |
이러한 상태 전이는 TCP 프로토콜이 신뢰성 있는 연결을 보장하기 위한 핵심 메커니즘이다. 각 상태는 시스템이 현재 연결 과정의 어느 단계에 있는지를 명확히 정의하여, 예기치 않은 패킷이나 중복 요청을 처리할 수 있게 한다.
연결 종료를 시작하는 측(클라이언트 또는 서버)은 애플리케이션에서 close() 시스템 호출을 실행한다. 이로 인해 TCP는 상대방에게 FIN 플래그가 설정된 세그먼트를 전송하고, 자신의 상태는 ESTABLISHED에서 FIN_WAIT_1로 변경된다.
상대방(서버 또는 클라이언트)은 FIN 세그먼트를 수신하면, 즉시 ACK 세그먼트로 응답한다. 이 ACK는 FIN에 대한 확인응답이다. 이때 수신측의 상태는 ESTABLISHED에서 CLOSE_WAIT로 변경된다. 수신측 애플리케이션도 close()를 호출하면, 자신의 FIN 세그먼트를 발신측에게 보낸다. 이때 수신측의 상태는 CLOSE_WAIT에서 LAST_ACK로 변경된다.
발신측은 상대방의 FIN 세그먼트를 받으면 ACK로 응답하고 상태를 TIME_WAIT로 변경한다. TIME_WAIT 상태는 일반적으로 2*MSL(Maximum Segment Lifetime, 최대 세그먼트 수명) 동안 유지된다[1]. 이 시간은 지연된 패킷이 네트워크에서 소멸되도록 보장하고, 연결 종료의 마지막 ACK가 손실된 경우 상대방의 FIN 재전송을 수신할 수 있게 한다. TIME_WAIT 대기 시간이 지나면 상태는 CLOSED가 되어 모든 자원이 해제된다.
단계 | 발신측 → 수신측 패킷 | 발신측 상태 | 수신측 상태 |
|---|---|---|---|
1단계 | FIN | ESTABLISHED → FIN_WAIT_1 | ESTABLISHED |
2단계 | ACK | FIN_WAIT_1 → FIN_WAIT_2 | ESTABLISHED → CLOSE_WAIT |
3단계 | FIN | FIN_WAIT_2 | CLOSE_WAIT → LAST_ACK |
4단계 | ACK | FIN_WAIT_2 → TIME_WAIT → CLOSED | LAST_ACK → CLOSED |
TCP 연결 종료는 양방향 연결을 각각 독립적으로 끊기 위해 일반적으로 네 번의 패킷 교환 과정을 거친다. 이 과정을 4-Way Handshake라고 부른다.
먼저 연결 종료를 시작하는 측(보통 클라이언트)이 FIN 플래그가 설정된 패킷을 상대방(서버)에게 보낸다. 이 패킷을 보낸 후, 클라이언트는 FIN_WAIT_1 상태로 진입한다. 서버는 이 FIN 패킷을 받으면 CLOSE_WAIT 상태가 되고, 데이터 전송이 완료되었다는 확인 응답으로 ACK 패킷을 클라이언트에게 회신한다. 이 ACK를 받은 클라이언트는 FIN_WAIT_2 상태로 이동한다.
서버는 자신의 모든 데이터 전송을 마친 후, 연결 종료를 완료하기 위해 자신의 FIN 패킷을 클라이언트에게 보낸다. 이때 서버는 LAST_ACK 상태가 된다. 클라이언트는 서버의 FIN 패킷을 받으면 TIME_WAIT 상태로 전환하고, 최종 확인 응답으로 ACK 패킷을 서버에게 보낸다. 서버는 이 ACK를 수신하면 연결을 완전히 닫고 CLOSED 상태가 된다. 클라이언트는 TIME_WAIT 상태에서 일정 시간(보통 2*MSL[2]) 대기한 후 CLOSED 상태로 최종 종료된다.
연결 종료를 시작하는 측(일반적으로 클라이언트)이 FIN 플래그가 설정된 세그먼트를 전송하면, 해당 측의 상태는 ESTABLISHED에서 FIN_WAIT_1으로 변화한다. 이 상태는 상대방으로부터 FIN에 대한 ACK 응답을 기다리는 상태이다.
상대방(일반적으로 서버)이 FIN에 대한 ACK를 보내면, 연결 종료를 시작한 측의 상태는 FIN_WAIT_2로 전환된다. 이 상태는 상대방이 보낼 추가 FIN 세그먼트를 기다리는 상태이다. 이후 상대방이 자신의 연결 종료를 위해 FIN 세그먼트를 전송하면, 이를 수신한 측은 즉시 ACK로 응답하고 상태를 TIME_WAIT로 변경한다.
TIME_WAIT 상태는 매우 중요한 종료 단계이다. 이 상태에서는 소켓 쌍(로컬 IP/포트, 원격 IP/포트)이 일정 시간(보통 2*MSL[3]) 동안 대기한다. 이 대기 시간은 지연된 패킷이 같은 연결의 새로운 인스턴스에 도착하여 데이터 무결성을 해치는 것을 방지하기 위해 필요하다. 또한 이 시간은 연결 종료의 마지막 ACK가 유실되었을 경우, 상대방이 FIN을 재전송할 수 있도록 보장하는 역할도 한다.
TIME_WAIT 대기 시간이 완료되면, 연결은 최종적으로 CLOSED 상태가 되어 모든 리소스가 해제된다. 한편, 연결 종료를 수동적으로 받는 측(서버)의 상태 변화는 ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED의 경로를 따르게 된다.
LISTEN 상태는 서버 소켓이 연결 요청을 기다리는 상태이다. 이 상태는 일반적으로 bind와 listen 시스템 호출을 수행한 후에 도달한다. 서버는 이 상태에서 클라이언트로부터의 SYN 패킷을 수신할 준비를 한다.
SYN_SENT 상태는 클라이언트가 connect 시스템 호출을 실행하여 서버로 SYN 패킷을 보낸 후, 해당 SYN에 대한 응답(SYN-ACK)을 기다리는 상태이다. ESTABLISHED 상태는 3-way 핸드셰이크가 완료되어 데이터 송수신이 가능한 연결이 수립된 상태를 의미한다. 이 상태에서 양측은 ACK 패킷을 통해 데이터의 정상 수신을 확인한다.
연결 종료 과정에서 나타나는 CLOSE_WAIT 상태는 로컬 애플리케이션이 close를 호출하기 전, 상대방으로부터 FIN 패킷을 먼저 받았을 때 발생한다. 이 상태는 로컬에서 남은 데이터를 전송하고 연결 종료 절차를 시작할 준비를 하는 기간이다. LAST_ACK 상태는 로컬 애플리케이션이 FIN 패킷을 보낸 후, 이에 대한 최종 ACK를 기다리는 상태이다.
TIME_WAIT 상태는 연결을 먼저 종료한 측(일반적으로 클라이언트)이 최종 ACK를 보낸 후 도달한다. 이 상태는 지연 도착할 수 있는 패킷이 네트워크에서 소멸되도록 보장하고, 원활한 연결 종료를 확인하기 위해 유지된다. 표준 TIME_WAIT 타임아웃은 2*MSL(Maximum Segment Lifetime)로, 대부분의 시스템에서 60초 정도이다[4].
LISTEN 상태는 서버 소켓이 연결 요청을 기다리는 수동적 개방 상태이다. 이 상태에서는 SYN 패킷을 수신할 준비가 되어 있으며, 일반적으로 bind()와 listen() 시스템 호출을 성공적으로 수행한 후에 진입한다. LISTEN 상태의 소켓은 실제 데이터 통신을 위한 연결이 아니라, 들어오는 연결 요청을 받아들이는 '수신 대기' 역할을 한다.
SYN_SENT 상태는 클라이언트가 SYN 패킷을 보낸 후, 상대방의 SYN-ACK 응답을 기다리는 상태이다. 이는 3-Way Handshake의 첫 번째 단계가 완료된 시점이다. 클라이언트가 connect() 시스템 호출을 실행하면 SYN 패킷이 전송되고 소켓은 이 상태로 전이된다. 만약 상대방으로부터 SYN-ACK 패킷을 제때 받지 못하면 연결 시도는 실패할 수 있다.
ESTABLISHED 상태는 3-Way Handshake가 완료되어 양방향 연결이 성공적으로 수립된 상태이다. 이 상태에서는 데이터의 송수신이 자유롭게 가능하다. 애플리케이션 관점에서 보면, 서버는 accept()를 통해, 클라이언트는 connect() 성공 후 이 상태에 진입한 소켓을 얻어 실제 데이터 교환을 시작한다. 대부분의 통신 시간은 이 상태에서 소비된다.
상태 | 역할 | 일반적 발생 주체 | 전이 조건 |
|---|---|---|---|
LISTEN | 연결 요청 대기 | 서버 |
|
SYN_SENT | 연결 요청 후 응답 대기 | 클라이언트 |
|
ESTABLISHED | 데이터 송수신 가능 | 클라이언트 & 서버 | 3-Way Handshake 완료 후 |
CLOSE_WAIT 상태는 TCP 연결 종료 과정에서 로컬 애플리케이션이 상대방의 FIN 패킷을 수신하고 이에 대한 ACK를 보낸 후, 아직 자신의 애플리케이션에서 연결 종료를 위한 FIN 패킷을 보내지 않은 대기 상태이다. 이 상태는 기본적으로 "로컬 애플리케이션이 연결을 닫을 준비를 기다리는" 단계에 해당한다. 정상적인 흐름에서는 애플리케이션이 close() 시스템 콜을 호출하면 곧바로 LAST_ACK 상태로 전환된다. 그러나 애플리케이션의 결함이나 지연으로 인해 CLOSE_WAIT 상태가 지속되면, 해당 소켓과 관련된 리소스가 해제되지 못해 시스템 자원이 고갈되는 문제가 발생할 수 있다.
LAST_ACK 상태는 로컬 애플리케이션이 연결 종료를 위해 FIN 패킷을 상대방에게 보내고, 그에 대한 최종 ACK를 기다리는 상태이다. 이 상태는 4단계 연결 종료 과정의 마지막 단계 중 하나로, 상대방으로부터 ACK를 수신하면 연결은 CLOSED 상태가 되어 모든 리소스가 해제된다. 만약 ACK를 받지 못하면 FIN 패킷을 재전송하는 메커니즘이 동작한다. LAST_ACK 상태는 일반적으로 매우 짧은 시간 동안만 유지된다.
TIME_WAIT 상태는 연결을 먼저 종료하려는 측(FIN을 먼저 보낸 측)이 경험하는 최종 상태이다. 이 상태는 ESTABLISHED 상태에서의 연결 종료가 완료된 후, 즉 FIN_WAIT_1, FIN_WAIT_2 상태를 거쳐 상대방의 FIN에 대한 ACK를 보내고 난 후에 진입한다. TIME_WAIT 상태가 존재하는 주된 이유는 두 가지이다. 첫째, 지연되어 도착할 수 있는 패킷이 같은 연결의 새로운 인스턴스(같은 IP와 포트 조합)에 의해 잘못 처리되는 것을 방지한다. 둘째, 연결 종료의 신뢰성을 보장하기 위해, 마지막 ACK가 유실되었을 경우 상대방이 FIN을 재전송할 수 있도록 충분한 시간을 제공한다[5]. 이 상태의 지속 시간은 일반적으로 2*MSL(Maximum Segment Lifetime)로, 대부분의 시스템에서 60초 정도이다.
TCP 연결의 생명주기는 명확하게 정의된 일련의 상태를 거치며, 이를 시각적으로 표현한 것이 상태 변화도 다이어그램이다. 이 다이어그램은 클라이언트와 서버가 각각 어떤 순서로 상태를 전이하는지, 그리고 패킷 교환이 어떻게 상태 변화를 유발하는지를 한눈에 보여준다.
표준적인 상태 변화 흐름은 크게 연결 수립과 연결 종료 두 가지 경로로 나뉜다. 연결 수립 시, 수동적으로 열린 소켓은 LISTEN 상태에서 시작하여, SYN 패킷을 받으면 SYN_RECEIVED 상태가 된다. 능동적으로 연결을 시도하는 쪽은 SYN_SENT 상태를 거쳐, 3-way handshake가 완료되면 양측 모두 ESTABLISHED 상태에 도달한다. 연결 종료는 양방향으로 독립적으로 이루어지며, 먼저 FIN 패킷을 보내는 쪽은 FIN_WAIT_1, FIN_WAIT_2, TIME_WAIT 상태를 순차적으로 경유한다. FIN을 받고 응답하는 쪽은 CLOSE_WAIT, LAST_ACK 상태를 거쳐 최종적으로 CLOSED 상태로 돌아간다.
시작 상태 | 전이 조건 (이벤트) | 다음 상태 |
|---|---|---|
SYN 패킷 수신 | ||
애플리케이션의 close() 호출 | FIN_WAIT_1 (능동 종료) | |
상대방의 FIN 패킷 수신 | CLOSE_WAIT (수동 종료) | |
2MSL(Maximum Segment Lifetime) 대기 시간 만료 |
예외 상황 및 특수 상태도 다이어그램에 포함된다. 예를 들어, SYN_SENT 상태에서 RST 패킷을 받으면 바로 CLOSED 상태로 돌아간다. 또한, 동시 종료 상황에서는 양측 모두 FIN_WAIT_1 상태에서 시작하여, 상대방의 FIN을 받으면 CLOSING 상태를 거친 후 TIME_WAIT 상태로 진입한다. 이러한 예외 경로는 네트워크 이상이나 동시적 요청으로 인한 정상적이지 않은 연결 해제를 처리하기 위해 존재한다.
표준 TCP 상태 변화 흐름은 클라이언트와 서버가 각각 어떤 순서로 상태를 전이하는지를 보여준다. 일반적으로 연결 수립과 연결 종료라는 두 개의 주요 흐름으로 나뉜다.
연결 수립 과정에서는 서버가 먼저 LISTEN 상태가 되어 특정 포트에서 연결 요청을 기다린다. 클라이언트가 SYN 패킷을 보내면 SYN_SENT 상태가 되고, 이를 받은 서버는 SYN_RECEIVED 상태가 되며 SYN-ACK 패킷으로 응답한다. 클라이언트가 ACK 패킷을 보내면 양측 모두 ESTABLISHED 상태에 진입하여 데이터 교환이 가능해진다.
연결 종료 과정은 양측에서 독립적으로 시작할 수 있다. 한쪽(예: 클라이언트)이 FIN 패킷을 보내면 FIN_WAIT_1 상태가 된다. 상대방(서버)이 이에 대한 ACK를 보내면 클라이언트는 FIN_WAIT_2 상태로 전이하고, 서버는 CLOSE_WAIT 상태가 된다. 서버도 자신의 연결 종료를 위해 FIN 패킷을 보내면 LAST_ACK 상태가 되고, 이를 받은 클라이언트는 ACK를 보낸 후 TIME_WAIT 상태로 들어간다. TIME_WAIT 상태는 일정 시간(보통 2*MSL[6]) 동안 유지된 후 CLOSED 상태로 최종 종료된다.
역할 | 연결 수립 과정 (상태 변화) | 연결 종료 과정 (상태 변화) |
|---|---|---|
수동적 개방(서버) | CLOSED → LISTEN | ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED |
능동적 개방(클라이언트) | CLOSED → SYN_SENT → ESTABLISHED | ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED |
이 표준 흐름에서 양측이 동시에 FIN 패킷을 보내는 등의 예외 상황이 발생하면 상태 전이는 약간 다르게 진행된다. 또한, TIME_WAIT 상태는 지연 도착하는 패킷이 새로운 연결에 영향을 주지 않도록 하는 중요한 안전 장치 역할을 한다.
예외 상황은 주로 연결 종료 과정에서 발생하며, 양측이 동시에 FIN 패킷을 전송하는 경우가 대표적이다. 이 경우 표준적인 4단계 종료 과정이 단축된다. 양쪽 모두 FIN_WAIT_1 상태에 진입한 후 상대방의 FIN 패킷을 ACK 패킷으로 응답받으면, 각각 CLOSING 상태를 거쳐 TIME_WAIT 상태로 직접 전이할 수 있다.
일부 특수 상태는 정상적인 흐름에서는 짧게 유지되지만, 문제 발생 시 장시간 지속되어 리소스를 점유할 수 있다. CLOSE_WAIT 상태는 로컬 애플리케이션이 close() 시스템 콜을 제때 호출하지 않아 상대방의 종료 요청(FIN 패킷)에 대한 응답만 하고 대기하는 상태이다. 이 상태가 과도하게 누적되면 파일 디스크립터 고갈의 원인이 된다. LAST_ACK 상태는 로컬에서 종료 요청(FIN 패킷)을 보내고 상대방의 최종 확인 응답(ACK 패킷)을 기다리는 최종 단계이다.
네트워크 장애나 시스템 강제 종료와 같은 비정상적인 상황에서는 연결이 중간 상태에 갇히는 경우도 있다. 예를 들어, SYN_RECEIVED 상태에서 과도한 연결 요청이 몰리는 SYN 플러드 공격이 발생하거나, 원격지 호스트가 갑자기 응답을 중지하여 FIN_WAIT_2 상태가 길어지는 현상이 있을 수 있다. 이러한 상태들은 운영체제별 타임아웃 설정에 의해 정리된다.
TCP 연결의 각 상태는 시스템 리소스를 점유하며, 운영체제의 소켓 테이블에서 관리된다. 특히 TIME_WAIT 상태는 연결이 완전히 종료된 후에도 일정 시간 동안 소켓 자원을 유지하는 중요한 상태이다. 이 상태는 지연 도착 패킷이 새로운 연결에 혼란을 주지 않도록 방지하는 역할을 한다[7]. 일반적으로 TIME_WAIT 상태의 지속 시간은 2*MSL로, 대부분의 시스템에서 60초 정도로 설정되어 있다.
상태 | 주요 목적 | 일반적인 지속 시간 | 점유하는 리소스 |
|---|---|---|---|
데이터 전송 | 연결이 유지되는 동안 | 송수신 버퍼, 연결 정보 | |
지연 패킷 정리 | 2*MSL (보통 60초) | 로컬 포트, 연결 정보 | |
애플리케이션의 종료 대기 | 애플리케이션 종료 호출까지 | 로컬 포트, 연결 정보 | |
상대방의 FIN 응답 대기 | 상대방의 상태에 의존 | 로컬 포트, 연결 정보 |
TIME_WAIT 상태가 누적되면 사용 가능한 로컬 포트가 고갈되어 새로운 연결을 수립하지 못하는 문제가 발생할 수 있다. 이는 특히 짧은 시간 내에 많은 연결을 생성하고 종료하는 서버에서 주로 나타난다. 이를 완화하기 위해 SO_REUSEADDR 소켓 옵션을 설정하여 TIME_WAIT 상태의 소켓이 점유한 주소와 포트를 새로운 연결에 즉시 재사용할 수 있도록 한다.
시스템은 각 TCP 상태에 해당하는 소켓을 관리하기 위해 커널 메모리를 할당한다. 상태가 정상적으로 천이되지 않고 CLOSE_WAIT이나 FIN_WAIT_2 같은 상태에 머물러 있으면, 해당 연결은 자원을 계속 점유하게 되어 결국 시스템의 리소스 부족을 초래한다. 따라서 애플리케이션은 연결 종료 시 반드시 정상적인 4-Way Handshake 과정을 시작하도록 close() 시스템 콜을 호출해야 한다.
TIME_WAIT 상태는 TCP 연결이 정상적으로 종료된 후, 한쪽 엔드포인트가 가지는 최종 상태이다. 이 상태는 액티브 클로저(Active Closer), 즉 먼저 FIN 패킷을 보내 연결 종료를 시작한 측(일반적으로 클라이언트)이 도달하게 된다. 연결이 완전히 종료(CLOSED)되기 전, 소켓은 일정 시간 동안 이 상태에 머무른다.
TIME_WAIT 상태의 기본 지속 시간은 일반적으로 2*MSL(Maximum Segment Lifetime)이다. MSL은 IP 패킷이 네트워크 상에서 존재할 수 있는 최대 시간을 의미하며, 대부분의 구현체에서 이 값은 60초로 설정되어 있다. 따라서 TIME_WAIT 상태의 일반적인 타임아웃은 120초(2분)이다. 이 시간 동안 해당 소켓 쌍(로컬 IP/포트, 원격 IP/포트)은 새로운 연결에 즉시 재사용될 수 없다.
TIME_WAIT 상태가 존재하는 주된 이유는 두 가지이다. 첫째, 지연된 패킷의 처리를 보장하기 위함이다. 종료된 연결의 마지막 ACK 패킷이 유실되면, 상대방(패시브 클로저)은 자신의 FIN 패킷을 재전송한다. TIME_WAIT 상태의 호스트는 이 지연되거나 재전송된 패킷을 수신하여 적절한 응답(ACK)을 다시 보낼 수 있어, 연결이 정상적으로 종료될 수 있도록 한다. 둘째, 이전 연결의 패킷이 새 연결에 혼입되는 것을 방지한다. 같은 소켓 쌍이 즉시 재사용될 경우, 네트워크에 남아있던 이전 연결의 지연 패킷이 새 연결의 데이터로 잘못 해석될 수 있는데, TIME_WAIT 상태는 이러한 오류를 방지한다.
그러나 TIME_WAIT 상태의 소켓이 과도하게 누적되면 시스템의 사용 가능한 포트나 메모리 리소스를 고갈시킬 수 있다. 이는 특히 짧은 시간 내에 대량의 연결을 생성하고 종료하는 서버(예: 로드 밸런서, 리버스 프록시)에서 문제가 될 수 있다. 이러한 문제를 완화하기 위해 SO_REUSEADDR 소켓 옵션을 설정하여 TIME_WAIT 상태의 소켓 주소를 새로운 연결에 바인딩할 수 있도록 하거나, TCP 타임스탬프 옵션(RFC 1323)을 사용하여 TIME_WAIT 상태의 지속 시간을 줄이는 방법 등이 사용된다[8].
TCP 연결은 소켓이라는 추상화된 엔드포인트를 통해 관리된다. 운영체제는 각 소켓에 대해 파일 디스크립터를 할당하고, 연결 상태, 송수신 버퍼, 로컬 및 원격 주소 정보, 포트 번호 등 연결에 필요한 메타데이터를 저장한다. 이 메타데이터는 커널 메모리 내 소켓 구조체에 유지되며, 활성 상태인 소켓은 시스템 리소스를 지속적으로 점유한다.
특히 TIME_WAIT 상태는 소켓 자원 관리에서 중요한 의미를 가진다. 연결이 정상적으로 종료된 후, 소켓은 일정 시간(보통 2*MSL[9]) 동안 이 상태로 남아 있다. 이는 지연되거나 중복된 패킷이 같은 연결 식별자(IP 주소와 포트 조합)를 가진 새로운 연결에 도착하는 것을 방지하기 위한 것이다. 그러나 TIME_WAIT 상태의 소켓이 과도하게 누적되면, 사용 가능한 포트 수가 고갈되어 새로운 연결 생성에 실패할 수 있다.
이러한 문제를 완화하기 위해 시스템 튜닝이 이루어진다. 일반적인 방법으로는 TIME_WAIT 상태의 재사용을 가능하게 하는 SO_REUSEADDR 또는 SO_REUSEPORT 소켓 옵션 설정, TIME_WAIT 상태 대기 시간을 줄이는 커널 파라미터 조정 등이 있다. 또한, 애플리케이션 측에서는 연결을 적절히 종료하고, 불필요한 연결을 장시간 유지하지 않도록 설계하여 소켓 자원의 효율적인 관리를 도모해야 한다.
TCP 연결의 상태를 모니터링하고 문제를 진단하는 것은 네트워크 관리와 애플리케이션 디버깅에 필수적이다. 주요 도구로는 netstat과 ss 명령어가 있다. netstat 명령어는 -n 옵션으로 호스트명과 서비스명 대신 숫자 형식의 주소와 포트를 보여주며, -t 옵션으로 TCP 연결만 필터링할 수 있다. 더 현대적이고 빠른 도구인 ss 명령어는 -t 옵션으로 TCP 소켓을, -a 옵션으로 모든 상태의 소켓을 표시한다. ss는 커널 공간에서 직접 정보를 수집하여 netstat보다 효율적이다.
일반적인 문제 상태로는 CLOSE_WAIT 상태의 소켓이 과도하게 누적되는 경우가 있다. 이는 로컬 애플리케이션이 원격 호스트의 FIN 패킷을 수신하고 ACK로 응답한 후, 애플리케이션 자체에서 close() 시스템 콜을 제때 호출하지 않아 발생한다. 이는 주로 애플리케이션의 소켓 종료 로직 결함이나 리소스 누수로 인한 것이다. 반면, TIME_WAIT 상태의 소켓이 많아지는 것은 일반적으로 정상적인 연결 종료의 결과이나, 매우 짧은 시간에 대량의 연결이 생성되고 종료되는 서버에서는 시스템의 사용 가능한 로컬 포트를 고갈시킬 수 있다.
문제 진단을 위해 특정 상태의 소켓 수를 집계할 수 있다. 예를 들어, TIME_WAIT 상태의 연결 수를 확인하는 명령어는 다음과 같다.
```bash
ss -tan state time-wait | wc -l
```
ESTABLISHED 상태가 아닌 연결이 비정상적으로 많거나, LAST_ACK 상태가 지속적으로 유지된다면 네트워크 장애나 상대방 호스트의 문제를 의심해볼 수 있다. 이러한 모니터링을 통해 연결 누수, 응답 불능 서비스, 네트워크 성능 저하 등의 원인을 신속하게 파악하고 조치할 수 있다.
netstat과 ss는 TCP 연결 상태를 모니터링하고 문제를 진단하는 데 널리 사용되는 명령줄 도구이다. 두 명령어 모두 시스템에서 활성화된 네트워크 연결, 수신 대기 중인 포트, 라우팅 테이블, 인터페이스 통계 등을 확인할 수 있게 해준다.
netstat은 역사가 긴 전통적인 도구로, 대부분의 유닉스 계열 운영 체제와 윈도우에서 사용 가능하다. TCP 상태를 확인하기 위한 일반적인 옵션은 netstat -tn 또는 netstat -tunap이다. -t 옵션은 TCP 연결만, -n은 호스트 이름과 서비스 이름 대신 숫자 형식의 주소와 포트를, -u는 UDP를, -a는 모든 연결과 수신 대기 소켓을, -p는 해당 연결을 소유한 프로세스 ID와 이름을 보여준다. 출력 결과에는 로컬 주소, 원격 주소, 현재 상태(예: ESTABLISHED, TIME_WAIT, CLOSE_WAIT) 등이 표시된다.
반면, ss(socket statistics)는 netstat을 대체하기 위해 개발된 더 빠르고 효율적인 현대적 도구이다. 특히 연결 수가 많은 시스템에서 성능이 우수하다. 기본 사용법은 ss -t 또는 ss -tunap으로, netstat과 유사한 옵션을 제공한다. ss는 더 풍부한 필터링 기능을 지원하여 특정 상태(예: ss state time-wait), 특정 포트, 또는 특정 IP 주소를 기준으로 연결을 검색하는 데 유용하다. 두 명령어의 일반적인 출력 비교는 다음과 같다.
명령어 | 주요 옵션 | 특징 |
|---|---|---|
|
| 범용적이고 널리 호환되지만, 대량 연결 처리 시 상대적으로 느림 |
|
| 처리 속도가 빠르고 필터링 기능이 강력하며, 최신 리눅스 커널 정보를 직접 노출 |
문제 진단 시, 예를 들어 CLOSE_WAIT 상태의 연결이 비정상적으로 많이 누적되어 있다면, 이는 일반적으로 로컬 애플리케이션이 상대방의 연결 종료 요청(FIN 패킷)을 수신한 후 자신의 종료 절차를 제대로 수행하지 못하고 있음을 의미한다. netstat -tunap | grep CLOSE_WAIT 또는 ss -t state close-wait 명령어를 실행하여 해당 연결과 관련된 프로세스를 특정할 수 있다. 이는 애플리케이션의 소켓 종료 로직 결함이나 자원 누수를 찾는 첫 단계가 된다.
일반적인 TCP 연결 문제는 특정 상태가 비정상적으로 오래 지속되거나 과도하게 누적될 때 발생한다. 대표적인 문제 상태로는 CLOSE_WAIT와 TIME_WAIT의 과다 누적, 그리고 FIN_WAIT_2 상태의 지연이 있다.
CLOSE_WAIT 상태는 로컬 애플리케이션이 상대방으로부터 FIN 패킷을 받고 ACK로 응답한 후, 애플리케이션 차원에서 자신의 소켓을 닫기 위해 close() 시스템 콜을 호출하기를 기다리는 상태이다. 이 상태가 다수 누적된다는 것은 애플리케이션이 소켓을 제때 닫지 못하고 있다는 명백한 증상이다. 이는 주로 애플리케이션의 로직 결함(예: 소켓 핸들 누수)이나 데드락으로 인해 발생하며, 결과적으로 시스템의 사용 가능한 파일 디스크립터가 고갈되어 새로운 연결을 수립하지 못하게 만든다.
반면, TIME_WAIT 상태는 연결을 먼저 종료(액티브 클로즈)한 측(주로 클라이언트)에서 정상적인 4단계 종료 과정의 마지막 단계에 진입한다. 이 상태는 지연 도착 패킷이 새 연결에 혼란을 주지 않도록 하기 위해 존재하며, 일반적으로 2*MSL(Maximum Segment Lifetime, 보통 60초) 동안 유지된다. TIME_WAIT 상태의 소켓이 과도하게 많아지는 현상은 짧은 시간 내에 매우 많은 연결을 생성하고 종료하는 서버(예: 웹 서버)에서 주로 관찰된다. 이는 사용 가능한 로컬 포트를 고갈시켜 새로운 아웃바운드 연결에 장애를 일으킬 수 있다. 운영체제 튜닝(예: net.ipv4.tcp_tw_reuse 파라미터 조정)으로 완화할 수 있지만, 근본적인 해결은 연결 풀링과 같은 애플리케이션 설계 개선을 통해 연결 생명주기를 관리하는 것이다.
문제 상태 | 발생 원인 | 주된 영향 | 일반적인 해결 방향 |
|---|---|---|---|
CLOSE_WAIT 누적 | 애플리케이션이 | 파일 디스크립터 고갈, 메모리 누수 | 애플리케이션 코드 디버깅 및 수정 |
TIME_WAIT 과다 | 단시간 내 대량의 연결 생성/종료 (액티브 클로즈 측) | 로컬 포트 고갈, 새 연결 실패 | 연결 재사용 설정, 애플리케이션 연결 풀링 도입 |
FIN_WAIT_2 지연 | 상대방이 최종 ACK를 보내지 않음 (패킷 손실 또는 상대방 장애) | 소켓 자원 장기 점유 | 운영체제의 |
이러한 상태를 모니터링하기 위해 netstat -ant 또는 ss -ant 명령어를 사용하여 각 상태별 소켓 수를 확인하는 것이 일반적이다. 특정 상태의 수가 시간이 지나도 줄지 않거나 지속적으로 증가하는 추세를 보인다면, 해당하는 애플리케이션 또는 네트워크 구성을 점검해야 한다.