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단계: SYN (클라이언트 ➡️ 서버)
- 클라이언트의 제안: "나 너랑 연결하고 싶어!"라는 신호를 보낸다.
- ISN(x)의 의미: 처음 시작하는 번호(Initial Sequence Number)를 랜덤하게 생성한다. 랜덤인 이유는 이전 연결의 패킷과 혼동되지 않게 하고, 보안상 예측을 어렵게 하기 위해서이다.
- 상태 변화: 클라이언트는 서버의 대답을 기다리는 `YN-SENT 상태가 된다. (CLOSET ➡️ SYN-SENT)
- 2단계: SYN-ACK (서버 ➡️ 클라이언트)
- 서버의 응답과 제안: "응, 네 신호(x) 잘 받았어! 나도 너랑 연결하고 싶어!"라고 대답한다.
- 번호의 마법
ack=x+1: 클라이언트가 보낸 x를 잘 받았으니, 다음엔 x+1번을 보내달라는 뜻이다.
seq=y: 서버 자신도 대화를 위해 자신의 시작 번호 y를 새로 만든다.
- 상태 변화: 서버는 클라이언트의 최종 확답을 기다리는 SYN-RECEIVED 상태가 된다. (LISTEN ➡️ SYN-RECEIVED)
- 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단계: FIN (클라이언트 ➡️ 서버)
- 클라이언트의 선언: "내가 보낼 데이터는 이게 마지막이야!"라고 알린다. 하지만 서버가 보낼 데이터는 아직 남아있을 수 있다는 점이 중요하다. (ESTABLISH ➡️ FIN-WAIT-1)
- 2단계: ACK (서버 ➡️ 클라이언트)
- 서버의 응답: "알았어! 일단 네 종료 요청은 확인했어. 그런데 나 아직 너한테 줄 게 좀 남았으니까 조금만 기다려줘."라고 말한다.
- 상태의 의미: 서버는 CLOSE-WAIT 상태가 되어 남은 작업을 마무리하고(ESTABLISHED ➡️ CLOSE-WAIT), 클라이언트는 서버의 마지막 인사를 기다리는 FIN-WAIT-2 상태가 된다(FIN-WAIT-1 ➡️ FIN-WAIT-2).
- 3단계: FIN (서버 ➡️ 클라이언트)
- 서버의 종료 준비 완료: 남은 데이터를 다 보낸 서버가 "나도 이제 진짜 끝났어! 너도 준비됐지?"라며 종료 신호를 보낸다. (CLOSE-WAIT ➡️ LAST-ACK)
- 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이 필요한 이유
- 서버의 정상적인 종료 보장 (신뢰성)
- 만약 클라이언트가 마지막 ACK를 보내자마자 바로 CLOSED 상태가 되어버린다면 어떻게 될까?
- 서버가 보낸 재전송 FIN을 받았을 때, 클라이언트는 이미 연결을 완전히 잊어버린 상태라 "너 누구야?"라며 오류(RST)를 보내게 된다.
- 결국 서버는 정상적으로 연결을 닫지 못하고 '좀비 상태(LAST-ACK)'로 남게 된다. TIME-WAIT는 이를 방지하기 위해 서버의 마지막 인사를 끝까지 기다려주는 배려이다.
- 길 잃은 패킷(Stray Packets)의 소멸 대기
- 네트워크 어딘가에서 지연되었다가 뒤늦게 도착한 패킷이 있을 수 있다.
- 만약 클라이언트가 즉시 새 연결을 맺었는데, 예전 연결의 패킷이 뒤늦게 도착하면 데이터가 꼬일 수 있다.
- 그래서 2MSL(Maximum Segment Lifetime의 2배)이라는 시간 동안 충분히 기다려, 네트워크상의 모든 패킷이 사라지게 만드는 것이다.
✨ TIME-WAIT 소진 문제
많은 연결을 빠르게 생성/종료하는 서버에서는 TIME-WAIT 상태의 소켓이 쌓여 포트 부족 현상이 발생할 수 있다.
이를 해결하는 방법은 크게 다음 3가지가 있다.
-
SO_REUSEADDR (포트 재사용)
"방이 아직 청소 중(TIME-WAIT)이라도, 바로 다시 사용할게요!"
- 상세 설명: 소켓 옵션 중 하나로, 해당 포트가 TIME-WAIT 상태에 있더라도 새로운 소켓이 그 포트를 즉시 재바인딩(Bind)할 수 있게 허용한다.
- 장점: 서버를 재시작할 때 "Address already in use" 에러를 피할 수 있고, 포트 효율성을 높인다.
- 주의: 아주 드물게 이전 연결의 패킷이 새 연결에 영향을 줄 수 있는 위험이 있어 신중하게 사용해야 한다.
-
연결 풀 (Connection Pool)
"매번 방을 만들고 부수지 말고, 미리 만들어둔 방을 돌려 써요!"
- 상세 설명: 미리 일정 수의 연결을 맺어놓고(Pool), 필요할 때마다 꺼내 쓰고 다시 반납하는 방식이다.
- 장점: 3-Way Handshake와 4-Way Handshake 과정 자체가 생략되므로 TIME-WAIT가 발생하지 않는다. CPU 자원도 아낄 수 있어 가장 권장되는 방식이다.
-
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 번호 확인 없이 즉시 종료되며, 버퍼에 남아있던 데이터는 모두 버려진다.