웹 브라우저에서 페이지를 요청하거나 파일을 전송할 때, 데이터를 보내기 전에 먼저 상대방과 '연결'을 맺는 과정이 있습니다. 이 연결을 수립하지 않으면, 데이터가 올바른 순서로 전달되었는지, 상대방이 실제로 수신 가능한 상태인지 확인할 방법이 없습니다.

TCP는 이 문제를 해결하기 위해 데이터 전송 전에 반드시 연결을 수립하고, 전송이 끝나면 연결을 명시적으로 해제합니다. 그렇다면 이 연결은 정확히 몇 단계로, 어떤 이유로 그 단계가 결정되는 걸까요?

이 글에서는 TCP 3-way Handshake로 연결이 수립되는 과정, 4-way Handshake로 연결이 해제되는 과정, 그리고 TIME_WAIT 상태가 왜 필요한지를 흐름 중심으로 정리해 보겠습니다.


요약

  • 3-way Handshake: TCP 연결 수립 — SYN → SYN-ACK → ACK 3단계
  • 4-way Handshake: TCP 연결 해제 — FIN → ACK → FIN → ACK 4단계
  • 왜 3단계인가: 양쪽이 서로의 송수신 능력을 확인하는 최소 단계
  • 왜 해제는 4단계인가: 서버가 FIN을 받은 시점에 아직 보낼 데이터가 남아 있을 수 있기 때문
  • TIME_WAIT: 클라이언트가 마지막 ACK 전송 후 일정 시간 대기하는 상태

1. 왜 연결 수립이 필요한가

TCP는 UDP와 달리 데이터를 보내기 전에 먼저 연결을 수립합니다. 이 과정이 필요한 이유는 다음과 같습니다.

  • 상대방이 존재하는지 확인: 서버가 실제로 요청을 받을 수 있는 상태인지 확인합니다.
  • 시퀀스 번호 동기화: 양쪽이 패킷 순서를 관리하기 위한 초기 시퀀스 번호를 교환합니다.
  • 송수신 능력 상호 확인: 클라이언트가 보낼 수 있고, 서버가 받을 수 있으며, 서버가 보낼 수 있고, 클라이언트가 받을 수 있음을 모두 확인합니다.

이 세 가지를 확인하는 최소 단계가 3단계입니다. 2단계로는 양방향 확인이 불가능하고, 4단계는 3단계의 SYN-ACK 하나로 처리할 수 있는 내용(수신 확인 + 연결 요청)을 별도 패킷 두 개로 나누는 것이므로 중복됩니다.


2. 3-way Handshake — 연결 수립

 

동작 순서

1. SYN      클라이언트 → 서버
            "연결 요청합니다. 제 초기 시퀀스 번호는 x입니다."

2. SYN-ACK  서버 → 클라이언트
            "수락합니다(ACK=x+1). 제 초기 시퀀스 번호는 y입니다."

3. ACK      클라이언트 → 서버
            "확인했습니다(ACK=y+1). 연결 완료."

 

각 플래그의 의미

  • SYN (Synchronize): 연결 요청 신호. 초기 시퀀스 번호(ISN, Initial Sequence Number)를 포함합니다.
  • ACK (Acknowledgment): 수신 확인 신호. ACK 번호는 "다음에 받을 시퀀스 번호"를 의미합니다. SYN/FIN 패킷은 데이터가 없어도 시퀀스 번호를 1 소비하므로 ACK = 상대방 SEQ + 1입니다. 데이터 전송 중에는 ACK = 상대방 SEQ + 수신한 바이트 수로 증가합니다.
  • SYN-ACK: SYN과 ACK를 동시에 보내는 것으로, 연결 수락과 자신의 연결 요청을 한 번에 처리합니다.

시퀀스 번호 교환의 의미

3-way handshake에서 단순히 연결 확인만 하는 것이 아니라 시퀀스 번호도 함께 교환합니다. 이 번호를 기반으로 이후 패킷의 순서를 관리하고, 손실된 패킷을 감지합니다.

주의: 초기 시퀀스 번호(ISN)는 고정값이 아니라 무작위로 생성됩니다. 예측 가능한 번호를 사용하면 TCP 시퀀스 번호 예측 공격에 취약해지기 때문입니다.

 


3. 3-way Handshake 이후 — 데이터 전송

연결이 수립되면 양방향으로 데이터를 주고받을 수 있습니다. 각 데이터 패킷은 시퀀스 번호와 함께 전송되고, 수신 측은 ACK로 확인 응답을 보냅니다.

# 아래는 동작 원리 설명을 위한 단순화 예시입니다.
# 실제 ACK 번호는 SEQ + 수신한 데이터 바이트 수만큼 증가하며,
# HTTP GET 요청처럼 수십~수백 바이트를 포함하는 경우 ACK = x+1+(요청 크기)가 됩니다.

클라이언트 → 서버: [SEQ=x+1] HTTP GET /
서버 → 클라이언트: [ACK=x+2] 수신 확인  # 단순화 (실제: x+1+요청바이트수)
서버 → 클라이언트: [SEQ=y+1] HTTP 200 OK (응답 데이터)
클라이언트 → 서버: [ACK=y+2] 수신 확인  # 단순화 (실제: y+1+응답바이트수)

 

이 과정에서 ACK가 일정 시간 안에 오지 않으면 재전송이 발생합니다. 재전송 타이머(RTO)의 계산 원리는 8편에서 자세히 다룹니다.


4. 4-way Handshake — 연결 해제

 

연결을 종료할 때는 3단계가 아닌 4단계가 필요합니다.

 

동작 순서

1. FIN      클라이언트 → 서버
            "저는 보낼 데이터를 모두 보냈습니다. 연결을 종료하겠습니다."

2. ACK      서버 → 클라이언트
            "수신 확인. 아직 전송 중인 데이터가 있어 FIN은 별도로 전송합니다."
            (서버는 클라이언트의 FIN을 받은 시점에 아직 전송 중인 데이터가 있을 수 있습니다. 
            ACK로 수신을 확인하되, 데이터를 모두 보낸 뒤 별도로 FIN을 전송합니다.)

3. FIN      서버 → 클라이언트
            "저도 보낼 데이터를 모두 보냈습니다. 연결을 종료하겠습니다."

4. ACK      클라이언트 → 서버
            "확인했습니다. 연결 종료."

 

왜 해제는 4단계인가

연결 수립(3단계)과 달리 해제가 4단계인 이유는 Half-close 개념 때문입니다.

클라이언트가 FIN을 보내면 클라이언트→서버 방향의 연결만 끊깁니다. 이것을 Half-close라고 하며, 서버→클라이언트 방향은 아직 열려 있습니다. 서버는 위 2단계에서 설명한 것처럼 전송 중인 데이터가 있을 수 있으므로 ACK와 FIN을 즉시 합쳐 보내지 않고 분리합니다. ACK로 수신을 먼저 확인하고, 남은 데이터를 모두 보낸 뒤 FIN을 전송합니다. 이 때문에 3단계가 아닌 4단계가 됩니다.


5. TIME_WAIT 상태

클라이언트가 마지막 ACK를 보낸 뒤 즉시 연결을 닫지 않고 TIME_WAIT 상태로 일정 시간 대기합니다.

클라이언트가 마지막 ACK 전송
         ↓
TIME_WAIT 상태 진입 (기본값: 2×MSL)
(RFC 793 기준 MSL=120초 → 2×MSL=240초,Linux 실제 구현은 약 60초로 하드코딩)
         ↓
TIME_WAIT 종료 후 연결 완전 종료

 

TIME_WAIT가 필요한 이유 2가지

  • 마지막 ACK 유실 대비: 클라이언트의 마지막 ACK가 유실되면 서버는 FIN을 재전송합니다. TIME_WAIT 상태에서 이 재전송된 FIN을 받아 다시 ACK를 보낼 수 있습니다. 연결을 즉시 닫아버리면 재전송된 FIN에 응답할 수 없습니다.
  • 지연 패킷 처리: 네트워크에 남아 있는 이전 연결의 지연 패킷이 새 연결에 혼입되는 것을 방지합니다.

주의: 서버에서 짧은 시간에 많은 연결을 처리하면 TIME_WAIT 상태의 소켓이 대량으로 쌓여 포트 고갈 문제가 발생할 수 있습니다. SO_REUSEADDR 소켓 옵션이나 tcp_tw_reuse 커널 파라미터로 완화할 수 있습니다.


6. 연결 상태 다이어그램

TCP 연결은 여러 상태를 거칩니다. 실무에서 netstat 명령어로 확인할 수 있습니다.

상태 설명
LISTEN 서버가 연결 요청을 기다리는 상태
SYN_SENT 클라이언트가 SYN을 보내고 응답 대기
SYN_RECEIVED 서버가 SYN을 받고 SYN-ACK를 보낸 상태
ESTABLISHED 연결 수립 완료, 데이터 전송 가능
FIN_WAIT_1 클라이언트가 FIN을 보내고 ACK 대기
FIN_WAIT_2 ACK를 받고 서버의 FIN 대기
CLOSE_WAIT 서버가 FIN을 받고 ACK를 보낸 상태
LAST_ACK 서버가 FIN을 보내고 ACK 대기
TIME_WAIT 마지막 ACK 전송 후 대기
CLOSED 연결 완전 종료

 

아래 명령어로 현재 시스템의 TCP 연결 상태를 직접 확인할 수 있습니다.

# 현재 TCP 연결 상태 확인
netstat -an | grep ESTABLISHED
netstat -an | grep TIME_WAIT

추가 팁

SYN Flood 공격

공격자가 다수의 SYN 패킷을 보내고 ACK를 보내지 않으면, 서버는 SYN_RECEIVED 상태의 연결을 대량으로 유지하게 됩니다. 서버의 연결 대기 큐(backlog)가 가득 차면 정상적인 연결 요청을 처리하지 못하는 서비스 거부(DoS) 상태가 됩니다. 이를 방어하기 위해 SYN Cookie 기법을 사용합니다.

 

Keep-Alive

HTTP/1.1은 기본적으로 Keep-Alive를 사용합니다. 하나의 TCP 연결을 유지하면서 여러 HTTP 요청을 처리합니다. 매번 3-way handshake를 반복하는 오버헤드를 줄일 수 있습니다.

 

TCP Fast Open (TFO)

3-way handshake의 지연을 줄이기 위해 첫 번째 SYN 패킷에 데이터를 포함시키는 기법입니다. 이전에 연결한 적 있는 서버와 통신 시 RTT를 1회 줄일 수 있습니다(RFC 7413).


📌 정리

  • 3-way Handshake는 SYN → SYN-ACK → ACK 3단계로 연결을 수립합니다.
  • 4-way Handshake는 FIN → ACK → FIN → ACK 4단계로 연결을 해제합니다.
  • 해제가 4단계인 이유는 서버가 FIN 수신 시점에 아직 보낼 데이터가 남아 있을 수 있기 때문입니다.
  • TIME_WAIT는 마지막 ACK 유실 대비와 지연 패킷 혼입 방지를 위해 필요합니다.

📌 실무에서 중요한 이유

  • netstat으로 TIME_WAIT 상태 소켓이 대량으로 쌓여 있다면 포트 고갈을 의심합니다.
  • SYN Flood 공격 방어를 위해 SYN Cookie 설정 여부를 확인합니다.
  • HTTP Keep-Alive 설정은 TCP 연결 수립 오버헤드를 줄이는 데 중요합니다. 특히 지연이 큰 환경에서 효과가 큽니다.
  • 로드밸런서 앞단에서 클라이언트 연결이 자주 끊기는 문제가 발생하면 TCP Handshake 과정의 타임아웃 설정을 확인합니다.

참고 자료

 

+ Recent posts