핵심 인사이트 (3줄 요약)

  1. 본질: CLOSE_WAIT과 LAST_ACK 상태는 내가 먼저 통신을 끊자고 한 게 아니라, 상대방이 먼저 "나 끊을게(FIN)"라고 이별 통보를 쐈을 때 패시브하게(수동적으로) 대응하는 쪽(Passive Close)이 거쳐 가는 2단계 이별 수순이다.
  2. CLOSE_WAIT (미련의 시간): 상대방이 끊자고(FIN) 해서 내가 "알았어(ACK)"라고 1차 대답을 해준 상태다. 여기서 당장 끊는 게 아니라, "잠깐만! 나 아직 네게 줄 데이터가 조금 남았으니까 이거 마저 챙겨가!"라며 내 쪽 애플리케이션이 마무리 작업을 할 수 있게 끈질기게 붙잡아두는 미련 뚝뚝 흐르는 유예 기간이다.
  3. LAST_ACK (찐막 기다림): 내 쪽에서도 남은 데이터를 다 주고 "이제 나도 진짜 미련 없다. 끊자!(FIN)"라고 마지막 인사를 쏜 뒤, 상대방이 마지막으로 고개를 끄덕여주는 최종 영수증(ACK) 딱 한 장만을 목이 빠지게 기다리며 숨을 참는 최후의 순간이다.

Ⅰ. 개요 및 필요성 (Context & Necessity)

  • 개념: TCP 4-Way Handshake 종료 과정에서, 연결 해제 요청을 '수신한 측(Passive Close)'의 OS 소켓이 거치게 되는 상태 머신(State Machine)의 과도기적 두 단계.

  • 필요성: 만약 통신이 무 자르듯 뚝 끊긴다면? 내가 구글 드라이브에 100MB 파일을 올리고 있는데, 99MB쯤 올라갔을 때 구글 서버(앱)가 갑자기 급한 일이 생겨서 나한테 FIN(끊자!) 패킷을 날렸다고 치자. 내가 그걸 받고 즉각 셔터를 내리면, 기껏 올린 99MB 파일은 전송 실패로 쓰레기통에 처박힌다. "네가 바쁜 건 알겠고 일단 셔터(입구)는 닫아줄게. 하지만 내가 내보낼 1MB 찌꺼기 트래픽이 아직 남아있으니 이거 마저 밀어 넣을 때까지만 출구 쪽 셔터를 붙잡아줘!!" 이것이 패시브 종료 모델의 존재 이유다.

  • 💡 비유: 이 과정은 식당의 **"마감 시간 풍경"**과 완벽히 똑같습니다.

    • CLOSE_WAIT: 영업시간 끝났다고 지배인이 입구 간판의 불을 껐습니다(상대의 FIN 수신). 하지만 아직 주방에서는 손님이 시킨 마지막 군만두(남은 데이터)를 요리 중입니다. 주방장은 군만두가 나올 때까지 주방 셔터를 붙잡고 버티고 섭니다(CLOSE_WAIT).
    • LAST_ACK: 군만두를 손님상에 내고 "이제 저희 진짜 문 닫습니다(나의 FIN 발송)"라고 찐막 선언을 했습니다. 이제 손님이 카드 결제 사인을 해주기(마지막 ACK 수신)만을 **계산대 앞에서 멍하니 기다리는 상태(LAST_ACK)**입니다.

📢 섹션 요약 비유: CLOSE_WAIT은 헤어지자는 애인의 통보를 받고 일단 고개를 끄덕였지만, "그래도 내가 사준 플스는 돌려주고 가!"라며 바짓가랑이를 붙잡고 남은 짐(데이터)을 싸주는 질척거리는 시간이며, LAST_ACK는 짐을 다 싸주고 상대가 마지막으로 "잘 살아라(최종 ACK)" 하고 뒤돌아 걸어가는 모습을 눈으로 확인하기 전 1초의 찰나입니다.


Ⅱ. 두 상태의 연쇄 동작과 무한 좀비 에러 (Deep Dive)

네트워크 실무에서 TIME_WAIT 못지않게 시스템 엔지니어의 피를 말리는 것이 바로 좀비처럼 쌓이는 CLOSE_WAIT 장애다.

1. 정상적인 흐름의 완벽한 복기

  • 클라이언트 ── FIN ──▶ 서버 (수신!)
  • 서버 OS: "어? 쟤가 끊재! 일단 알았다고 대답해 줘!"
    • 서버 ── ACK ──▶ 클라이언트
    • [서버 소켓 상태: CLOSE_WAIT 진입!]
  • 서버 OS -> 서버 애플리케이션(예: Tomcat): "야 톰캣아! 저기 손님이 이제 그만 먹고 나가겠대! 넌 짐 빨리 다 싸고 입에 물고 있는 소켓 닫아(Close() 함수 호출)!"
  • 서버 애플리케이션은 남은 파일을 싹 다 밀어낸 뒤, 마지막으로 OS에게 "그래, 나도 다 줬어. 셔터 닫아라!"라고 지시한다.
  • 서버 OS: "오케이!"
    • 서버 ── FIN ──▶ 클라이언트
    • [서버 소켓 상태: LAST_ACK 진입!]
  • 클라이언트 ── ACK (마지막 영수증) ──▶ 서버
  • [서버 소켓 상태: CLOSED (완전 소멸, 깔끔한 해피엔딩!)]

2. 실무 대참사: 멈춰버린 시계 "CLOSE_WAIT 좀비"

리눅스 서버에 접속해 netstat -ano를 쳤는데, 100개가 넘는 소켓이 CLOSE_WAIT 상태로 며칠째 사라지지 않고 썩어간다면? 이건 **네트워크 문제가 아니라 100% 개발자의 코딩 실수(애플리케이션 버그)**다.

  • 원리: CLOSE_WAIT 상태는 OS가 "야, 상대방이 끊쟀어! 너 하던 거 빨리 마무리하고 소켓 닫아라!"라고 어플리케이션(개발자가 짠 코드)에게 통보한 상태다.
  • 버그 발생: 그런데 미친 어플리케이션이 무한 루프에 빠졌거나, 개발자가 코드 맨 마지막에 socket.close() 라는 종료 함수를 깜빡하고 안 적어 놓았다!
  • 결과: 앱은 "나 아직 안 끝났어!"라고 멍때리고, OS는 "앱이 아직 안 끝났다니까 셔터 못 내리겠네..." 하고 무한정 기다려준다(CLOSE_WAIT). 상대방은 이미 퇴근한 지 오렌지인데, 내 서버 혼자서 끝도 없이 메모리에 소켓을 붙잡고 있다가 결국 서버 램이 꽉 차서 죽어버린다.
 ┌─────────────────────────────────────────────────────────────┐
 │                CLOSE_WAIT 장애 시나리오의 완벽한 묘사              │
 ├─────────────────────────────────────────────────────────────┤
 │                                                             │
 │   [ 1. 고객 PC ] "나 로그아웃함 ㅂㅂ" (FIN) ──▶ [ 2. 우리 회사 서버 OS ] │
 │                                              상태: CLOSE_WAIT     │
 │                                                             │
 │   [ 3. 회사 서버 OS ] "야 백엔드 프로그램아! 고객이 끊쟀어 문 닫아라!"    │
 │       │                                                     │
 │       ▼ (명령 하달)                                          │
 │                                                             │
 │   [ 4. 미친 백엔드 프로그램 ] (개발자가 버그 내서 무한 연산 도는 중)       │
 │      "...... (무응답, close() 함수 호출 안 함)"                │
 │                                                             │
 │   ▶ 결론: OS는 앱이 명령을 내릴 때까지 수동적으로 평생 CLOSE_WAIT 상태로│
 │           기다린다. 이건 네트워크나 방화벽 문제가 아니라 순전히 개발자  │
 │           가 코드를 X 같이 짜서 발생한 애플리케이션 찌꺼기 에러다!!    │
 └─────────────────────────────────────────────────────────────┘

📢 섹션 요약 비유: CLOSE_WAIT 좀비 현상은 식당 지배인(OS)이 손님이 나갔다고 주방장(어플리케이션)에게 마감하라고 소리쳤는데, 주방장이 이어폰을 끼고 노래를 들으며 영원히 설거지만 하고 있어서, 지배인이 주방장 눈치를 보며 차마 식당 문셔터를 내리지 못하고 평생 밤을 새우는 환장할 코미디입니다. 해결책은 주방장(개발자) 등짝을 때려서 설거지(버그)를 멈추게 하는 것뿐입니다.