본문 바로가기

네트워크

[네트워크] 3-way handshake, 4-way handshake

 

3-Way Handshake(3방향 핸드셰이크)와 4-Way Handshake(4방향 핸드셰이크)는 네트워크 통신을 하기 위한 중요한 절차이며, 각각 TCP 연결 설정과 종료 과정에서 사용된다.

 

3-Way Handshake (TCP 연결 설정)

TCP는 애플리케이션 프로세스 간의 데이터 전송 전에 논리적인 접속 성립(establish)하기 위해 3-way handshake 과정을 거친다.

  • TCP / IP 프로토콜을 이용해서 통신하는 응용 프로그램이 데이터를 전송하기 전에 정확한(신뢰할 수 있는) 전송을 보장하기 위해 상대방 컴퓨터와 사전에 세션을 수립하는 과정
  • 신뢰성을 위해 3번의 핸드쉐이킹 과정을 거쳐 연결을 맺는다.
  1. Client → Server : TCP SYN - 클라이언트가 서버에 연결 요청 (SYN 패킷 전송)
  2. Server → Client : TCP SYN ACK - 서버가 요청을 받고 응답 (SYN-ACK 패킷 전송)
  3. Client → Server : TCP ACK - 클라이언트가 응답을 확인하고 연결 완료 (ACK 패킷 전송)

 

📌 흐름 예시

1. 클라이언트 → 서버 : SYN (시퀀스 번호 x)
2. 서버 → 클라이언트 : SYN-ACK (시퀀스 번호 y, ACK 번호 x+1)
3. 클라이언트 → 서버 : ACK (ACK 번호 y+1)

 

이후 TCP 연결이 설정되며 데이터 전송 가능

 

3-way handshake 에서 사용되는 TCP 헤더 필드

1. Sequence Number

  • TCP 세그먼트의 연속된 데이터 번호(Sequence Number)
    • TCP 는 데이터를 연속된 바이트 스트림(Continuous Byte Strem) 으로 다룬다.
    • 이 때문에 패킷 단위로 데이터를 보내지만, 실제로는 각 바이트마다 고유한 번호(Sequence Number)가 부여된다.
    • ex) 0~999, 1000~1999 Segment 보낼 seq# = 각각 0, 1000
      • TCP는 한 번에 일정 크기의 데이터를 보내지 못하므로, 세그먼트(segment) 단위로 나누어 보내는데,
        이때 각 세그먼트의 첫 번째 바이트 번호가 SEQ# 이 된다.
  • ISN (Initial Sequence Number)
    • 처음에 클라이언트가 서버로 보내는 SYN(Sequence Number)로, 랜덤한 난수로 지정된 시퀀스 번호

 

바이트 스트림 (byte stream)

네크워크 또는 파일 입출력에서 데이터를 바이트 단위(8bit)로 연속적으로 전송하는 방식

특징

1. 연속적인 데이터 흐름
  - TCP 같은 프로토콜에서 데이터를 패킷이 아닌 순차적인 바이트 흐름으로 처리
  - 파일 입출력, 소켓 통신, 직렬화(Serialization) 등에 사용
2. 구조 없이 전송
  - 데이터의 시작과 끝을 구분하지 않고, 단순히 나열된 바이트 형태로 전달됨.
  - 수신 측에서는 데이터를 해석할 **명확한 경계(Delimiter)**를 정해줘야 한다.
3. 순서 보장
  - TCP 같은 프로토콜은 바이트의 순서를 보장 하며, 손실 시 재전송을 통해 복구
ISN을 0부터 시작하지 않고 난수로 보내는 이유

connection을 맺을 때 사용하는 포트는 유한 범위 내에서 사용하고, 시간이 지남에 따라 재사용된다.
이 때문에 두 통신 호스트가 과거에 사용된 포트 번호 쌍을 재사용 하는 가능성이 존재한다.

Sever 측에서는 패킷의 SYN을 보고 패킷을 구분하게 되는데, 난수가 아닌 순차적 number가 전송되면 이 전의 connection으로 오는 패킷으로 인식할 수 있다. (문제)

또한 순차적으로 사용하여 Sequence Number가 노출되면 공격자가 위조 패킷을 보낼 수 있다. (보안 문제)
이러한 문제들이 발생할 가능성을 줄이기 위해 랜덤 값으로 설정

 

한 줄 정리 : "tcp에서는 데이터를 전송할 때 세그먼트로 전체 데이터를 분할해서 보내는데 이때 각 바이트마다 고유한 번호가 붙고 그거를 시퀀스 넘버(SEQ#)라고 한다. 그리고 이때 고유한 번호가 붙을 때 가장 처음 번호는 랜덤으로 설정된다."

 

2. Acknowledgement Number

수신 측이 다음에 받을 것으로 기대하는 TCP 세그먼트 데이터 번호
송신자가 보낸 마지막 바이트의 다음 바이트 번호

  1. 송신자가 데이터를 보냄 → SEQ# = N, 데이터 크기 = M 바이트
  2. 수신자가 데이터를 정상적으로 받음
  3. 수신자는 받은 데이터의 마지막 바이트 다음 번호를 ACK#으로 설정하여 응답
    → ACK# = N + M

 

 

3. Control Bits (Flag)

  • SYN (Synchronize Sequence Number) : 연결을 요청할 때 SYN bit를 사용한다.
  • 연결을 요청하는 경우에는 SYN bit를 1로 설정하고, 다른 모든 경우에는 SYN bit를 0으로 설정한다.

송신측에서 SYN 패킷을 보낼 때 랜덤한 SEQ#과 SYN 플래그(SYN=1) 를 설정하면, 수신 측은 이 패킷이 연결 요청(SYN 패킷)임을 알 수 있다.

  • ACK (Acknowledgement) : 패킷을 받았다는 것을 의미하는 flag
  • Acknowledgement Number 필드가 유효한지를 나타낸다.
  • 최초 연결 과정(client → server : SYN)에서 전송되는 세그먼트를 제외한 모든 세그먼트의 ACK 비트는 1로 설정
    • 최초 연결의 첫 번째 Hankshake 과정에서는 응답할 요청이 없기 때문 - SYN 만
    • TCP 3-Way Handshake 과정에서 최초 SYN 패킷을 제외한 모든 패킷은 ACK 비트가 1
    • 3-way handshake로 연결이 된 이후에도 송신한 데이터를 수신자가 제대로 받았는지 매번 확인해야 하므로 모든 데이터 패킷에는 ACK 비트가 1로 설정

 

 

3-way Handshake 과정

 

State (Port 상태 정보)

  • CLOSED : 포트가 닫힌 상태
  • LISTEN : 포트가 열린 상태로 연결 요청을 대기하는 상태
  • SYN-SENT : 연결 요청을 하고 Server의 ACK를 기다리는 상태
  • SYN-RECV : 연결 요청에 대한 응답 / 연결을 요청하고 Client의 응답을 기다리는 상태
  • ESTABLISHED : 포트가 연결된 상태

 

동작 방식

[Step 1] : Client → SYN → Server

  1. 클라이언트가 서버에게 접속을 요청하는 SYN Segment를 전송
    • Segment Header의 SYN bit를 1로 설정
    • Sequence Number는 클라이언트의 최초 순서 번호(client_isn / 난수)로 설정함
      • 클라이언트는 syn=1, 데이터 조각(세그머트)의 첫 번째 난수로 생성된 번호만 전송
    • SYN Segment 전송한  SYN-SENT 상태로 바뀌고, 서버의 ACK Segment 기다림
  2. 서버는 클라이언트로부터 SYN Segment 를 수신

 

[Step 2]: Server → SYN + ACK → Client

  1. 서버는 Listen 상태에서 클라이언트의 SYN Segment에 대한 ACK Segment를 전송
    • Acknowledge Number는 client_isn + 1로 설정
  2. 동시에 서버가 클라이언트에게 연결을 요청하는 SYN Segment를 전송
    • SYN bit를 1로 설정
    • Sequence Number는 서버의 최초 순서 번호(server_isn)로 설정
  3. SYN + ACK Segment를 클라이언트에게 전송한 후, SYN-RECV 상태로 바뀌고 클라이언트의 ACK를 기다림
  4. 클라이언트는 ACK Segment을 받고 연결이 완료된 ESTABLISHED 상태가 됨

-- 여기까지 하고 established 상태가 되면 2-way handshake

2-way handshake의 문제점

서버와 클라이언트가 서로의 상황을 모르기 때문에 문제가 발생할 수 있다.

 

[Step 3]: Client → ACK → Server

  1. 클라이언트는 서버의 SYN + ACK Segment에 대한 ACK Segment를 전송
    • 연결 요청이 아니기 때문에 SYN bit를 0으로 설정
    • Acknowledge Number는 server_isn + 1로 설정
  2. 서버는 ACK Segment를 받고 연결이 완료된 ESTABLISHED 상태가 됨

 

[Step 4]: Client → Data → Server

  1. 클라이언트와 서버가 데이터를 주고 받음 (ack flag 는 항상 1)

 

 

4-Way Handshake (TCP 연결 종료)

TCP 연결을 신뢰성 있게 종료하는 과정
TCP 프로토콜이 3-way handshake를 통해 연결을 수립했다면 4-way handshake를 통해 연결을 해제해야 한다.

왜 4-Way Handshake에서 신뢰성 있는 연결 종료가 필요한가?

TCP는 양방향(full-duplex) 통신을 지원하므로, 데이터 송·수신을 각각 안전하게 종료해야 한다. 따라서 4-Way Handshake를 사용하여 각 방향의 데이터 전송이 완전히 끝났음을 보장해야 한다.


📌 신뢰성 있는 종료가 필요한 이유

1. 데이터 손실 방지
  - 송신자가 FIN을 보낸 후, 수신자가 아직 처리하지 못한 데이터가 있을 수도 있음.
  - FIN → ACK을 주고받으며 모든 데이터가 정상적으로 도착했음을 확인하고 종료.
2. Half-Close 지원
  - 한쪽에서 FIN을 보내더라도, 상대방이 데이터를 더 보낼 수도 있음.
  - 이를 위해 각 방향별로 독립적으로 FIN과 ACK을 주고받으며 종료.
3. Half-Open 연결 방지
  - 일방적으로 종료하면 상대방은 연결이 끊겼는지 모름. FIN과 ACK을 주고받아 서로 종료를 확실히 인지하도록 함.



📌 결론

TCP 연결 종료는 단순히 연결을 끊는 것이 아니라, 모든 데이터가 정상적으로 송수신되었음을 확인하고 종료하는 과정이 필요하다. 따라서 4-Way Handshake를 통해 신뢰성 있는 종료를 보장한다.

 

  1. FIN: 클라이언트가 서버에 연결 종료 요청 (FIN 패킷 전송)
  2. ACK: 서버가 요청을 확인하고 응답 (ACK 패킷 전송)
  3. FIN: 서버도 종료를 요청 (FIN 패킷 전송)
  4. ACK: 클라이언트가 응답하고 연결 종료 (ACK 패킷 전송)

 

📌 흐름 예시

1. 클라이언트 → 서버 : FIN (연결 종료 요청)
2. 서버 → 클라이언트 : ACK (요청 확인)
3. 서버 → 클라이언트 : FIN (서버도 종료 요청)
4. 클라이언트 → 서버 : ACK (서버 요청 확인 후 종료)

✅ 이후 TCP 연결이 완전히 종료됨.

 

 

4-way handshake 과정

 

[Step1]

  • 클라이언트가 연결 해제 요청 FIN을 서버로 전송한다.
  • 클라이언트 상태: ESTABLISHED -> FIN_WAIT_1

 

[Step2]

  • 서버는 연결 해제 요청을 승인한다는 의미로 클라이언트에 ACK을 전송한다.
  • 연결 해제 요청에 대한 승인으로 → 바로 해제 되는 것이 아닌, CLOSE_WAIT 상태로 진입한 후 연결 해제 준비를 한다.
  • 서버 상태: ESTABLISHED -> CLOSE_WAIT : 클라이언트가 요청을 끊고 싶다는 요청만 받은 상태
  • 클라이언트 상태: FIN_WAIT_1 -> FIN_WAIT_2 : 클라이언트의 연결은 끊기고 서버의 연결 종료를 기다리는 상태

Close-Wait 상태 후 애플리케이션이 종료됬다면 다음 단계로 넘어간다.

 

[Step3]

  • 서버는 연결 해제 준비가 완료되었다는 FIN을 클라이언트로 전송한다.
  • 서버 상태: CLOSE_WAIT -> LAST_ACK
  • 클라이언트 상태: FIN_WAIT_2 -> TIME_WAIT

 

[Step4]

  • 클라이언트는 TIME_WAIT 시간 만큼 대기 후 서버로 ACK을 전송함으로써 마침내 연결이 해제된다.대개의 경우 2MSL(maximum segment lifetime - 1분~4분) 동안 TIME_WAIT 상태로 대기한다.
  • 클라이언트는 연결을 해제하겠다는 ACK을 바로 서버로 보내는 대신 2MSL 동안 TIMW_WAIT 상태로 대기함으로써 서버는 아직 도착하지 못한 패킷들을 받을 수 있는 여유 시간이 생긴다.
  • 클라이언트 상태: TIME_WAIT -> CLOSED
  • 서버 상태: LAST_ACK -> CLOSED

 

연결을 종료할때 4-way-handshake를 해야하는 이유

클라이언트의 데이터 전송이 끝났더라도, 서버에서는 데이터를 전송하고 있을수 있기 때문이다. 클라이언트의 데이터 전송 종료(Step1)에 해당하는 FIN의 응답으로 서버에서 ACK를 보내지만, 서버에서 클라이언트로 보내는 데이터 전송이 끝나면 전송의 종료에 대한 FIN을 보내게된다.

 

TIME - WAIT

[Step4]에서 서버로부터 ACK를 받은후 바로 Closed가 아닌 Time-Wait하는 이유가 무엇일까?

  1. 지연 패킷 발생
    • 지연으로 인해 FIN 보다 늦게 도착하는 데이터가 있을 수 있기 때문에 유실을 방지하고자 TIME-WAIT을 둔다.
    • 지연 패킷을 예상 못하고 또 다른 연결을 진행했다면 해당 패킷으로 에러가 갈 수 있다.

  2. 원격 중단의 연결 확인 불가 - TCP 연결 종료 과정에서 마지막 ACK가 유실되면 발생하는 문제

1) 클라이언트가 마지막 ACK을 보내는데, 이게 손실됨

  • 서버는 LAST_ACK 상태에서 "마지막 ACK이 올 때까지 기다림"
  • 하지만 ACK이 유실되면 서버는 연결이 종료되지 않고 계속 기다리는 상태가 됨.

2) 클라이언트가 새로운 연결을 시도함

  • 클라이언트는 TIME_WAIT 후 새로운 연결을 시작하려고 SYN을 보냄
  • 그런데 서버는 여전히 LAST_ACK 상태라서 "이전 연결이 끝나지 않았다"고 인식

3) 서버가 RST(Reset) 패킷을 보내면서 연결을 거부함

  • 서버는 "이전 연결이 아직 종료되지 않았는데 새로운 연결을 시도하네?"라고 생각함
  • 그래서 새로운 SYN 요청에 강제로 연결을 끊는 RST(Reset) 패킷을 보냄
    • RST (Reset the connection) : 커넥션을 강제로 끊는 플래그
  • 결과적으로 새로운 연결이 실패함
만약 TCP 커넥션을 4-way Handshake를 할 여유 없이 빨리 종료해야 하는 경우 RST 플래그를 1로 설정하면 된다.
RST 플래그가 사용되는 경우는 아래와 같다.

  - 존재하지 않는 TCP 연결에 대해 비SYN 세그먼트가 수신된 경우
  - 열린 커넥션에서 일부 TCP 구현은 잘못된 헤더가 있는 세그먼트가 수신된 경우
  - 일부 구현에서 기존 TCP 연결을 종료해야 하는 경우
  - 원격 호스트에 연결할 수 없고 응답이 중지된 경우

이렇게 갑작스럽게 연결이 해제된 것을 Abrupt connection release 라고 부르고, 양쪽 커넥션이 서로 커넥션을 닫을 때까지 연결되어 있는 정상적인 종료를 Graceful connection release이라고 부른다.

 

Half - Close

한쪽에서 데이터 전송을 끝냈다는 신호(FIN)를 보내면서도, 반대쪽에서는 계속 데이터를 받을 수 있도록 하는 방법

 

Half-Close가 없으면 어떤 문제가 생길까?

❌ 1. TCP 연결을 계속 열어둔다면?

  • 데이터를 더 보낼 필요 없는데도 소켓이 계속 열려 있어서 블로킹 상태가 발생 → 에러 발생

❌ 2. 임의의 시점에 소켓을 닫아버린다면?

  • 상대방이 아직 데이터를 보내고 있는데 갑자기 연결을 끊으면 데이터가 유실됨 → 에러 발생

➡ 그래서 Half-Close를 사용하면?

  • 데이터를 더 보낼 필요 없을 때 출력 스트림(보내는 쪽)만 먼저 끊고,
  • 상대방이 보낸 데이터를 끝까지 받을 수 있도록 입력 스트림(받는 쪽)은 유지

 

스트림과 Half-Close

✔ TCP 연결이 형성되면 "두 개의 스트림"이 생김

  • 하나는 내가 데이터를 보내는 스트림 (출력 스트림)
  • 하나는 상대방이 데이터를 보내는 스트림 (입력 스트림)

✔ Half-Close란?

  • 내가 데이터를 보낼 필요 없을 때, 내 출력 스트림만 먼저 끊는 것
  • 하지만 상대방의 데이터를 받을 수 있도록 입력 스트림은 유지

정리 : Half-Close는 데이터를 더 보낼 필요 없을 때 내 송신을 종료(FIN)하지만, 상대방이 보내는 데이터를 끝까지 받을 수 있도록 하는 방식이다. 

 

굳이 Half-Close를 써야하는가?

무조건적인 방법은 아니지만, 안전한 종료를 위해 필요하다.
마지막 송신에 마지막을 의미하는 특정 데이터를 보낼수도 있지만, 스트림을 통해 데이터를 주고받는 과정에서에 바이너리 데이터에 시그널 데이터가 포함되어 문제가 발생할수 있기때문에 Half-Close가 권장된다.

 

 

reference

https://github.com/jmxx219/CS-Study/blob/main/network/3-way%20handshake.md

 

CS-Study/network/3-way handshake.md at main · jmxx219/CS-Study

Computer Science && Tech Interview . Contribute to jmxx219/CS-Study development by creating an account on GitHub.

github.com

https://github.com/jmxx219/CS-Study/blob/main/network/4-way%20handshake.md

 

CS-Study/network/4-way handshake.md at main · jmxx219/CS-Study

Computer Science && Tech Interview . Contribute to jmxx219/CS-Study development by creating an account on GitHub.

github.com