네트워크 용어 사전
TCPSYN · ACK · TIME_WAIT · 4-way 종료

3-way 핸드셰이크

연결을 맺기 위해 세 번 주고받는 절차. 양쪽의 순서번호를 교환하고 서로 수신 가능함을 확인한다.

TCP가 연결을 맺기 위해 세 번 주고받는 절차.

클라이언트                          서버
    │  ── SYN(seq=x) ──────────→     │   "연결하고 싶다. 내 번호는 x"
    │  ←─ SYN+ACK(seq=y,ack=x+1) ─   │   "좋다. 내 번호는 y, 네 x 받았다"
    │  ── ACK(ack=y+1) ────────→     │   "네 y 받았다"
    │            연결 성립           │

왜 세 번인가

두 번이면 부족하다

  • 클라이언트는 "서버가 내 말을 들었다" 를 알지만
  • 서버는 "클라이언트가 내 말을 들었다" 를 모른다

세 번이면 양쪽 모두 '보내기와 받기가 모두 가능함' 을 확인한다 동시에 양쪽의 초기 순서번호(ISN)를 교환한다

초기 순서번호는 무작위로 정한다 — 예측 가능하면 공격자가 연결에 끼어들 수 있기 때문이다.

종료는 네 번

    │  ── FIN ─────────→  │   "나는 다 보냈다"
    │  ←─ ACK ──────────  │   "알았다" (아직 내 데이터는 남았을 수 있다)
    │  ←─ FIN ──────────  │   "나도 다 보냈다"
    │  ── ACK ─────────→  │   "알았다"
              ↓
         TIME_WAIT (2×MSL 동안 대기)

연결을 끊는 건 양방향이 각각이라 네 번이 된다(한쪽만 닫는 half-close도 가능하다).

TIME_WAIT — 실무에서 자주 만난다

먼저 닫은 쪽이 2×MSL(보통 60초) 동안 남는다

왜?

  • ① 마지막 ACK 가 유실되면 상대가 FIN 을 재전송한다 — 받아 줄 누군가가 있어야 한다
  • ② 같은 포트 조합의 '옛 패킷' 이 뒤늦게 도착해 새 연결을 오염시키는 것을 막는다
ss -s                          # 상태별 소켓 수
netstat -an | grep TIME_WAIT | wc -l

증상: 짧은 연결을 대량으로 맺는 서버에서 TIME_WAIT 이 수만 개 쌓인다

  • 포트가 고갈되어 새 연결을 못 맺는다

처방

  • keep-alive 로 연결을 재사용한다 (근본 해법)
  • net.ipv4.tcp_tw_reuse = 1 (클라이언트 측 재사용)
  • SO_REUSEADDR 로 서버 재시작 시 바인딩 문제 회피
  • ⚠️ tcp_tw_recycle 은 NAT 환경에서 문제를 일으켜 커널에서 제거됐다

SYN 플러딩

SYN 만 잔뜩 보내고 ACK 를 안 보낸다

  • 서버의 대기 큐(백로그)가 가득 차 정상 연결을 못 받는다
  • SYN 쿠키로 방어한다 (큐에 저장하지 않고 순서번호에 정보를 담아 되돌린다)

면접 함정

  • "종료도 3번이면 된다" → 각 방향을 따로 닫아 4번이다.
  • "TIME_WAIT은 서버에 쌓인다"먼저 닫은 쪽에 쌓인다. 서버가 먼저 닫으면 서버에 쌓인다.

상태 전이를 눈으로

ss -tan | head
# State      Recv-Q Send-Q   Local Address:Port   Peer Address:Port
# LISTEN     0      128      0.0.0.0:8080         0.0.0.0:*
# ESTAB      0      0        10.0.0.5:8080        10.0.0.9:51234
# TIME-WAIT  0      0        10.0.0.5:8080        10.0.0.9:51233
주요 상태
  LISTEN       연결을 기다린다
  SYN_SENT     SYN 을 보내고 응답 대기 (여기서 멈춰 있으면 방화벽·경로 문제)
  SYN_RECV     SYN 을 받고 SYN+ACK 를 보낸 상태 (여기가 쌓이면 SYN 플러딩 의심)
  ESTABLISHED  연결됨
  CLOSE_WAIT   상대가 FIN 을 보냈는데 내가 close() 를 안 했다  ← 애플리케이션 버그 신호
  TIME_WAIT    먼저 닫은 쪽이 2×MSL 대기

CLOSE_WAIT이 쌓이면 100% 애플리케이션 버그다 — 소켓을 닫지 않는 코드가 있다는 뜻이라, 커널 파라미터로는 해결되지 않는다.

keep-alive는 다른 이야기

  • TCP keep-alive — 연결이 살아 있는지 주기적으로 확인 (기본 2시간 — 매우 길다)

  • HTTP keep-alive — 응답 후에도 TCP 연결을 끊지 않고 재사용 (핸드셰이크를 아낀다)

  • 이름이 같지만 계층도 목적도 다르다

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — TCP — 핸드셰이크·신뢰성·흐름/혼잡 제어