네트워크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단계가 필요합니다.