반응형
TCP 연결 해제 과정 (4-Way Handshake)
TCP(Transmission Control Protocol)는 신뢰성 있는 연결 지향적 프로토콜로, 연결을 맺을 때뿐만 아니라 연결을 해제할 때도 정확한 절차를 따릅니다. TCP 연결 해제는 4-Way Handshake라는 과정을 통해 이루어지며, 양방향 연결을 안전하게 종료합니다.
TCP 연결의 양방향성
TCP 연결을 이해하기 위해서는 먼저 연결의 양방향성을 알아야 합니다:
- TCP 연결은 전이중(Full-Duplex) 통신입니다
- 클라이언트 → 서버 방향의 연결
- 서버 → 클라이언트 방향의 연결
- 두 방향이 독립적으로 종료될 수 있습니다
4-Way Handshake 과정
1단계: 클라이언트의 연결 종료 요청 (FIN)
클라이언트 → 서버: FIN 세그먼트 전송
- FIN 플래그가 설정된 세그먼트를 전송
- 클라이언트가 "더 이상 보낼 데이터가 없음"을 알림
- 클라이언트는 FIN_WAIT_1 상태로 전환
- 하지만 서버로부터 데이터는 여전히 받을 수 있습니다
2단계: 서버의 FIN 확인 응답 (ACK)
서버 → 클라이언트: ACK 세그먼트 전송
- 서버가 클라이언트의 FIN을 확인했다는 ACK 전송
- ACK 번호는 받은 FIN의 시퀀스 번호 + 1
- 서버는 CLOSE_WAIT 상태로 전환
- 클라이언트는 FIN_WAIT_2 상태로 전환
- 서버는 아직 클라이언트로 데이터를 보낼 수 있습니다
3단계: 서버의 연결 종료 요청 (FIN)
서버 → 클라이언트: FIN 세그먼트 전송
- 서버도 "더 이상 보낼 데이터가 없음"을 알리는 FIN 전송
- 서버는 LAST_ACK 상태로 전환
- 이제 양방향 모두 종료 요청이 완료됨
4단계: 클라이언트의 최종 확인 (ACK)
클라이언트 → 서버: ACK 세그먼트 전송
- 클라이언트가 서버의 FIN에 대한 ACK 전송
- 클라이언트는 TIME_WAIT 상태로 전환
- 서버는 ACK를 받고 CLOSED 상태로 전환
TCP 상태 변화 다이어그램
클라이언트 서버
ESTABLISHED ──┐ ┌── ESTABLISHED
│ │
│ ①FIN │
├─────────────────→│
FIN_WAIT_1 ───┤ ├─── CLOSE_WAIT
│ │
│ ②ACK │
│←─────────────────┤
FIN_WAIT_2 ───┤ │
│ │
│ ③FIN │
│←─────────────────┤
TIME_WAIT ────┤ ├─── LAST_ACK
│ │
│ ④ACK │
├─────────────────→│
│ │
CLOSED ────┘ └─── CLOSED
주요 TCP 상태 설명
FIN_WAIT_1
- 클라이언트가 FIN을 보내고 ACK를 기다리는 상태
- 능동적으로 연결을 종료하려는 측의 첫 번째 상태
FIN_WAIT_2
- 서버로부터 ACK를 받았지만, 서버의 FIN을 기다리는 상태
- 클라이언트 → 서버 방향은 종료되었지만, 서버 → 클라이언트는 아직 활성
CLOSE_WAIT
- 클라이언트의 FIN을 받고 ACK를 보낸 후의 서버 상태
- 애플리케이션이 종료 준비를 마치고 FIN을 보낼 때까지 대기
LAST_ACK
- 서버가 FIN을 보내고 클라이언트의 ACK를 기다리는 상태
TIME_WAIT
- 가장 중요한 상태 중 하나
- 2MSL(Maximum Segment Lifetime) 동안 대기
- 보통 30초~2분 정도
TIME_WAIT 상태의 중요성
왜 TIME_WAIT이 필요한가?
- 지연된 패킷 처리
- 네트워크에서 지연되어 도착하는 패킷들을 기다림
- 새로운 연결과 충돌하지 않도록 방지
- 마지막 ACK 재전송
- 서버가 마지막 ACK를 받지 못한 경우를 대비
- 서버가 FIN을 재전송하면 ACK로 응답할 수 있어야 함
- 포트 재사용 방지
- 같은 포트로 새로운 연결이 즉시 생성되는 것을 방지
- 이전 연결의 잔여 패킷과 새 연결의 패킷이 섞이지 않도록 함
TIME_WAIT과 성능
TIME_WAIT 상태는 시스템 리소스를 소모하므로, 고성능 서버에서는 다음과 같은 최적화 기법을 사용합니다:
- SO_REUSEADDR 소켓 옵션 사용
- Connection Pooling 기법
- Keep-Alive 연결 재사용
비정상적인 연결 종료
RST(Reset) 세그먼트
4-Way Handshake 대신 RST 플래그를 사용하여 강제로 연결을 종료하는 경우:
- 애플리케이션이 close() 대신 abort() 호출
- 소켓 버퍼에 읽지 않은 데이터가 남아있을 때 종료
- 예상치 못한 세그먼트를 받았을 때
Half-Close 상태
TCP는 한 방향만 종료하는 Half-Close를 지원합니다:
// 클라이언트에서 쓰기만 종료
shutdown(sockfd, SHUT_WR); // FIN 전송
// 읽기는 여전히 가능
실제 코드에서의 연결 종료
클라이언트 측
// 정상적인 종료
close(sockfd); // FIN 전송 후 4-Way Handshake 수행
// 강제 종료
struct linger ling;
ling.l_onoff = 1;
ling.l_linger = 0;
setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
close(sockfd); // RST 전송
서버 측
// 클라이언트의 연결 종료 감지
while ((n = read(connfd, buffer, sizeof(buffer))) > 0) {
// 데이터 처리
}
if (n == 0) {
// 클라이언트가 FIN 전송 (정상 종료)
close(connfd);
} else {
// 에러 발생
perror("read error");
}
성능 및 최적화 고려사항
TIME_WAIT 소켓 관리
# TIME_WAIT 상태의 소켓 확인
netstat -an | grep TIME_WAIT | wc -l
# 시스템 전체 TIME_WAIT 소켓 제한
echo 'net.ipv4.tcp_max_tw_buckets = 65536' >> /etc/sysctl.conf
서버 사이드 최적화
- Connection Pooling: 연결을 재사용하여 4-Way Handshake 횟수 감소
- Keep-Alive: 장시간 연결 유지로 연결 생성/해제 비용 절약
- 비동기 I/O: 블로킹 없이 다수의 연결을 효율적으로 관리
결론
TCP 4-Way Handshake는 신뢰성 있는 연결 종료를 보장하는 중요한 메커니즘입니다. 각 단계의 의미와 상태 변화를 정확히 이해하면:
- 네트워크 문제 디버깅이 쉬워집니다
- 성능 최적화 전략을 세울 수 있습니다
- 안정적인 네트워크 애플리케이션을 개발할 수 있습니다
특히 TIME_WAIT 상태의 중요성을 이해하고, 적절한 최적화 기법을 적용하는 것이 고성능 네트워크 프로그래밍의 핵심입니다.
반응형