본문 바로가기
TYLER SONGBlog
← Blog index
CS6 min read

TCP 3-way Handshake: 시퀀스 번호를 동기화하는 연결 수립 절차

TCP 3-way Handshake는 클라이언트와 서버가 세 개의 세그먼트(SYN, SYN-ACK, ACK)를 교환해 연결을 수립하는 절차다. 양방향 통신 가능 여부를 확인하고 초기 시퀀스 번호(Initial Sequence Number)를 동기화한다. 연결 수립에는 일반적으로 한 번의 왕복 시간(RTT)이 필요하다.

By 송민성

1. 개념

TCP 3-way Handshake는 전송 제어 프로토콜(TCP) 연결을 시작할 때 통신 양쪽이 연결 상태와 초기 시퀀스 번호(Initial Sequence Number, ISN)를 합의하는 과정이다.

세 단계에서 사용하는 제어 플래그(Control Flag)는 다음과 같다.

  1. 클라이언트 → 서버: SYN
  2. 서버 → 클라이언트: SYN + ACK
  3. 클라이언트 → 서버: ACK

여기서 동기화(Synchronize)를 뜻하는 SYN은 연결 시작과 시퀀스 번호 동기화를 요청한다. 확인 응답(Acknowledgment)을 뜻하는 ACK는 상대가 보낸 세그먼트를 정상적으로 받았음을 나타낸다.

2. 왜 사용하는가

TCP는 연결 지향형(Connection-oriented) 프로토콜이다. 데이터를 보내기 전에 양쪽이 통신 준비 상태인지 확인하고, 이후 데이터의 순서와 손실을 관리할 기준을 정해야 한다.

3-way Handshake는 다음 조건을 확인한다.

  • 클라이언트가 서버로 데이터를 보낼 수 있다.
  • 서버가 클라이언트로 데이터를 보낼 수 있다.
  • 양쪽이 상대의 초기 시퀀스 번호를 알고 있다.
  • 양쪽이 동일한 연결을 식별할 상태 정보를 생성했다.

두 단계만 사용하면 서버는 자신의 응답이 클라이언트에 도착했는지 확인할 수 없다. 세 번째 ACK가 도착해야 서버도 양방향 통신 경로가 준비됐다고 판단할 수 있다.

3. 동작 원리

클라이언트의 초기 시퀀스 번호를 x, 서버의 초기 시퀀스 번호를 y라고 가정한다.

1단계: SYN

클라이언트가 서버에 연결을 요청한다.

text
클라이언트 → 서버
SYN = 1
SEQ = x

클라이언트 상태는 CLOSED에서 SYN-SENT로 바뀐다. 서버는 연결 요청을 기다리는 LISTEN 상태다.

2단계: SYN-ACK

서버가 요청을 받고 자신의 초기 시퀀스 번호를 전달하면서 클라이언트의 SYN을 확인한다.

text
서버 → 클라이언트
SYN = 1
ACK = 1
SEQ = y
ACK 번호 = x + 1

서버 상태는 LISTEN에서 SYN-RECEIVED로 바뀐다.

SYN은 시퀀스 번호 공간을 1만큼 소비한다. 따라서 서버는 다음에 받을 번호로 x + 1을 응답한다.

3단계: ACK

클라이언트가 서버의 SYN을 확인한다.

text
클라이언트 → 서버
ACK = 1
SEQ = x + 1
ACK 번호 = y + 1

클라이언트는 ESTABLISHED 상태가 된다. 서버도 이 ACK를 받으면 ESTABLISHED 상태가 된다.

text
클라이언트                                 서버
CLOSED                                    LISTEN
   |                                        |
   | --- SYN, SEQ=x ----------------------> |
   |                                        | SYN-RECEIVED
   | <--- SYN-ACK, SEQ=y, ACK=x+1 --------- |
   | ESTABLISHED                            |
   | --- ACK, SEQ=x+1, ACK=y+1 ----------> |
   |                                        | ESTABLISHED

연결은 출발지 IP 주소, 출발지 포트, 목적지 IP 주소, 목적지 포트로 구성된 4-튜플(4-tuple)로 식별한다.

4. 코드 예제

Node.js의 net 모듈은 운영체제의 TCP 구현을 사용한다. 애플리케이션 코드에서 SYN을 직접 만들지는 않지만, connect()가 호출되면 운영체제가 3-way Handshake를 수행한다.

다음 파일을 tcp-demo.ts로 저장한다.

typescript
import { createServer, createConnection } from "node:net"; const host = "127.0.0.1"; const port = 8080; const server = createServer((socket) => { console.log( `서버: 연결 수립 완료 ${socket.remoteAddress}:${socket.remotePort}`, ); socket.on("data", (data) => { console.log(`서버: ${data.toString()}`); socket.end("응답 완료"); }); }); server.listen(port, host, () => { console.log(`서버: ${host}:${port}에서 대기 중`); const client = createConnection({ host, port }, () => { console.log("클라이언트: 연결 수립 완료"); client.write("요청 전송"); }); client.on("data", (data) => { console.log(`클라이언트: ${data.toString()}`); }); client.on("end", () => { server.close(); }); client.on("error", (error) => { console.error(`클라이언트 오류: ${error.message}`); server.close(); }); }); server.on("error", (error) => { console.error(`서버 오류: ${error.message}`); });

tsx가 설치된 환경에서 실행한다.

shell
npx tsx tcp-demo.ts

로컬 루프백 인터페이스에서 패킷을 관찰하면 SYN, SYN-ACK, ACK 순서를 확인할 수 있다. macOS에서는 다음 명령을 별도 터미널에서 실행한다.

shell
sudo tcpdump -i lo0 -nn "tcp port 8080"

리눅스에서는 인터페이스 이름으로 lo를 사용한다.

shell
sudo tcpdump -i lo -nn "tcp port 8080"

5. 시간 복잡도 또는 성능 특성

3-way Handshake는 입력 크기에 따라 반복되는 알고리즘이 아니므로 시간 복잡도를 빅오(Big-O)로 표현하는 것은 실용적이지 않다. 교환하는 TCP 세그먼트 수는 고정된 3개다.

애플리케이션이 연결 완료 후 데이터를 보내는 일반적인 경우, 연결 수립 지연은 약 한 번의 왕복 시간(Round-Trip Time, RTT)이다.

text
연결 수립 지연 ≈ 1 RTT

패킷 손실이 발생하면 TCP 재전송 타이머에 따라 SYN 또는 SYN-ACK를 다시 전송하므로 지연이 증가한다.

연결마다 운영체제는 송수신 버퍼, 시퀀스 번호, 재전송 상태 같은 정보를 관리한다. 짧은 요청마다 새 TCP 연결을 만들면 Handshake 지연과 연결 상태 관리 비용이 반복된다.

6. 실무 사용 사례

HTTP 연결

HTTP/1.1과 HTTP/2가 TCP를 사용할 때 HTTP 요청 전에 TCP 연결이 필요하다. 지속 연결(Persistent Connection)과 연결 풀(Connection Pool)을 사용하면 여러 요청이 하나의 TCP 연결을 재사용해 Handshake 횟수를 줄일 수 있다.

HTTPS 연결

HTTPS에서는 TCP 3-way Handshake 뒤에 전송 계층 보안(TLS) Handshake가 이어진다. 새 연결이 많거나 RTT가 큰 환경에서는 두 연결 절차의 지연이 함께 누적된다.

데이터베이스 연결

PostgreSQL 같은 데이터베이스에 접속할 때도 TCP 연결이 먼저 수립된다. 요청마다 새 연결을 생성하지 않고 데이터베이스 연결 풀을 사용하는 이유 중 하나다.

로드 밸런서와 프록시

로드 밸런서가 TCP 연결을 종료하는 구조에서는 클라이언트와 로드 밸런서, 로드 밸런서와 백엔드 서버 사이에 각각 별도 TCP 연결이 만들어진다.

7. 주의할 점

연결 완료와 애플리케이션 준비는 다르다

3-way Handshake 성공은 TCP 계층의 연결이 수립됐다는 뜻이다. 서버 애플리케이션의 인증, HTTP 처리, 데이터베이스 조회까지 성공했다는 의미는 아니다.

SYN Flood 공격

공격자가 많은 SYN을 보내고 마지막 ACK를 보내지 않으면 서버의 반개방 연결(Half-open Connection) 자원이 소모될 수 있다. 이를 SYN Flood 공격이라고 한다.

운영체제는 SYN 쿠키(SYN Cookie), 연결 대기열 크기 조정, 요청 속도 제한 같은 방어 기법을 사용할 수 있다.

Handshake 패킷도 손실될 수 있다

SYN, SYN-ACK, ACK가 손실되면 재전송이 발생한다. 연결 시간 초과(Connection Timeout)는 네트워크 RTT뿐 아니라 재전송 횟수와 운영체제 설정의 영향도 받는다.

ACK 직후 데이터 전송

클라이언트는 세 번째 ACK에 애플리케이션 데이터를 함께 실어 보낼 수 있다. 따라서 패킷 캡처에서 순수한 ACK 세그먼트 대신 ACK 플래그와 데이터가 함께 있는 세그먼트를 볼 수 있다.

8. 핵심 정리

  • TCP 3-way Handshake는 SYN → SYN-ACK → ACK 순서로 진행된다.
  • 양쪽의 통신 가능 여부를 확인하고 초기 시퀀스 번호를 동기화한다.
  • SYN은 시퀀스 번호 공간을 1만큼 소비하므로 확인 번호는 상대 시퀀스 번호에 1을 더한 값이다.
  • 연결 수립에는 일반적으로 1 RTT가 필요하다.
  • 연결 재사용과 연결 풀은 반복되는 Handshake 지연과 운영체제 자원 사용을 줄인다.