전체 목록
네트워크Medium#93

TCP 3-way handshake와 4-way handshake를 설명해주세요.

#네트워크#TCP#핸드셰이크#연결
힌트

SYN, SYN-ACK, ACK, FIN 플래그의 역할을 생각해보세요.

정답 및 해설

TCP 3-way handshake와 4-way handshake를 설명해주세요.

TCP(Transmission Control Protocol)는 신뢰성 있는 연결 기반 통신을 보장하는 프로토콜입니다. 연결을 수립할 때 3-way handshake를, 연결을 종료할 때 4-way handshake를 사용합니다. 이 과정을 통해 양측이 데이터 송수신 준비 완료를 상호 확인하고, 모든 데이터가 전송된 후 안전하게 연결을 종료합니다.

TCP 헤더의 제어 플래그

TCP 헤더에는 6가지 제어 비트(플래그)가 있습니다:

SYN (Synchronize): 연결 초기화 요청, 시퀀스 번호 동기화
ACK (Acknowledge): 수신 확인, ack 번호 필드 유효 표시
FIN (Finish):      연결 종료 요청
RST (Reset):       연결 강제 종료
PSH (Push):        데이터를 즉시 애플리케이션에 전달
URG (Urgent):      긴급 데이터 포함

시퀀스 번호(Sequence Number)와 확인 응답 번호(ACK Number)

Sequence Number (seq):
- 송신하는 데이터의 첫 번째 바이트 번호
- 초기값은 랜덤(보안을 위해)

Acknowledgment Number (ack):
- 다음에 받기를 기대하는 바이트 번호
- 상대방의 seq + 1 = 내가 보내는 ack
- "seq까지 잘 받았으니 seq+1부터 보내줘"의 의미

3-Way Handshake (연결 수립)

전체 흐름

클라이언트                                    서버
   |                                            |
   |                                          LISTEN
   |                                            |
   |---------- 1. SYN ----------------------->|
   |   seq=x, SYN=1                             |
   |                                         SYN_RCVD
   |                                            |
   |<--------- 2. SYN-ACK -------------------|
   |              seq=y, ack=x+1, SYN=1, ACK=1 |
   |                                            |
   |---------- 3. ACK ----------------------->|
   |   seq=x+1, ack=y+1, ACK=1                 |
   |                                         ESTABLISHED
ESTABLISHED                                     |
   |                                            |
   |=========== 데이터 전송 시작 =============|

1단계: SYN (클라이언트 → 서버)

클라이언트가 서버에 연결 요청을 보냅니다.

TCP 헤더:
  - SYN = 1
  - seq = x (클라이언트의 초기 시퀀스 번호, ISN: Initial Sequence Number)
  - ack = 0 (아직 서버의 seq를 모름)

클라이언트 상태: SYN_SENT

2단계: SYN-ACK (서버 → 클라이언트)

서버가 연결 요청을 수락하고 자신의 연결 요청도 함께 보냅니다.

TCP 헤더:
  - SYN = 1 (서버도 연결 요청)
  - ACK = 1 (클라이언트의 SYN 수신 확인)
  - seq = y (서버의 초기 시퀀스 번호, ISN)
  - ack = x + 1 ("x까지 받았으니 x+1번부터 보내줘")

서버 상태: SYN_RCVD

3단계: ACK (클라이언트 → 서버)

클라이언트가 서버의 SYN-ACK에 대한 확인 응답을 보냅니다.

TCP 헤더:
  - ACK = 1
  - seq = x + 1
  - ack = y + 1 ("y까지 받았으니 y+1번부터 보내줘")

클라이언트 상태: ESTABLISHED
서버 상태: ESTABLISHED (ACK 수신 후)

왜 3번인가? (2번이 부족한 이유)

2-way만으로는 부족한 이유:

[2-way 시도]
클라이언트: "나 연결할 수 있어?" (SYN)
서버: "응, 나도 연결할 수 있어!" (SYN-ACK)

문제: 서버는 클라이언트가 서버의 SYN-ACK를 받았는지 확인 불가!
→ 서버의 SYN에 대한 ACK가 없으면, 서버는 클라이언트가 수신 가능한지 알 수 없음

[3-way로 해결]
1. 클라이언트 → 서버: "나 보낼 수 있어?" (SYN)
2. 서버 → 클라이언트: "응, 나도 보낼 수 있어. 너도 받을 수 있어?" (SYN-ACK)
3. 클라이언트 → 서버: "응, 받을 수 있어!" (ACK)
→ 양방향 통신 가능 확인 완료!

4-Way Handshake (연결 종료)

전체 흐름

클라이언트                                    서버
   |                                            |
ESTABLISHED                               ESTABLISHED
   |                                            |
   |---------- 1. FIN ----------------------->|
   |   seq=u, FIN=1                             |
   |                                        CLOSE_WAIT
FIN_WAIT_1                                      |
   |                                            |
   |<--------- 2. ACK ------------------------|
   |              seq=v, ack=u+1, ACK=1        |
   |                                            |
FIN_WAIT_2                           [남은 데이터 전송]
   |                                            |
   |<--------- 3. FIN ------------------------|
   |              seq=w, ack=u+1, FIN=1, ACK=1 |
   |                                        LAST_ACK
TIME_WAIT                                       |
   |                                            |
   |---------- 4. ACK ----------------------->|
   |   seq=u+1, ack=w+1, ACK=1                 |
   |                                          CLOSED
   |                                            |
[2MSL 대기 후]
   |
CLOSED

1단계: FIN (클라이언트 → 서버)

클라이언트가 더 이상 보낼 데이터가 없음을 알립니다.
"나는 데이터 전송을 다 마쳤어"

TCP 헤더:
  - FIN = 1, ACK = 1
  - seq = u

클라이언트 상태: FIN_WAIT_1
주의: 클라이언트는 FIN을 보낸 후에도 서버의 데이터를 수신할 수 있음 (half-close)

2단계: ACK (서버 → 클라이언트)

서버가 클라이언트의 FIN을 받았음을 확인합니다.
"알겠어, 근데 나는 아직 보낼 데이터가 남아있어"

TCP 헤더:
  - ACK = 1
  - ack = u + 1

서버 상태: CLOSE_WAIT (아직 보낼 데이터가 있을 수 있음)
클라이언트 상태: FIN_WAIT_2 (서버의 FIN을 기다리는 중)

3단계: FIN (서버 → 클라이언트)

서버가 모든 데이터 전송을 마치고 연결 종료를 요청합니다.
"나도 이제 다 보냈어"

TCP 헤더:
  - FIN = 1, ACK = 1
  - seq = w

서버 상태: LAST_ACK

4단계: ACK (클라이언트 → 서버)

클라이언트가 서버의 FIN을 확인합니다.

TCP 헤더:
  - ACK = 1
  - ack = w + 1

클라이언트 상태: TIME_WAIT → (2MSL 후) CLOSED
서버 상태: CLOSED (ACK 수신 즉시)

TIME_WAIT 상태

TIME_WAIT: 마지막 ACK를 보낸 후 일정 시간 대기하는 상태

대기 시간: 2MSL (Maximum Segment Lifetime, 보통 30초~4분)
           → 일반적으로 60초~240초

TIME_WAIT가 필요한 이유

1. 마지막 ACK 유실 대비
   - 클라이언트가 보낸 마지막 ACK가 네트워크에서 손실될 수 있음
   - 서버가 ACK를 못 받으면 FIN을 재전송
   - TIME_WAIT 상태에서 재전송된 FIN을 받아 다시 ACK 전송 가능

2. 지연 패킷 처리
   - 이전 연결의 패킷이 늦게 도착할 수 있음
   - TIME_WAIT 동안 포트/IP를 점유하여 새 연결이 같은 포트를 사용하지 못하게 함
   - 2MSL 후 이전 연결의 모든 패킷이 네트워크에서 소멸됨을 보장

상황:
서버: FIN 전송
클라이언트: ACK 전송 → TIME_WAIT 진입
네트워크: ACK 유실!
서버: FIN 재전송
클라이언트: TIME_WAIT 상태이므로 FIN 수신 가능 → ACK 재전송

TIME_WAIT 관련 실무 문제

# 서버에 TIME_WAIT 연결이 많을 경우 확인
netstat -an | grep TIME_WAIT | wc -l

# 포트 재사용 설정 (Linux)
sysctl -w net.ipv4.tcp_tw_reuse=1

# TIME_WAIT 대기 시간 단축 (기본값: 60초)
# /etc/sysctl.conf 에서 설정
net.ipv4.tcp_fin_timeout = 30

비정상 종료: RST

4-way handshake 없이 강제 종료할 때 RST 플래그 사용:
- 애플리케이션 크래시
- 방화벽에 의한 차단
- 연결 타임아웃

RST 수신 시: 즉시 연결 종료, TIME_WAIT 없음

연결 상태 다이어그램

서버: LISTEN → SYN_RCVD → ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED
클라이언트: CLOSED → SYN_SENT → ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED

정리

구분단계방향플래그설명
3-way (연결)1클라이언트 → 서버SYN연결 요청
3-way (연결)2서버 → 클라이언트SYN+ACK요청 수락 + 역방향 연결 요청
3-way (연결)3클라이언트 → 서버ACK역방향 연결 확인
4-way (종료)1클라이언트 → 서버FIN+ACK종료 요청
4-way (종료)2서버 → 클라이언트ACK종료 확인 (half-close)
4-way (종료)3서버 → 클라이언트FIN+ACK서버 종료 준비 완료
4-way (종료)4클라이언트 → 서버ACK최종 확인 → TIME_WAIT

핵심: 3-way handshake는 양방향 통신 가능 여부를 3단계로 확인하고, 4-way handshake에서 2,3 단계 사이에 서버가 남은 데이터를 전송할 수 있어 4단계가 필요합니다.