연결 수립과 종료
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)를 알리고 확인하는 과정입니다.
TCP 연결 수립에서 확인하는 두 초기 순서 번호
클라이언트가 초기 순서 번호를 보냅니다. 클라이언트가 서버 SYN을 확인합니다. 각 단계의 값과 전환 이유를 비교합니다.
TCP · 연결 수립
두 초기 순서 번호를 세 메시지로 확인한다
양쪽의 SYN은 각각 순서 번호 하나를 소비합니다. 데이터 없는 기본 예에서 ACK 값은 상대의 다음 순서 번호이며, 세 메시지를 세 번의 왕복으로 세지 않습니다.
아래로 시간 순서가 진행하며 간격은 실제 시간이 아닙니다. 실전에서는 세 번째 ACK에 애플리케이션 데이터를 실을 수 있으며, 이 그림은 데이터가 없는 연결 수립만 표시합니다.
이 예의 조건
- 본문과 전체 도표의 예를 따라 SYN부터 ACK까지 진행합니다.
관계도의 보충 설명
| 구분 | 전체 도표의 내용 |
|---|---|
| 제목 | TCP 연결 수립에서 확인하는 두 초기 순서 번호 |
| 관계 설명 | 클라이언트 SYN seq 100, 서버 SYN ACK seq 500 ack 101, 클라이언트 ACK seq 101 ack 501 순입니다. SYN은 순서 번호 하나를 소비합니다. |
장면별 값과 변화
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| SYN 클라이언트가 초기 순서 번호를 보냅니다. |
|
|
| SYN-ACK 서버가 자기 초기 번호와 상대 SYN 확인을 보냅니다. |
|
|
| ACK 클라이언트가 서버 SYN을 확인합니다. |
|
|
TCP 연결 수립에서 확인하는 두 초기 순서 번호
클라이언트가 초기 순서 번호를 보냅니다. 클라이언트가 서버 SYN을 확인합니다. 각 단계의 값과 전환 이유를 비교합니다.
TCP · 연결 수립
두 초기 순서 번호를 세 메시지로 확인한다
양쪽의 SYN은 각각 순서 번호 하나를 소비합니다. 데이터 없는 기본 예에서 ACK 값은 상대의 다음 순서 번호이며, 세 메시지를 세 번의 왕복으로 세지 않습니다.
아래로 시간 순서가 진행하며 간격은 실제 시간이 아닙니다. 실전에서는 세 번째 ACK에 애플리케이션 데이터를 실을 수 있으며, 이 그림은 데이터가 없는 연결 수립만 표시합니다.
이 예의 조건
- 본문과 전체 도표의 예를 따라 SYN부터 ACK까지 진행합니다.
관계도의 보충 설명
| 구분 | 전체 도표의 내용 |
|---|---|
| 제목 | TCP 연결 수립에서 확인하는 두 초기 순서 번호 |
| 관계 설명 | 클라이언트 SYN seq 100, 서버 SYN ACK seq 500 ack 101, 클라이언트 ACK seq 101 ack 501 순입니다. SYN은 순서 번호 하나를 소비합니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| SYN 클라이언트가 초기 순서 번호를 보냅니다. |
|
|
| SYN-ACK 서버가 자기 초기 번호와 상대 SYN 확인을 보냅니다. |
|
|
| ACK 클라이언트가 서버 SYN을 확인합니다. |
|
|
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도 내가 받았다”를 모두 확인해야 하기 때문입니다.
그림 뒤에 바이트를 보내기: 그림의 ISN은 클라이언트 100, 서버 500입니다. 두 SYN을 확인한 뒤 클라이언트의 첫 데이터 번호는 101, 서버의 첫 데이터 번호는 501이 됩니다. 클라이언트가 순서대로 5바이트를 보내면 번호 101~105를 차지하므로 서버의 누적 ACK는 다음 번호 106입니다. 서버가 보낸 데이터 없는 ACK는 서버 자신의 데이터 순서 번호를 소비하지 않습니다.
독립 계산: 클라이언트 ISN만 1000으로 바꾸었습니다. 연결 뒤 4바이트를 순서대로 보내고 곧바로 FIN을 보냈다면, 데이터 뒤 기대하는 ACK와 FIN까지 확인한 ACK는 각각 얼마일까요? 손실·재정렬·다른 데이터·번호 순환은 없는 조건입니다.
SYN·바이트·FIN을 각각 세기
SYN이 1000을 소비해 데이터는 1001~1004입니다. 데이터 네 바이트 뒤 누적 ACK는 1005이고, FIN이 번호 1005를 하나 더 소비해 FIN까지 확인한 ACK는 1006입니다. ACK만 있는 세그먼트에는 이 1을 추가하지 않습니다. ACK 번호는 방금 받은 마지막 번호가 아니라 다음에 기대하는 번호입니다.
TCP 명세 RFC 9293 §3.4의 순서 번호 규칙으로 계산합니다. 연결 수립의 세 메시지를 왕복 세 번으로 바꾸어 세지도 않습니다.
ISN의 역할
ISN(Initial Sequence Number)은 연결마다 새롭게 고르는 32비트 시작 순서 번호입니다.
단순히 0부터 시작하지 않는 이유는 이전 연결의 지연 세그먼트와 새 연결을 구분하고, sequence number 예측 공격을 어렵게 만들기 위해서입니다.
현대 구현은 보통 시간에 따라 변하는 값과 4-tuple, 비밀값 기반 함수를 조합해 공격자가 다음 ISN을 맞히기 어렵게 만듭니다.
TCP 연결은 출발지 IP, 출발지 포트, 목적지 IP, 목적지 포트로 이루어진 4-tuple로 식별됩니다.
같은 4-tuple이 재사용될 때 과거 세그먼트가 네트워크에 남아 있으면 새 연결과 혼동될 수 있습니다.
그래서 ISN 선택과 TIME_WAIT는 서로 보완적으로 동작합니다.
FIN 기반 정상 종료
TCP는 양방향 바이트 스트림입니다.
한쪽이 FIN을 보냈다는 것은 “나는 더 이상 보낼 데이터가 없다”는 뜻이지, 상대방도 즉시 보낼 데이터가 없다는 뜻은 아닙니다.
FIN으로 두 송신 방향을 각각 닫는 TCP 종료
클라이언트가 자기 송신 방향만 닫습니다. 클라이언트가 서버 FIN을 확인합니다. 각 단계의 값과 전환 이유를 비교합니다.
TCP · 정상 종료
FIN은 두 송신 방향을 각각 닫는다
클라이언트의 FIN 뒤에도 서버는 송신할 수 있습니다. 여기서는 추가 데이터 없이 각 FIN이 순서 번호 하나를 소비하고, 마지막 ACK를 보낸 클라이언트가 TIME-WAIT로 들어가는 기본 예를 봅니다.
아래로 시간 순서가 진행하며 간격은 실제 시간이 아닙니다. ACK와 FIN이 합쳐지거나 양쪽이 동시에 종료할 수도 있고, RST로 중단하는 경우는 이 정상 종료 예와 다릅니다.
이 예의 조건
- 본문과 전체 도표의 예를 따라 클라이언트 FIN부터 마지막 ACK까지 진행합니다.
관계도의 보충 설명
| 구분 | 전체 도표의 내용 |
|---|---|
| 제목 | FIN으로 두 송신 방향을 각각 닫는 TCP 종료 |
| 관계 설명 | 클라이언트 FIN seq 101, 서버 ACK 102, 서버 FIN seq 501, 클라이언트 ACK 502 순입니다. 두 FIN 사이 서버는 송신을 계속할 수 있습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 클라이언트 FIN 클라이언트가 자기 송신 방향만 닫습니다. |
|
|
| 서버 ACK 서버가 FIN이 소비한 순서 번호 하나를 확인합니다. |
|
|
| 서버 FIN 서버 앱도 자기 송신 방향을 닫습니다. |
|
|
| 마지막 ACK 클라이언트가 서버 FIN을 확인합니다. |
|
|
FIN으로 두 송신 방향을 각각 닫는 TCP 종료
클라이언트가 자기 송신 방향만 닫습니다. 클라이언트가 서버 FIN을 확인합니다. 각 단계의 값과 전환 이유를 비교합니다.
TCP · 정상 종료
FIN은 두 송신 방향을 각각 닫는다
클라이언트의 FIN 뒤에도 서버는 송신할 수 있습니다. 여기서는 추가 데이터 없이 각 FIN이 순서 번호 하나를 소비하고, 마지막 ACK를 보낸 클라이언트가 TIME-WAIT로 들어가는 기본 예를 봅니다.
아래로 시간 순서가 진행하며 간격은 실제 시간이 아닙니다. ACK와 FIN이 합쳐지거나 양쪽이 동시에 종료할 수도 있고, RST로 중단하는 경우는 이 정상 종료 예와 다릅니다.
이 예의 조건
- 본문과 전체 도표의 예를 따라 클라이언트 FIN부터 마지막 ACK까지 진행합니다.
관계도의 보충 설명
| 구분 | 전체 도표의 내용 |
|---|---|
| 제목 | FIN으로 두 송신 방향을 각각 닫는 TCP 종료 |
| 관계 설명 | 클라이언트 FIN seq 101, 서버 ACK 102, 서버 FIN seq 501, 클라이언트 ACK 502 순입니다. 두 FIN 사이 서버는 송신을 계속할 수 있습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 클라이언트 FIN 클라이언트가 자기 송신 방향만 닫습니다. |
|
|
| 서버 ACK 서버가 FIN이 소비한 순서 번호 하나를 확인합니다. |
|
|
| 서버 FIN 서버 앱도 자기 송신 방향을 닫습니다. |
|
|
| 마지막 ACK 클라이언트가 서버 FIN을 확인합니다. |
|
|
일반적인 active close 흐름은 다음과 같습니다.
| 단계 | 세그먼트 · 상태 의미 |
|---|---|
| 1 | 세그먼트: Client → Server: FIN 상태 의미: 클라이언트 송신 방향 종료, 클라이언트는 FIN-WAIT-1 |
| 2 | 세그먼트: Server → Client: ACK 상태 의미: 서버는 FIN을 확인하고 CLOSE-WAIT, 클라이언트는 FIN-WAIT-2 |
| 3 | 세그먼트: Server → Client: FIN 상태 의미: 서버 애플리케이션도 close한 뒤 자신의 송신 방향 종료 |
| 4 | 세그먼트: Client → Server: ACK 상태 의미: 클라이언트는 마지막 FIN을 확인하고 TIME-WAIT로 이동 |
이 네 단계는 가장 흔한 설명용 흐름입니다.
실제로는 양쪽이 거의 동시에 FIN을 보내는 simultaneous close, FIN과 ACK가 같은 세그먼트에 실리는 경우, RST로 abort되는 경우도 있습니다.
중요한 원칙은 FIN이 한 방향 송신 스트림의 끝을 알리고, FIN도 SYN처럼 순서 번호 공간을 1만큼 소비한다는 점입니다.
한 방향만 끝났는지 판단하기: 클라이언트가 FIN을 보내고 서버가 그 FIN을 ACK했습니다. 아직 서버 자신의 FIN은 없으며 서버에 보낼 응답이 남아 있다고 합시다. 클라이언트의 송신 방향은 끝났지만 서버→클라이언트 방향은 아직 남아 있으므로, 이 시점에 “양방향 데이터가 모두 끝났다”고 결론 내릴 수 없습니다. 애플리케이션에서 응답을 계속 읽어야 하는 경우에는 송신만 닫는 half-close와 소켓 전체를 닫는 close()의 사용 계약을 구분해야 합니다. 앞의 연결 실습 코드는 close()를 사용하는 관측 예이며 half-close 응답 수신을 구현한 것은 아닙니다.
마지막 ACK가 사라졌다면: 위 기본 종료에서 마지막 ACK만 손실되면 서버가 자신의 FIN을 다시 보낼 수 있습니다. 클라이언트의 TIME_WAIT는 이 재전송에 다시 ACK할 수 있는 상태를 남기는 목적도 있습니다. “내 앱이 끝났으므로 모든 상태를 즉시 지워도 된다”는 판단과 구분합니다.
TCP 연결 상태 전이
TCP 상태명은 “무슨 패킷을 보냈는지”, “무슨 패킷을 기다리는지”, “애플리케이션이 close를 호출했는지”를 보여 줍니다.
운영에서 자주 보는 상태는 다음처럼 해석하면 됩니다.
| 상태 | 의미 · 오래 누적될 때 보는 지점 |
|---|---|
| SYN-SENT | 의미: SYN을 보내고 SYN-ACK을 기다림 오래 누적될 때 보는 지점: 서버 listen, 방화벽, 라우팅, SYN 손실 |
| SYN-RECEIVED | 의미: SYN을 받고 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에 들어갈 수도 있습니다.
TIME_WAIT의 목적은 두 가지입니다.
첫째, 마지막 ACK가 손실되면 상대가 FIN을 재전송할 수 있으므로, TIME_WAIT 쪽이 그 FIN에 다시 ACK할 수 있어야 합니다.
둘째, 같은 4-tuple을 너무 빨리 재사용했을 때 과거 연결의 지연 세그먼트가 새 연결에 섞이는 일을 줄입니다.
RFC 문맥에서는 2MSL 대기가 등장하지만, 운영체제별 실제 TIME_WAIT 지속 시간과 튜닝 가능 여부는 다릅니다.
Linux, Windows, BSD 계열의 기본값과 정책이 다를 수 있으므로 고정된 초 단위 숫자로 외우기보다 목적과 구현 차이를 같이 봐야 합니다.
TIME_WAIT 실무 문제와 완화
짧은 TCP 연결을 매우 많이 만들면 TIME_WAIT가 누적되어 임시 포트, 연결 추적 테이블, 로드밸런서 상태 테이블을 압박할 수 있습니다.
하지만 TIME_WAIT 자체를 없애는 것보다 연결 재사용과 트래픽 구조를 먼저 보는 편이 안전합니다.
# Linux: TCP 상태별 개수
ss -tan | awk 'NR > 1 { count[$1]++ } END { for (s in count) print count[s], s }' | sort -rn
# Linux: TIME-WAIT만 확인
ss -Htan 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 주의사항: 데이터 손실과 프로토콜 오류 위험, 일반 권장 아님 |
HTTP/3의 연결 재사용은 QUIC 세션에 대한 설명입니다. TCP를 사용하는 HTTP 연결의 TIME_WAIT와 구분합니다.
비정상 종료: RST
FIN은 정상 종료이고, RST(Reset)는 연결 상태를 즉시 폐기하라는 신호입니다.
수신 측은 RST가 현재 연결 문맥에서 유효한지 확인한 뒤 연결을 abort합니다.
RST가 발생하는 대표적인 경우는 다음과 같습니다.
- 리슨하지 않는 포트로 SYN이 도착한 경우: 호스트가 연결을 거부하며 RST로 응답할 수 있습니다.
- 한쪽 TCP 상태가 사라졌는데 상대가 데이터를 보내는 경우: half-open 상태를 발견하고 RST로 정리할 수 있습니다.
- 애플리케이션이나 OS가 abortive close를 수행한 경우: 예를 들어
SO_LINGER(0)은 FIN 대신 RST를 유도할 수 있습니다. - 방화벽이나 프록시가 연결을 거부하는 경우: 장비 정책에 따라 RST를 보내거나, 아무 응답 없이 drop할 수 있습니다.
RST는 빠르지만 정상적인 데이터 배출을 보장하지 않습니다.
따라서 애플리케이션이 “보낼 데이터가 모두 상대에게 전달되었다”는 의미로 RST를 사용하면 안 됩니다.
연결 수립/종료 실습
이 실습은 Windows의 netstat 또는 Linux의 ss를 사용합니다. 0.1초 뒤의 스냅샷에서 모든 중간 상태가 관측되는 것은 아니며, recv() 한 번의 반환량도 메시지 전체 길이로 보장되지 않습니다.
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가 끊긴 구간부터 좁혀 갑니다.
다음 절에서는 TCP가 데이터를 안정적으로 전송하기 위한 핵심 메커니즘인 흐름 제어와 혼잡 제어를 살펴보겠습니다.