본문으로 건너뛰기

안동민 개발노트

본문 시작

연결 수립과 종료

TCP 3-way handshake와 FIN 종료의 순서 번호·상태 전이를 추적하고 TIME_WAIT 및 RST 상황을 진단합니다.

TCP가 연결 지향이라는 말은, 데이터를 보내기 전에 양쪽 TCP가 순서 번호와 옵션을 맞추고 연결 상태를 만든다는 뜻입니다.

연결 수립은 보통 3-way handshake, 정상 종료는 양쪽 송신 방향을 각각 닫는 FIN 기반 종료로 설명합니다.

면접에서는 “SYN, ACK 순서”만 외우기 쉽지만, 실무 디버깅에서는 상태 전이, sequence/ack 번호, TIME_WAIT, RST의 의미까지 같이 봐야 합니다.


3-way Handshake

TCP 연결 수립은 양쪽이 서로의 초기 순서 번호(ISN)를 알리고 확인하는 과정입니다.

HTML 다이어그램: /docs/network/ch6/ch6-2/1.html

1단계 — SYN: 클라이언트가 active open을 수행하며 SYN, seq=x를 보냅니다.

SYN은 순서 번호 공간을 1만큼 소비하므로, 상대가 기대하는 다음 번호는 x+1입니다.

2단계 — SYN-ACK: 서버는 클라이언트의 SYN을 확인하는 ACK x+1과 자신의 SYN, seq=y를 한 세그먼트에 담아 보냅니다.

이때 MSS, Window Scale, SACK Permitted, Timestamps 같은 TCP 옵션도 협상됩니다.

3단계 — ACK: 클라이언트는 서버의 SYN을 확인하는 ACK y+1을 보냅니다.

이 마지막 ACK가 서버에 도착하면 서버도 연결을 완료 상태로 보고, 이후 애플리케이션 데이터 전송이 안정적으로 시작됩니다.

일부 구현과 상황에서는 세 번째 ACK에 데이터를 실을 수 있지만, 서버가 연결을 완전히 받아들이는 시점과 애플리케이션 처리 정책은 구현에 따라 달라질 수 있습니다.

3-way일까요?

양쪽이 “내 SYN이 상대에게 도착했고, 상대의 SYN도 내가 받았다”를 모두 확인해야 하기 때문입니다.

HTML 다이어그램: /docs/network/ch6/ch6-2/2.html

ISN의 역할

ISN(Initial Sequence Number)은 연결마다 새롭게 고르는 32비트 시작 순서 번호입니다.

단순히 0부터 시작하지 않는 이유는 이전 연결의 지연 세그먼트와 새 연결을 구분하고, sequence number 예측 공격을 어렵게 만들기 위해서입니다.

현대 구현은 보통 시간에 따라 변하는 값과 4-tuple, 비밀값 기반 함수를 조합해 공격자가 다음 ISN을 맞히기 어렵게 만듭니다.

HTML 다이어그램: /docs/network/ch6/ch6-2/3.html

TCP 연결은 출발지 IP, 출발지 포트, 목적지 IP, 목적지 포트로 이루어진 4-tuple로 식별됩니다.

같은 4-tuple이 재사용될 때 과거 세그먼트가 네트워크에 남아 있으면 새 연결과 혼동될 수 있습니다.

그래서 ISN 선택과 TIME_WAIT는 서로 보완적으로 동작합니다.


FIN 기반 정상 종료

TCP는 양방향 바이트 스트림입니다.

한쪽이 FIN을 보냈다는 것은 “나는 더 이상 보낼 데이터가 없다”는 뜻이지, 상대방도 즉시 보낼 데이터가 없다는 뜻은 아닙니다.

HTML 다이어그램: /docs/network/ch6/ch6-2/4.html

일반적인 active close 흐름은 다음과 같습니다.

단계세그먼트상태 의미
1Client → Server: FIN클라이언트 송신 방향 종료, 클라이언트는 FIN-WAIT-1
2Server → Client: ACK서버는 FIN을 확인하고 CLOSE-WAIT, 클라이언트는 FIN-WAIT-2
3Server → Client: FIN서버 애플리케이션도 close한 뒤 자신의 송신 방향 종료
4Client → Server: ACK클라이언트는 마지막 FIN을 확인하고 TIME-WAIT로 이동

이 네 단계는 가장 흔한 설명용 흐름입니다.

실제로는 양쪽이 거의 동시에 FIN을 보내는 simultaneous close, FIN과 ACK가 같은 세그먼트에 실리는 경우, RST로 abort되는 경우도 있습니다.

중요한 원칙은 FIN이 한 방향 송신 스트림의 끝을 알리고, FIN도 SYN처럼 순서 번호 공간을 1 소비한다는 점입니다.


TCP 연결 상태 전이

TCP 상태명은 “무슨 패킷을 보냈는지”, “무슨 패킷을 기다리는지”, “애플리케이션이 close를 호출했는지”를 보여 줍니다.

HTML 다이어그램: /docs/network/ch6/ch6-2/5.html

운영에서 자주 보는 상태는 다음처럼 해석하면 됩니다.

상태의미오래 누적될 때 보는 지점
SYN-SENTSYN을 보내고 SYN-ACK을 기다림서버 listen, 방화벽, 라우팅, SYN 손실
SYN-RECEIVEDSYN을 받고 SYN-ACK을 보낸 뒤 최종 ACK 대기SYN backlog, SYN flood, handshake 미완료
ESTABLISHED데이터 송수신 가능정상 연결 또는 장기 연결
CLOSE-WAIT상대 FIN은 받았고, 로컬 애플리케이션 close 대기애플리케이션이 소켓을 닫지 않는 문제 가능
FIN-WAIT-2내 FIN은 ACK 됐고, 상대 FIN을 기다림상대 애플리케이션 종료 지연 또는 정책 timeout
TIME-WAIT마지막 ACK 이후 지연 세그먼트 격리짧은 연결이 매우 많을 때 포트/테이블 압박 점검

TIME_WAIT 상태

TIME_WAIT는 오류가 아니라 정상 종료를 안전하게 마무리하기 위한 상태입니다.

보통 마지막 ACK를 보낸 쪽, 즉 active closer가 TIME_WAIT에 들어갑니다.

동시 종료 같은 예외에서는 양쪽이 TIME_WAIT에 들어갈 수도 있습니다.

HTML 다이어그램: /docs/network/ch6/ch6-2/6.html

TIME_WAIT의 목적은 두 가지입니다.

첫째, 마지막 ACK가 손실되면 상대가 FIN을 재전송할 수 있으므로, TIME_WAIT 쪽이 그 FIN에 다시 ACK할 수 있어야 합니다.

둘째, 같은 4-tuple을 너무 빨리 재사용했을 때 과거 연결의 지연 세그먼트가 새 연결에 섞이는 일을 줄입니다.

RFC 문맥에서는 2MSL 대기가 등장하지만, 운영체제별 실제 TIME_WAIT 지속 시간과 튜닝 가능 여부는 다릅니다.

Linux, Windows, BSD 계열의 기본값과 정책이 다를 수 있으므로 고정된 초 단위 숫자로 외우기보다 목적과 구현 차이를 같이 봐야 합니다.


TIME_WAIT 실무 문제와 완화

짧은 TCP 연결을 매우 많이 만들면 TIME_WAIT가 누적되어 임시 포트, 연결 추적 테이블, 로드밸런서 상태 테이블을 압박할 수 있습니다.

하지만 TIME_WAIT 자체를 없애는 것보다 연결 재사용과 트래픽 구조를 먼저 보는 편이 안전합니다.

time_wait_check.sh
# Linux: TCP 상태별 개수
ss -tan | awk 'NR > 1 { count[$1]++ } END { for (s in count) print count[s], s }' | sort -rn

# Linux: TIME-WAIT만 확인
ss -tan state time-wait | wc -l

# Linux: 임시 포트 범위 확인
cat /proc/sys/net/ipv4/ip_local_port_range

# 특정 목적지로 열린 연결이 많은지 확인
ss -tan '( dport = :443 or sport = :443 )'
접근효과주의사항
Connection Pool연결 생성과 종료 자체를 줄임DB, HTTP 클라이언트에서 가장 먼저 검토
Keep-Alive / HTTP/2 / HTTP/3요청 여러 개를 같은 연결 또는 세션에 태움서버 idle timeout, 프록시 정책과 함께 조정
임시 포트 범위 확대클라이언트 쪽 포트 고갈 여유 증가연결 남발의 근본 해결은 아님
커널 파라미터 조정특정 OS에서 재사용 조건을 완화할 수 있음OS 버전별 의미가 달라 문서 확인 후 적용
SO_LINGER(0) / RST 종료정상 FIN 종료를 건너뛰고 즉시 abort데이터 손실과 프로토콜 오류 위험, 일반 권장 아님

비정상 종료: RST

FIN은 정상 종료이고, RST(Reset)는 연결 상태를 즉시 폐기하라는 신호입니다.

수신 측은 RST가 현재 연결 문맥에서 유효한지 확인한 뒤 연결을 abort합니다.

HTML 다이어그램: /docs/network/ch6/ch6-2/7.html

RST가 발생하는 대표적인 경우는 다음과 같습니다.

  • 리슨하지 않는 포트로 SYN이 도착한 경우: 호스트가 연결을 거부하며 RST로 응답할 수 있습니다.
  • 한쪽 TCP 상태가 사라졌는데 상대가 데이터를 보내는 경우: half-open 상태를 발견하고 RST로 정리할 수 있습니다.
  • 애플리케이션이나 OS가 abortive close를 수행한 경우: 예를 들어 SO_LINGER(0)은 FIN 대신 RST를 유도할 수 있습니다.
  • 방화벽이나 프록시가 연결을 거부하는 경우: 장비 정책에 따라 RST를 보내거나, 아무 응답 없이 drop할 수 있습니다.

RST는 빠르지만 정상적인 데이터 배출을 보장하지 않습니다.

따라서 애플리케이션이 “보낼 데이터가 모두 상대에게 전달되었다”는 의미로 RST를 사용하면 안 됩니다.


연결 수립/종료 실습

connection_states.py
import socket
import subprocess
import sys
import time

PORT = 8765

def show_connection_state(description):
    print(f"\n--- {description} ---")
    if sys.platform == "win32":
        command = ["netstat", "-an"]
    else:
        command = ["ss", "-tan"]

    result = subprocess.run(command, capture_output=True, text=True, check=False)
    for line in result.stdout.splitlines():
        if str(PORT) in line:
            print(" ", line.strip())

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(("127.0.0.1", PORT))
server.listen(1)
show_connection_state("서버 LISTEN")

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", PORT))
conn, _ = server.accept()
show_connection_state("ESTABLISHED")

client.sendall(b"Hello TCP!")
print("\n수신 데이터:", conn.recv(1024).decode())

client.close()
time.sleep(0.1)
show_connection_state("클라이언트 close 이후")

conn.close()
server.close()
time.sleep(0.1)
show_connection_state("종료 이후")

연결 관련 면접 질문 정리

질문핵심 답변
3-way handshake를 설명하세요SYN → SYN-ACK → ACK, 양쪽 ISN과 도달 가능성을 확인
왜 2-way가 아니라 3-way인가요?서버가 보낸 SYN이 클라이언트에게 도달했는지 확인해야 함
4-way 종료를 설명하세요FIN → ACK → FIN → ACK 형태로 양방향 송신 스트림을 닫음
왜 종료는 분리되나요?한쪽 FIN 이후에도 상대는 남은 데이터를 보낼 수 있음
TIME_WAIT의 목적은?마지막 ACK 손실 대비와 지연 세그먼트 격리
CLOSE_WAIT이 누적되면?애플리케이션이 close를 호출하지 않는 문제 가능
RST와 FIN의 차이는?FIN은 정상 종료, RST는 연결 상태 즉시 폐기
SYN flood란?대량 SYN으로 반연결 상태 자원을 고갈시키는 공격

연결 수립·종료 문제는 SYN, SYN-ACK, ACK, FIN, RST가 끊긴 구간부터 좁혀 갑니다.

HTML 다이어그램: /docs/network/ch6/ch6-2/8.html

다음 절에서는 TCP가 데이터를 안정적으로 전송하기 위한 핵심 메커니즘인 흐름 제어와 혼잡 제어를 살펴보겠습니다.