[Network] TCP: 3 way handshake & 4 way handshake

jiny·2026년 2월 10일

Computer Science

목록 보기
8/16

1. 기본 개념 이해

✨ 3-way/4-way handshake

  • 3-way handshake: TCP 연결을 "서로 준비 완료 + 초기 시퀀스 번호(ISN) 동기화"까지 확정하는 절차
  • 4-way handshake: TCP 연결을 "양방향(Full-duplex) 스트림을 각각" 안전하게 닫는 절차

✨ TCP가 연결 지향적인 이유

  • 신뢰성 보장: TCP는 데이터가 손실되거나 순서가 바뀌지 않도록 보장한다.
  • 양방향 통신: 클라이언트와 서버 모두 데이터를 송수신할 수 있어야 한다.
  • 흐름 제어: 수신자의 처리 속도에 맞춰 전송 속도를 조절한다.

✨ 주요 플래그(Flag) 의미

  • SYN (Synchronize): 연결 요청, 순서 번호 동기화
  • ACK (Acknowledgment): 확인 응답, 수신 확인
  • FIN (Finish): 연결 종료 요청
  • RST (Reset): 연결 강제 종료

2. 3-Way Handshake (연결 수립)

✨ 상세 과정 (시각화)

✨ 각 단계별 설명

  1. 1단계: SYN (클라이언트 ➡️ 서버)
    • 클라이언트의 제안: "나 너랑 연결하고 싶어!"라는 신호를 보낸다.
    • ISN(x)의 의미: 처음 시작하는 번호(Initial Sequence Number)를 랜덤하게 생성한다. 랜덤인 이유는 이전 연결의 패킷과 혼동되지 않게 하고, 보안상 예측을 어렵게 하기 위해서이다.
    • 상태 변화: 클라이언트는 서버의 대답을 기다리는 `YN-SENT 상태가 된다. (CLOSET ➡️ SYN-SENT)
  1. 2단계: SYN-ACK (서버 ➡️ 클라이언트)
    • 서버의 응답과 제안: "응, 네 신호(x) 잘 받았어! 나도 너랑 연결하고 싶어!"라고 대답한다.
    • 번호의 마법
      • ack=x+1: 클라이언트가 보낸 x를 잘 받았으니, 다음엔 x+1번을 보내달라는 뜻이다.
      • seq=y: 서버 자신도 대화를 위해 자신의 시작 번호 y를 새로 만든다.
    • 상태 변화: 서버는 클라이언트의 최종 확답을 기다리는 SYN-RECEIVED 상태가 된다. (LISTEN ➡️ SYN-RECEIVED)
  1. 3단계: ACK (클라이언트 ➡️ 서버)
    • 클라이언트의 최종 확정: "응, 네 신호(y)도 잘 받았어! 이제 진짜 연결된 거야!"라고 마지막 확인 도장을 찍는다.
    • 번호의 마법: ack=y+1을 통해 서버의 y번 신호를 잘 받았음을 증명한다.
    • 상태 변화: 이제 양쪽 모두 ESTABLISHED 상태가 되어, 비로소 실제 데이터(HTTP 요청 등)를 주고받을 수 있게 된다.

📞 실생활 비유: 전화 통화

  • 1단계 (SYN): (전화를 걸며) "여보세요? 제 목소리 들리세요?"
  • 2단계 (SYN-ACK): "네, 잘 들려요! 제 목소리도 들리시나요?"
  • 3단계 (ACK): "네, 잘 들립니다! 말씀하세요."

✨ 3번의 과정이 필요한 이유

  • 2-Way로는 부족한 이유
    • 클라이언트만 서버의 존재를 확인하고, 서버는 클라이언트가 준비되었는지 확인할 수 없다.
    • 지연된 중복 패킷으로 인한 혼란을 방지할 수 없다.
  • 3-Way의 장점
    • 양방향 통신 준비 완료 확인
    • 양쪽의 초기 순서 번호 교환 및 동기화
    • 이전 연결의 지연 패킷과 구분 가능

✨ 순서 번호(Sequence Number)를 랜덤하게 생성하는 이유

  • 보안: 공격자가 다음에 올 패킷 순서 번호를 예측하여 연결을 가로채는(TCP Hijacking) 것을 방지한다.
  • 혼선 방지: 이전 연결에서 쓰던 지연 패킷과 현재 연결 패킷을 구분하기 위해서이다.

3. 4-Way Handshake (연결 종료)

✨ 상세 과정 (시각화)

✨ 각 단계별 설명

  1. 1단계: FIN (클라이언트 ➡️ 서버)
    • 클라이언트의 선언: "내가 보낼 데이터는 이게 마지막이야!"라고 알린다. 하지만 서버가 보낼 데이터는 아직 남아있을 수 있다는 점이 중요하다. (ESTABLISH ➡️ FIN-WAIT-1)
  1. 2단계: ACK (서버 ➡️ 클라이언트)
    • 서버의 응답: "알았어! 일단 네 종료 요청은 확인했어. 그런데 나 아직 너한테 줄 게 좀 남았으니까 조금만 기다려줘."라고 말한다.
    • 상태의 의미: 서버는 CLOSE-WAIT 상태가 되어 남은 작업을 마무리하고(ESTABLISHED ➡️ CLOSE-WAIT), 클라이언트는 서버의 마지막 인사를 기다리는 FIN-WAIT-2 상태가 된다(FIN-WAIT-1 ➡️ FIN-WAIT-2).
  1. 3단계: FIN (서버 ➡️ 클라이언트)
    • 서버의 종료 준비 완료: 남은 데이터를 다 보낸 서버가 "나도 이제 진짜 끝났어! 너도 준비됐지?"라며 종료 신호를 보낸다. (CLOSE-WAIT ➡️ LAST-ACK)
  1. 4단계: ACK (클라이언트 ➡️ 서버)
    • 클라이언트의 마지막 인사: "응, 확인했어! 잘 가!"라고 마지막 ACK를 보낸다.
    • TIME-WAIT가 필요한 이유 (중요)
      • 마지막 ACK 유실 대비: 만약 클라이언트가 보낸 마지막 ACK가 서버에 도착하지 못하면, 서버는 계속 LAST-ACK 상태에 갇히게 된다. 이를 방지하기 위해 클라이언트는 잠시 기다렸다가 서버가 재전송을 요청하면 다시 ACK를 보낸다.
      • 지연 패킷 처리: 네트워크 어딘가에 떠돌던 이전 데이터 조각이 뒤늦게 도착해서 새로운 연결에 혼란을 주는 것을 막기 위해, 모든 패킷이 소멸될 때까지 기다리는 것이다.

📞 실생활 비유: 전화 통화

  • 1단계 (FIN): "나 이제 할 말 다 했어. 끊을게~"
  • 2단계 (ACK): "어, 그래? 잠깐만, 나 너한테 말해줄 거 하나 더 있어. (남은 데이터 전송)"
  • 3단계 (FIN): "이제 나도 다 말했어! 진짜 끊는다?"
  • 4단계 (ACK): "응, 알았어! 잘 자! (하고 바로 안 끊고 상대방이 끊는 소리 들릴 때까지 잠시 대기)"

✨ 4번의 과정이 필요한 이유

TCP는 전이중(Full-Duplex) 통신이다.

  • 양방향으로 독립적인 데이터 흐름이다.
  • 한쪽이 보낼 데이터가 없어도, 다른 쪽은 아직 보낼 데이터가 있을 수 있다.
  • 따라서 각 방향의 연결을 독립적으로 종료해야 한다.

4. TIME-WAIT 상태의 중요성

✨ TIME-WAIT이란?

클라이언트가 마지막 ACK를 보낸 후 바로 종료하지 않고 일정 시간 대기하는 상태

✨ TIME-WAIT의 대기 시간

  • 2MSL(Maximum Segment Lifetime): 보통 1~4분

    MSL: 패킷이 네트워크에서 살아있을 수 있는 최대 시간 (보통 30초~2분)

✨ TIME-WAIT이 필요한 이유

  1. 서버의 정상적인 종료 보장 (신뢰성)
    • 만약 클라이언트가 마지막 ACK를 보내자마자 바로 CLOSED 상태가 되어버린다면 어떻게 될까?
    • 서버가 보낸 재전송 FIN을 받았을 때, 클라이언트는 이미 연결을 완전히 잊어버린 상태라 "너 누구야?"라며 오류(RST)를 보내게 된다.
    • 결국 서버는 정상적으로 연결을 닫지 못하고 '좀비 상태(LAST-ACK)'로 남게 된다. TIME-WAIT는 이를 방지하기 위해 서버의 마지막 인사를 끝까지 기다려주는 배려이다.
  1. 길 잃은 패킷(Stray Packets)의 소멸 대기
    • 네트워크 어딘가에서 지연되었다가 뒤늦게 도착한 패킷이 있을 수 있다.
    • 만약 클라이언트가 즉시 새 연결을 맺었는데, 예전 연결의 패킷이 뒤늦게 도착하면 데이터가 꼬일 수 있다.
    • 그래서 2MSL(Maximum Segment Lifetime의 2배)이라는 시간 동안 충분히 기다려, 네트워크상의 모든 패킷이 사라지게 만드는 것이다.

✨ TIME-WAIT 소진 문제

많은 연결을 빠르게 생성/종료하는 서버에서는 TIME-WAIT 상태의 소켓이 쌓여 포트 부족 현상이 발생할 수 있다.
이를 해결하는 방법은 크게 다음 3가지가 있다.

  1. SO_REUSEADDR (포트 재사용)

    "방이 아직 청소 중(TIME-WAIT)이라도, 바로 다시 사용할게요!"

    • 상세 설명: 소켓 옵션 중 하나로, 해당 포트가 TIME-WAIT 상태에 있더라도 새로운 소켓이 그 포트를 즉시 재바인딩(Bind)할 수 있게 허용한다.
    • 장점: 서버를 재시작할 때 "Address already in use" 에러를 피할 수 있고, 포트 효율성을 높인다.
    • 주의: 아주 드물게 이전 연결의 패킷이 새 연결에 영향을 줄 수 있는 위험이 있어 신중하게 사용해야 한다.
  1. 연결 풀 (Connection Pool)

    "매번 방을 만들고 부수지 말고, 미리 만들어둔 방을 돌려 써요!"

    • 상세 설명: 미리 일정 수의 연결을 맺어놓고(Pool), 필요할 때마다 꺼내 쓰고 다시 반납하는 방식이다.
    • 장점: 3-Way Handshake와 4-Way Handshake 과정 자체가 생략되므로 TIME-WAIT가 발생하지 않는다. CPU 자원도 아낄 수 있어 가장 권장되는 방식이다.
  1. Keep-Alive (연결 유지)

    "용건 끝났다고 바로 끊지 말고, 잠시만 수화기를 들고 있어 봐요!"

    • 상세 설명: 한 번 맺은 연결을 통해 여러 개의 데이터를 주고받는 방식이다. (예: HTTP/1.1의 Persistent Connection)
    • 장점: 연결을 끊는 횟수 자체를 획기적으로 줄여주기 때문에 자연스럽게 TIME-WAIT 발생 빈도가 낮아진다.


5. 연결 상태 다이어그램

✨ 클라이언트 상태 전이

클라이언트는 먼저 대화를 걸고(Active Open), 먼저 작별 인사를 건네는(Active Close) 주체이다.

  • SYN-SENT: "저기요, 대화 가능할까요?"라고 물어보고 답장을 기다리는 상태
  • ESTABLISHED: 서로 확인을 마치고 신나게 데이터를 주고받는 상태
  • FIN-WAIT-1/2: "전 이제 끝났어요"라고 말하고, 서버의 남은 이야기가 끝나길 기다리는 상태
  • TIME-WAIT: 작별 인사가 잘 전달되었는지 확인하기 위해 잠시 자리를 지키는 마지막 안전장치

✨ 서버 상태 전이

서버는 손님을 기다리고(Passive Open), 손님이 가겠다고 하면 남은 일을 마무리하는(Passive Close) 역할을 한다.

  • LISTEN: "언제든 환영입니다"라며 문을 열어두고 기다리는 상태
  • SYN-RECEIVED: 손님의 인사를 받고 "네, 저도 준비됐어요"라고 답한 뒤 확답을 기다리는 상태
  • CLOSE-WAIT: 손님이 가겠다고 했으니, 나도 보내줄 준비(남은 데이터 전송)를 하는 상태
  • LAST-ACK: "저도 이제 다 보냈어요. 진짜 안녕!"이라며 마지막 확인을 기다리는 상태

6. 예외 상황과 특수 케이스

✨ Simultaneous Open (동시 연결)

양쪽이 모두 클라이언트처럼 행동하는 상황이다. 3-Way가 아닌 4-Way 방식으로 연결된다.

✨ Simultaneous Close (동시 종료)

양쪽이 동시에 "이제 그만하자"고 말하는 경우이다. 이 경우 양쪽 모두 TIME-WAIT 상태를 거쳐 안전하게 종료된다.

✨ Half-Close (반만 닫기)

일반적인 close()는 송수신을 모두 끊지만, shutdown() 함수를 쓰면 내 데이터 전송만 끊고 상대방의 데이터는 계속 받을 수 있는 상태가 된다.

  • 사용 이유: 클라이언트가 서버에 요청 데이터를 다 보낸 뒤, 서버가 처리 결과를 다 보낼 때까지 연결을 유지해야 할 때 유용하다.
  • 상태: 클라이언트는 FIN을 보냈으므로 FIN-WAIT 상태가 되지만, 서버로부터 오는 데이터는 계속 수신한다.

✨ RST(Reset)를 통한 강제 종료

정상적인 4-Way Handshake를 거칠 여유가 없거나, 심각한 오류가 발생했을 때 사용하는 '비상 정지 버튼'이다.

  • 발생 상황
    • 닫혀 있는 포트로 연결을 시도할 때
    • 상대방 컴퓨터가 갑자기 꺼졌다가 다시 켜졌을 때 (연결 정보 상실)
    • 보안상의 이유연결을 즉시 차단해야 할 때
  • 특징: ACK 번호 확인 없이 즉시 종료되며, 버퍼에 남아있던 데이터는 모두 버려진다.

0개의 댓글