CS

TCP 3-way handshake 쉽게 이해하기

Enchantée 2026. 6. 30. 23:15
728x90
반응형

웹 페이지가 열리고, 앱이 API를 호출하고, 서버가 DB에 연결하는 과정 뒤에는 대부분 TCP connection이 있습니다.

개발자는 보통 HTTP 요청, SQL Query, socket read/write 같은 application level을 먼저 보지만, 실제 통신이 시작되기 전에는 TCP 3-way handshake가 먼저 일어납니다.

그리고 통신이 끝난 뒤에도 connection은 바로 사라지지 않고 TIME_WAIT 같은 상태로 잠시 남을 수 있습니다.

 

TCP 3-way handshake는 양쪽이 통신 가능한 상태인지 확인하고, TIME_WAIT은 닫힌 connection 주변의 늦은 packet을 안전하게 정리하기 위한 상태입니다.

 

이번 글에서는 TCP connection이 어떻게 만들어지는지, 왜 3번의 packet 교환이 필요한지, TIME_WAIT이 왜 남는지, 그리고 실무에서 어떤 장애 신호로 봐야 하는지 정리해보겠습니다.

 

TCP connection은 SYN, SYN-ACK, ACK 과정을 거쳐 만들어지고, 종료 후에는 TIME_WAIT 같은 상태가 잠시 남을 수 있습니다.

 


1. TCP connection은 무엇인가?

TCP connection은 두 endpoint가 순서 있는 byte stream을 주고받기 위해 만든 논리적인 통신 경로입니다.

HTTP 요청 하나를 보낼 때도, DB 서버에 Query를 보낼 때도, Redis나 Kafka 같은 외부 시스템에 접근할 때도 내부적으로는 TCP connection을 사용하는 경우가 많습니다.

여기서 중요한 점은 TCP가 message 단위가 아니라 stream 단위로 데이터를 다룬다는 것입니다.

application은 요청과 응답을 message처럼 생각하지만, TCP 입장에서는 순서가 보장된 byte 흐름을 안정적으로 전달하는 것이 핵심입니다.

구성 요소 의미
Client IP connection을 시작한 쪽의 IP
Client port client가 임시로 사용하는 ephemeral port
Server IP 요청을 받는 server의 IP
Server port server가 listen 중인 port, 예를 들어 80, 443, 5432

이 네 가지 값을 묶어 4-tuple이라고 부릅니다.

운영체제는 이 4-tuple을 기준으로 어떤 packet이 어떤 connection에 속하는지 구분합니다.

따라서 같은 server port로 들어오는 요청이 많아도 client IP와 client port가 다르면 서로 다른 connection으로 관리됩니다.

 


2. 왜 3-way handshake가 필요한가?

TCP는 데이터를 보내기 전에 양쪽이 모두 송신과 수신이 가능한 상태인지 확인해야 합니다.

또한 각 endpoint는 sequence number를 사용해 byte stream의 순서를 추적합니다.

3-way handshake는 이 두 가지를 맞추는 과정입니다.

단계 방향 packet 의미
1 Client -> Server SYN client가 connection 시작을 요청하고 자신의 초기 sequence number를 알림
2 Server -> Client SYN-ACK server가 요청을 수락할 준비가 있음을 알리고 자신의 초기 sequence number를 보냄
3 Client -> Server ACK client가 server의 응답을 확인하고 connection이 established 상태가 됨

첫 번째 SYN만으로는 server가 client에게 응답할 수 있는지 확인되지 않습니다.

두 번째 SYN-ACK만으로는 client가 server의 sequence number를 확인했다는 사실이 server에게 전달되지 않습니다.

그래서 마지막 ACK가 필요합니다.

이 과정을 통해 양쪽은 서로의 송신 가능성과 수신 가능성을 확인하고, 이후 데이터 전송에 필요한 sequence number 기준점을 맞춥니다.

 


3. 그림으로 이해하기

TCP connection의 시작과 종료 흐름을 단순화하면 다음처럼 볼 수 있습니다.

 

Handshake는 connection을 여는 과정이고, TIME_WAIT은 닫힌 connection의 마지막 정리를 위해 남는 상태입니다.

 

packet 흐름만 더 정확히 보면 다음 sequence diagram으로 정리할 수 있습니다.

- TCP handshake and close flow

Client                                      Server
  |                                           |
  |  SYN                                      |
  |------------------------------------------>|
  |                                           |
  |  SYN-ACK                                  |
  |<------------------------------------------|
  |                                           |
  |  ACK                                      |
  |------------------------------------------>|
  |                                           |
  |              ESTABLISHED                  |
  |<=========================================>|
  |                                           |
  |  FIN                                      |
  |------------------------------------------>|
  |                                           |
  |  ACK                                      |
  |<------------------------------------------|
  |                                           |
  |  FIN                                      |
  |<------------------------------------------|
  |                                           |
  |  ACK                                      |
  |------------------------------------------>|
  |                                           |
  |  TIME_WAIT                                |
  |                                           |

 

위 흐름은 client가 먼저 connection을 닫는 경우를 단순화한 것입니다.

실제로는 server가 먼저 닫을 수도 있고, 상황에 따라 RST로 즉시 끊기는 경우도 있습니다.

핵심은 TIME_WAIT이 “누가 먼저 닫았는가”와 관련이 있다는 점입니다.

 


4. TIME_WAIT은 왜 남는가?

TIME_WAIT은 active closer, 즉 connection을 먼저 닫은 쪽에 주로 남는 상태입니다.

많은 개발자가 netstat이나 ss에서 TIME_WAIT을 많이 보면 바로 누수라고 생각하지만, TIME_WAIT 자체는 TCP의 정상적인 상태입니다.

TIME_WAIT이 필요한 이유는 크게 두 가지입니다.

  1. 늦게 도착한 duplicate packet이 다음 connection에 섞이는 것을 막는다.
  2. 상대가 마지막 ACK를 받지 못해 FIN을 재전송하면 다시 ACK를 보낼 수 있게 한다.

TCP packet은 네트워크 상황에 따라 늦게 도착할 수 있습니다.

만약 connection을 닫자마자 같은 4-tuple을 바로 재사용하면, 이전 connection의 늦은 packet이 새 connection의 packet처럼 보일 위험이 있습니다.

TIME_WAIT은 이런 packet이 네트워크에서 사라질 시간을 확보합니다.

개념적으로는 2MSL 동안 대기한다고 설명하지만, 실제 대기 시간은 운영체제와 설정에 따라 달라질 수 있습니다.

 

상태 의미 실무에서 보는 관점
ESTABLISHED 데이터 송수신 가능한 상태 정상 연결 또는 오래 유지되는 connection 확인
TIME_WAIT 닫힌 connection의 늦은 packet을 정리하기 위해 대기 짧은 connection이 많으면 자연스럽게 증가 가능
CLOSE_WAIT 상대는 닫았지만 local application이 아직 socket을 닫지 않음 많이 쌓이면 application close 누락을 의심
SYN_SENT client가 SYN을 보냈지만 아직 응답을 받지 못함 네트워크, 방화벽, server listen 상태 확인
SYN_RECV server가 SYN을 받고 SYN-ACK를 보낸 상태 SYN backlog, handshake 지연, 공격성 트래픽 확인

TIME_WAIT이 많다는 사실만으로 문제라고 단정하면 안 됩니다.

짧은 connection을 매우 많이 만들고 닫는 서비스라면 TIME_WAIT이 늘어나는 것은 자연스러운 결과입니다.

다만 너무 많은 connection churn은 ephemeral port 고갈, connection 생성 latency 증가, kernel resource 사용량 증가로 이어질 수 있습니다.

 


5. 실무에서는 어떻게 확인할까?

운영 환경에서 TCP 상태를 볼 때는 connection 수만 보지 말고 상태별 분포를 함께 봐야 합니다.

Linux에서는 ss를 자주 사용합니다.

ss -tan state time-wait

 

예상 결과

Recv-Q Send-Q Local Address:Port   Peer Address:Port
0      0      10.0.0.10:52344     10.0.0.20:443
0      0      10.0.0.10:52345     10.0.0.20:443

TIME_WAIT 상태인 connection 목록이 표시됨

 

macOS에서는 netstat으로 비슷하게 확인할 수 있습니다.

netstat -an -p tcp | grep TIME_WAIT

 

예상 결과

tcp4       0      0  127.0.0.1.52344  127.0.0.1.443    TIME_WAIT
tcp4       0      0  127.0.0.1.52345  127.0.0.1.443    TIME_WAIT

로컬 또는 외부 endpoint와 닫힌 뒤 대기 중인 connection이 표시됨

 

이 결과를 볼 때는 다음 질문을 같이 던져야 합니다.

  1. 어떤 process가 connection을 많이 만들고 있는가?
  2. 같은 server로 짧은 connection을 반복 생성하고 있는가?
  3. HTTP keep-alive, DB connection pool, Redis connection pool이 제대로 동작하는가?
  4. CLOSE_WAIT이 함께 많이 쌓여 application close 누락이 있는가?
  5. SYN_SENT나 SYN_RECV가 많아 handshake 자체가 지연되고 있는가?

TIME_WAIT이 많을 때 가장 먼저 할 일은 kernel parameter를 바로 바꾸는 것이 아닙니다.

connection을 과도하게 만들고 닫는 application pattern이 있는지, reuse 가능한 connection을 매번 새로 만들고 있는지부터 봐야 합니다.

 


6. 서버 개발 관점에서 보는 핵심

서버 application에서 accept를 호출한다고 해서 handshake의 모든 packet을 application이 직접 처리하는 것은 아닙니다.

TCP handshake와 상태 관리는 대부분 kernel이 처리하고, application은 established 상태가 된 connection을 socket으로 받아 처리합니다.

그래서 server가 느릴 때는 application thread만 볼 것이 아니라 kernel queue와 connection 상태도 함께 봐야 합니다.

관점 확인할 내용
handshake 지연 SYN_SENT, SYN_RECV 증가, packet loss, firewall, load balancer 설정
accept 처리 지연 listen backlog, accept queue, worker thread 처리량
connection churn 짧은 connection 반복 생성, keep-alive 미사용, pool 크기 문제
close 누락 CLOSE_WAIT 증가, application에서 socket close 지연
TIME_WAIT 증가 active closer가 누구인지, 요청 패턴이 짧은 연결 중심인지 확인

예를 들어 API 서버가 외부 인증 서버를 호출할 때 매 요청마다 새 TCP connection을 만든다고 생각해보겠습니다.

트래픽이 적을 때는 문제가 없어 보이지만, 트래픽이 늘면 connection 생성 비용과 TIME_WAIT이 함께 증가합니다.

이때 HTTP client의 connection pool을 사용하면 기존 connection을 재사용할 수 있어 handshake 비용과 TIME_WAIT 증가를 줄일 수 있습니다.

DB도 마찬가지입니다.

Query마다 DB connection을 새로 만들면 handshake, 인증, 세션 초기화 비용이 반복됩니다.

실무에서 DB connection pool을 기본으로 사용하는 이유도 단지 편의성 때문이 아니라 connection lifecycle 비용을 줄이기 위해서입니다.

 


7. 자주 하는 오해

TCP handshake와 TIME_WAIT을 볼 때는 몇 가지 오해를 피해야 합니다.

  1. 오해 1. 3-way handshake는 보안을 위한 과정이다.
    Handshake는 암호화나 인증을 제공하지 않습니다. TLS handshake는 별도의 계층에서 보안 속성을 제공합니다.
  2. 오해 2. TIME_WAIT이 보이면 무조건 장애다.
    TIME_WAIT은 정상적인 TCP 상태입니다. 문제는 개수 자체보다 connection 생성 패턴, resource 사용량, port 고갈 여부입니다.
  3. 오해 3. server에만 TIME_WAIT이 남는다.
    TIME_WAIT은 보통 active closer 쪽에 남습니다. client가 먼저 닫으면 client 쪽에, server가 먼저 닫으면 server 쪽에 더 많이 보일 수 있습니다.
  4. 오해 4. CLOSE_WAIT과 TIME_WAIT은 비슷하다.
    TIME_WAIT은 닫힌 connection의 정상 정리 상태에 가깝고, CLOSE_WAIT은 application이 아직 local socket을 닫지 않은 상태입니다. CLOSE_WAIT이 계속 쌓이면 application 코드 문제를 의심해야 합니다.
  5. 오해 5. keep-alive를 켜면 항상 좋다.
    connection 재사용은 handshake 비용을 줄이지만, 너무 오래 유지되는 idle connection은 server resource를 차지할 수 있습니다. timeout과 pool 크기를 함께 조정해야 합니다.

즉, TCP 상태를 볼 때는 상태 이름 하나만 보고 결론을 내리기보다 connection lifecycle 전체를 봐야 합니다.

 


8. 면접 질문 예시

이 주제는 네트워크 기본기와 실무 감각을 함께 묻기 좋습니다.

  1. TCP 3-way handshake는 왜 2번이 아니라 3번의 packet 교환이 필요한가요?
    양쪽의 송신과 수신 가능성을 확인하고, 각 endpoint의 sequence number를 서로 확인하기 위해 마지막 ACK가 필요하다고 설명하면 좋습니다.
  2. TIME_WAIT은 왜 존재하며, 많이 쌓이면 무조건 문제인가요?
    늦은 packet 처리와 마지막 ACK 재전송 대응을 위해 존재하며, 정상 상태일 수 있지만 connection churn과 port 고갈 가능성은 함께 봐야 한다고 답할 수 있습니다.
  3. CLOSE_WAIT과 TIME_WAIT의 차이는 무엇인가요?
    TIME_WAIT은 active close 이후의 대기 상태이고, CLOSE_WAIT은 peer가 닫았지만 local application이 socket을 닫지 않은 상태라는 점을 구분하면 됩니다.

좋은 답변은 단순히 packet 이름을 외우는 것이 아니라, 왜 그 상태가 필요한지와 장애 분석에서 어떻게 해석하는지를 함께 설명합니다.

 


9. 실무에서 보는 점검 순서

TCP connection 문제가 의심될 때는 다음 순서로 확인하면 원인을 좁히기 좋습니다.

  1. 문제가 client 쪽인지 server 쪽인지 먼저 나눈다.
  2. SYN_SENT, SYN_RECV, ESTABLISHED, TIME_WAIT, CLOSE_WAIT 상태 분포를 확인한다.
  3. 특정 endpoint로 connection이 몰리는지 확인한다.
  4. 짧은 connection을 반복해서 만드는 코드 경로가 있는지 확인한다.
  5. HTTP keep-alive, DB connection pool, Redis connection pool 설정을 확인한다.
  6. CLOSE_WAIT이 많다면 application에서 socket close가 누락되거나 지연되는지 확인한다.
  7. TIME_WAIT이 많다면 active closer가 누구인지와 ephemeral port 사용량을 확인한다.
  8. kernel parameter 조정은 application pattern을 확인한 뒤 마지막에 검토한다.

특히 장애 상황에서는 단일 metric만 보고 판단하기 쉽습니다.

하지만 TCP 문제는 application latency, retry, connection pool, load balancer, kernel queue, firewall 정책이 함께 얽힐 수 있습니다.

따라서 상태별 connection 수와 application 로그, endpoint별 트래픽을 같이 봐야 합니다.

 


10. 정리

TCP 3-way handshake는 connection을 열기 전에 양쪽의 송수신 가능성과 sequence number 기준을 맞추는 과정입니다.

TIME_WAIT은 닫힌 connection의 늦은 packet과 마지막 ACK 재전송을 처리하기 위해 남는 정상 상태입니다.

TIME_WAIT이 많다고 바로 장애는 아니지만, 짧은 connection 반복과 ephemeral port 고갈 가능성은 확인해야 합니다.

CLOSE_WAIT이 계속 쌓이면 application이 socket을 닫지 않는 문제일 수 있습니다.

실무에서는 packet 이름을 외우는 것보다 connection lifecycle과 상태별 의미를 함께 해석하는 것이 중요합니다.

 


728x90
반응형