본문 바로가기

카테고리 없음

TCP 3-Way Handshake 와 4-Way Handshake 에 대해 설명해주세요.

TCP는 신뢰할 수 있는 데이터 전송을 보장하기 위해 3-Way Handshake 절차로 연결을 설정하고 4-Way Handshake 절차로 연결을 종료합니다.

 

3-Way Handshake 과정을 설명해주세요

1. 클라이언트 → 서버: SYN 전송

클라이언트는 서버에 연결 요청을 보내며, 이 요청(SYN 세그먼트)에는 초기 순서 번호(Sequence Number, ISN)와 윈도우 크기(Window Size) 정보가 포함됩니다.

2. 서버 → 클라이언트: SYN + ACK 전송

서버는 요청을 수락하며, 자신의 초기 순서 번호와 클라이언트의 순서 번호에 대한 응답(ACK=클라이언트 ISN + 1)을 포함한 SYN + ACK 세그먼트를 클라이언트로 보냅니다.

3. 클라이언트 → 서버: ACK 전송

클라이언트는 서버의 응답을 확인하고, 서버의 초기 순서 번호에 대한 응답(ACK=서버 ISN + 1)을 포함한 ACK 세그먼트를 서버로 전송합니다. 이 절차가 완료되면 클라이언트와 서버 간에 신뢰할 수 있는 연결이 설정되고, 데이터 전송이 시작될 수 있습니다.

💡왜 2-Way Handshake가 아니라 3-Way Handshake 일까요?
TCP 연결은 양방향성(bidirectional) 연결입니다. 클라이언트가 서버에게 자신의 존재와 데이터를 보낼 수 있다는 것을 알리는 것처럼, 서버도 클라이언트에게 자신의 존재와 데이터를 보낼 수 있다는 신호를 보내야 합니다. 이러한 이유로 2-Way Handshake 로는 부족하고, 3-Way Handshake 가 필요합니다.
💡SYN(synchronize sequence numbers) 패킷과 ACK(acknowledgement) 패킷?
TCP Header에는 **Code Bit(Flag bit)**라는 부분이 존재한다. 이 부분은 총 6Bit로 이루어져 있이며 각각 한 bit들이 의미를 갖고 있다. Urg-Ack-Psh-Rst-Syn-Fin 순서로 되어 있으며 해당 위치의 비트가 1이면 해당 패킷이 어떠한 내용을 담고 있는 패킷인지를 나타낸다. SYN 패킷일 경우엔 000010이 되고 ACK 패킷일 경우에는 010000이 되는 것이다.
💡ISN을 0부터 시작하지 않고 난수를 생성해서 설정하는 이유?
컴퓨터 네트워크에서는 연결을 설정할 때 포트 번호를 사용합니다. 그런데 포트 번호는 한정된 범위 내에서 사용되기 때문에 시간이 지나면 이전에 사용한 포트 번호를 다시 사용할 수 있습니다. 예를 들어, 두 컴퓨터 간에 연결이 끝난 후, 같은 포트 번호가 다시 사용될 수 있는 상황이 발생할 수 있습니다.
그런데 연결을 시작할 때 클라이언트가 보내는 SYN 패킷에 포함된 **ISN(초기 시퀀스 번호)**도 중요한 역할을 합니다. 만약 이전에 사용했던 포트 번호와 같은 번호를 사용하고, 그때와 동일하게 ISN이 0부터 시작한다면, 서버는 "아, 예전에 했던 연결이 또 온 건가?"라고 혼동할 수 있습니다. 이럴 경우, 서버가 예전의 연결 데이터를 처리하거나 새로운 연결을 제대로 인식하지 못할 수 있습니다.
따라서 ISN을 난수로 설정하면, 이전에 사용했던 번호와는 완전히 다른 숫자가 생성되어, 서버가 예전 연결과 새로운 연결을 헷갈리지 않게 됩니다. 이렇게 난수를 사용함으로써 이전 연결과의 혼동을 방지하고, 네트워크 통신이 정확하게 이루어지도록 합니다.

 

4-Way Handshake 과정

1. 클라이언트 → 서버: FIN 전송

클라이언트는 더 이상 데이터를 보내지 않겠다는 의사를 표시하며 FIN 플래그가 설정된 세그먼트를 서버로 전송합니다.

2. 서버 → 클라이언트: ACK 전송

서버는 클라이언트의 FIN 요청을 확인하고, ACK 세그먼트를 반환합니다. 이후 서버는 TIME_WAIT 상태가 되며, 데이터를 마저 전송하며 연결 종료 준비를 합니다.

3. 서버 → 클라이언트: FIN 전송

서버가 데이터 전송을 모두 완료하면, 연결을 종료하겠다는 FIN 플래그를 클라이언트로 보냅니다.

4. 클라이언트 → 서버: ACK 전송

클라이언트는 서버의 FIN 요청을 확인한 뒤 ACK 세그먼트를 서버로 전송합니다. 클라이언트는 이후 일정 시간 동안 TIME_WAIT 상태에 들어가 재전송 가능성이 있는 패킷을 처리한 뒤 연결을 완전히 종료합니다.

💡왜 서버는 바로 FIN 패킷을 보내지 않고, ACK를 먼저 보낼까요?
만약 서버가 ACK 패킷을 먼저 보내지 않고, 모든 프로세스를 마친 후에 FIN 패킷만 바로 보내면, 클라이언트는 FIN 패킷이 유실되었을 것으로 오해하고 계속 FIN 패킷을 전송할 수 있습니다. 이를 방지하기 위해 서버는 ACK 패킷을 먼저 보내고, 이후 연결 종료를 위한 FIN 패킷을 전송합니다.
💡만약 서버에서 FIN을 전송하기 전에 이미 전송한 패킷이 라우팅 지연이나 패킷 유실 등으로 인해 FIN 패킷보다 늦게 도착하는 상황이 발생한다면, 어떻게 될까요?
클라이언트가 FIN을 수신하여 세션을 종료시킨 후에 뒤늦게 도착하는 패킷이 있다면 이 패킷은 드롭되고 데이터가 유실될 수 있기 때문에, 이를 방지하기 위해 클라이언트는 서버로부터 FIN을 수신하더라도 일정시간(디폴트 240초) 동안 세션을 남겨놓고 잉여 패킷을 기다리는 "TIME_WAIT" 과정을 거칩니다.
💡TCP 연결은 언제 유지되며, 언제 종료되나요?
HTTP/1.0에서는 Keep-Alive가 기본적으로 비활성화 되어 있으며, 연결을 재사용하려면 헤더에 Connection: Keep-Alive를 명시적으로 설정해야 합니다. 그렇지 않으면 요청이 완료되면 연결이 닫힙니다.
HTTP/1.1에서는 Keep-Alive가 기본적으로 활성화되어 있어, 서버가 요청을 처리한 후에도 TCP 연결을 닫지 않고 유지합니다. 만약 연결을 명시적으로 종료하고 싶다면 Connection: close 헤더를 추가해야 합니다.
HTTP/2 및 HTTP/3에서는 Keep-Alive 개념이 더 이상 필요하지 않습니다. 여러 요청과 응답을 단일 TCP 연결에서 멀티플렉싱하여 처리할 수 있기 때문입니다.

TCP 연결 종료
정상 종료일 경우, 운영체제가 열려 있는 TCP 소켓을 닫는 명령을 정상적으로 처리하면, 4-way handshake가 수행됩니다.브라우저가 강제로 닫히거나, 프로세스가 비정상적으로 종료되면 TCP 소켓을 닫을 기회 없이 연결이 중단됩니다. 운영체제는 소켓을 즉시 RST(Reset) 패킷으로 닫을 수 있으며, 서버는 이를 비정상 연결 종료로 인식합니다.
TCP 연결 종료 예시
1. 클라이언트가 강제로 연결 종료(예: 브라우저를 닫거나, 클라이언트 코드에서 연결을 명시적으로 닫음).
2. 서버 타임아웃 (서버가 Keep-Alive 타임아웃을 초과한 비활성 연결을 닫음)
- Spring Boot에서 기본 타임아웃은 톰캣 설정을 통해 제어 가능
:server: http2: enabled: true tomcat: keep-alive-timeout: 30000 # 30초
3.네트워크 오류 (패킷 손실, 라우팅 문제, 클라이언트-서버 간 네트워크 단절)
4.서버나 클라이언트 재시작 (서버 또는 클라이언트가 재시작되면 기존 연결이 끊어집니다.)

 

TCP 3-Way Handshake & 4-Way Handshake는 언제 사용하나요?

TCP는 연결 지향형 프로토콜로 3-Way Handshake와 4-Way Handshake 과정을 거치며, 데이터의 신뢰성이 중요한 경우에 사용됩니다. 이는 데이터가 정확하게 전달되고 오류 발생 시 재전송이 필요할 때 적합합니다.

하지만 실시간 속도가 중요한 서비스에서는 TCP의 신뢰성보다 빠른 전송 속도가 더 중요할 수 있습니다. 이때 UDP가 유리합니다. UDP는 비연결 지향형 프로토콜로, Handshake 과정 없이 데이터를 빠르게 전송할 수 있습니다. 따라서 스트리밍 서비스나 게임처럼 빠르고 끊기지 않는 전송이 중요한 경우에는 UDP가 적합한 선택이 될 수 있습니다.

 

TCP 3-Way Handshake 과정 중 발생할 수 있는 문제와 해결방안은 무엇이 있을까요?

3-Way Handshake 과정에서 서버는 클라이언트의 첫 번째 요청(SYN)을 받으면, 이를 처리하기 위해 일정한 메모리를 할당하고 큐에 저장합니다. 그러나 좀비PC들이 대규모로 SYN 메시지만 보내고 서버가 SYN+ACK를 응답한 후, 마지막 ACK 메시지를 보내지 않으면 서버는 계속해서 응답을 기다립니다. 이로 인해 서버는 할당된 메모리로 자원을 소모하게 되어, 결국 정상적인 사용자에게 서비스를 제공할 수 없게 됩니다. 이러한 공격은 SYN Flooding이라 불리며, DDoS(분산 서비스 거부 공격)의 일종입니다.

이 문제를 해결하기 위한 방법은 여러 가지가 있습니다. 첫 번째는 백로그 큐 늘리기입니다. 이는 메모리를 확장하여 대기할 수 있는 연결 수를 늘리는 방법이지만, 공격량이 계속 증가하면 여전히 서버가 다운될 수 있으므로 근본적인 해결책은 아닙니다.

두 번째는 Anti-DDoS 기능 사용하기로, 예를 들어 특정 IP에서 갑자기 SYN 메시지가 일정 수 이상 온다면 접속을 차단하는 방식입니다. 정상적인 사용자가 갑자기 많은 SYN을 보내는 일이 드물기 때문에 정상 사용자에게는 영향을 주지 않으며, 임계치 기반 방어로 공격을 제한할 수 있습니다.

세 번째는 SYN Cookie 사용하기입니다. 이 방법은 서버가 SYN 패킷을 백로그 큐에 저장하지 않고, 클라이언트의 정보를 바탕으로 SYN Cookie를 생성하는 방식입니다. 생성된 SYN Cookie는 서버의 ISN으로 사용되어 SYN+ACK 메시지로 클라이언트에게 전송됩니다. 클라이언트가 정상적으로 ACK를 보내면, 서버는 ACK에서 받은 Sequence Number를 확인하여 생성한 SYN Cookie와 일치하는지 검증합니다. 이를 통해 서버는 메모리를 할당하지 않고도 연결을 수립할 수 있습니다.

 

참고