작성일 :

연결이란 무엇인가

Part 1에서 소켓이 무엇인지, TCP와 UDP의 차이를 살펴보았습니다. TCP는 “연결”을 맺는다고 했는데, 연결이란 정확히 무엇일까요?

물리적으로 전용 회선이 생기는 것이 아닙니다. 인터넷은 패킷 교환 네트워크입니다. 각 패킷은 독립적으로 라우팅되며, 같은 연결의 패킷이라도 다른 경로로 갈 수 있습니다. TCP의 연결은 양쪽 끝점의 상태 동기화입니다.

TCP 연결이 수립되면 양쪽 호스트의 커널은 동일한 정보를 공유합니다.

동기화되는 정보 용도
시퀀스 번호 데이터 순서 추적
윈도우 크기 수신 가능한 데이터량
연결 상태 ESTABLISHED, CLOSED 등

시퀀스 번호는 전송하는 각 바이트에 붙이는 번호입니다. 패킷이 순서대로 도착하지 않아도 수신측이 올바른 순서로 재조립할 수 있습니다. 윈도우 크기는 “나는 지금 N바이트까지 받을 수 있다”는 정보입니다. 송신측은 이 값을 보고 수신측 버퍼가 넘치지 않도록 전송 속도를 조절합니다.

이 정보가 동기화되어 있으면 “연결되어 있다”고 말합니다.

반면 UDP는 상태를 유지하지 않습니다. 각 데이터그램은 독립적이며, 이전에 데이터를 주고받았는지 상대방이 준비되어 있는지 알지 못합니다. 그래서 UDP를 비연결형(Connectionless) 프로토콜이라고 합니다.


TCP 상태 전이 다이어그램

TCP 연결은 11가지 상태를 가집니다. 각 상태는 연결 수립, 데이터 전송, 연결 종료 과정에서 나타납니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
┌─────────────────────────────────────────────────────────────────┐
│                         CLOSED                                  │
└───────────────────────────┬─────────────────────────────────────┘
                            │
        ┌───────────────────┴───────────────────┐
        ↓                                       ↓
   [서버 경로]                             [클라이언트 경로]

    LISTEN                                 SYN_SENT
        ↓ SYN 수신                              ↓ SYN+ACK 수신
    SYN_RCVD                                    │
        ↓ ACK 수신                              │
        └───────────────┬───────────────────────┘
                        ↓
                   ESTABLISHED  ← 데이터 전송
                        │
        ┌───────────────┴───────────────┐
        ↓                               ↓
   [능동 종료]                      [수동 종료]

   FIN_WAIT_1                      CLOSE_WAIT
        ↓ ACK 수신                      ↓ close()
   FIN_WAIT_2                      LAST_ACK
        ↓ FIN 수신                      ↓ ACK 수신
   TIME_WAIT ──── 2MSL 대기 ────→  CLOSED
        ↓
      CLOSED


각 상태의 의미입니다.

연결 수립 단계

상태 주체 설명 대기 중인 이벤트
CLOSED - 연결 없음 -
LISTEN 서버 클라이언트 연결 요청 대기 SYN 수신
SYN_SENT 클라이언트 SYN 전송 완료 SYN+ACK 수신
SYN_RCVD 서버 SYN 수신, SYN+ACK 전송 완료 ACK 수신
ESTABLISHED 양쪽 연결 수립 완료, 데이터 전송 가능 -

연결 종료 단계

상태 주체 설명 대기 중인 이벤트
FIN_WAIT_1 능동 종료 측 close() 호출, FIN 전송 완료 ACK 수신
FIN_WAIT_2 능동 종료 측 자신의 FIN에 대한 ACK 수신 상대방 FIN 수신
TIME_WAIT 능동 종료 측 상대방 FIN 수신, ACK 전송 완료 2MSL 타이머 만료
CLOSE_WAIT 수동 종료 측 상대방 FIN 수신, ACK 전송 완료 애플리케이션 close() 호출
LAST_ACK 수동 종료 측 close() 호출, FIN 전송 완료 ACK 수신
CLOSING 양쪽 양쪽이 동시에 FIN 전송 (드문 경우) ACK 수신

3-Way Handshake: 왜 3번인가

TCP 연결을 수립할 때 3-Way Handshake가 필요합니다. 왜 2번이 아니라 3번일까요?


2-Way Handshake의 문제

2번의 교환으로 연결을 수립한다고 가정합니다.

1
2
3
4
5
6
7
클라이언트                           서버
    │                                 │
    │──── 연결 요청 (seq=100) ────→   │
    │                                 │
    │←── 연결 수락 (ack=101) ─────    │
    │                                 │
    연결 수립!                      연결 수립!

정상적인 경우에는 문제가 없어 보입니다. 하지만 네트워크에서는 오래된 패킷이 늦게 도착할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
클라이언트                                            서버
    │                                                   │
    │──── SYN (seq=100) ────────────────────────→ X     │ ← 패킷이 네트워크에서 지연
    │                                                   │
    │     (타임아웃, 재시도)                             │
    │                                                   │
    │──── SYN (seq=200) ────────────────────────→      │
    │←─── SYN+ACK ──────────────────────────────       │
    │──── ACK ──────────────────────────────────→      │
    │                                                   │
    │     [정상 통신 후 연결 종료]                       │
    │                                                   │
    │                        오래된 SYN (seq=100) 도착 →│
    │                                                   │
    │←─── SYN+ACK (새 연결로 오해) ─────────────       │
    │                                                   │
    ???                                           연결 수립!

서버는 오래된 SYN을 새 연결 요청으로 착각하고 자원을 할당합니다. 클라이언트는 이 연결에 대해 아무것도 모르므로 응답하지 않습니다. 서버는 존재하지 않는 연결을 위해 자원을 낭비합니다.


3-Way Handshake의 해결책

1
2
3
4
5
6
7
8
클라이언트                                서버
    │                                      │
    │─── SYN (seq=x) ────────────────→    │  1단계: 클라이언트 → 서버
    │                                      │
    │←── SYN+ACK (seq=y, ack=x+1) ────    │  2단계: 서버 → 클라이언트
    │                                      │
    │─── ACK (ack=y+1) ──────────────→    │  3단계: 클라이언트 → 서버
    │                                      │

세 번째 ACK가 핵심입니다. 서버는 SYN+ACK를 보낸 후 바로 연결을 수립하지 않고, 클라이언트의 ACK를 기다립니다.

앞서 본 오래된 SYN 문제를 다시 봅시다.

1
2
3
4
5
6
7
8
9
클라이언트                                            서버
    │                                                   │
    │                        오래된 SYN (seq=100) 도착 →│
    │                                                   │
    │←─── SYN+ACK (seq=y, ack=101) ─────────────       │  서버: ACK 대기 상태
    │                                                   │
    │     (클라이언트: 요청한 적 없음, 무시 또는 RST)     │
    │                                                   │
    │                                              타임아웃, 연결 취소

클라이언트는 자신이 보낸 적 없는 SYN에 대한 SYN+ACK를 받습니다. 이 패킷을 무시하거나 RST(연결 거부)를 보냅니다. 서버는 ACK를 받지 못하면 연결을 수립하지 않습니다. 세 번째 단계가 클라이언트의 의도를 확인하는 역할을 합니다.


시퀀스 번호 동기화

3-Way Handshake의 또 다른 목적은 시퀀스 번호(ISN, Initial Sequence Number)를 교환하는 것입니다.

1
2
3
4
5
6
7
8
클라이언트                                서버
    │                                      │
    │─── SYN (seq=1000) ────────────→     │  "내 시작 번호는 1000"
    │                                      │
    │←── SYN+ACK (seq=5000, ack=1001) ─   │  "알겠어. 내 시작 번호는 5000"
    │                                      │
    │─── ACK (ack=5001) ────────────→     │  "알겠어"
    │                                      │

TCP는 전송하는 각 바이트에 시퀀스 번호를 붙입니다. 패킷이 순서대로 도착하지 않아도 수신측이 번호를 보고 재정렬할 수 있습니다.

그런데 시퀀스 번호는 왜 0이 아니라 임의의 값(1000, 5000 등)으로 시작할까요?

이유 설명
보안 예측 가능한 시퀀스 번호는 TCP 세션 하이재킹 공격에 취약
구분 이전 연결의 지연된 패킷과 새 연결의 패킷을 구분

현대 운영체제는 암호학적으로 안전한 난수 생성기로 ISN을 만듭니다.


4-Way Handshake: 연결 종료

연결 수립은 3번, 연결 종료는 4번이 필요합니다. 왜 더 많을까요?

3-Way Handshake에서는 서버가 SYN과 ACK를 동시에 보낼 수 있었습니다. 클라이언트의 SYN을 받으면 서버도 바로 자신의 SYN을 보낼 준비가 되어 있기 때문입니다.

하지만 종료는 다릅니다. 클라이언트가 FIN을 보내도 서버는 아직 보낼 데이터가 남아있을 수 있습니다. 서버는 ACK만 먼저 보내고, 자신의 데이터 전송이 끝난 후에야 FIN을 보냅니다.


반이중 종료(Half-Close)

TCP 연결은 양방향입니다. A→B와 B→A, 두 개의 독립적인 데이터 흐름이 있으며, 각 방향을 독립적으로 종료합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
클라이언트 (능동 종료)                           서버 (수동 종료)
[ESTABLISHED]                               [ESTABLISHED]
    │                                            │
    │─── FIN ───────────────────────────→       │
    │                                            │
[FIN_WAIT_1]                                     │  FIN 수신
    │                                            ↓
    │←── ACK ────────────────────────────  [CLOSE_WAIT]
    │                                            │
[FIN_WAIT_2]                                     │
    │                                            │  (남은 데이터 전송)
    │←── 데이터... ──────────────────────       │
    │                                            │
    │                                            │  close() 호출
    │←── FIN ────────────────────────────  [LAST_ACK]
    │                                            │
[TIME_WAIT]                                      │
    │─── ACK ───────────────────────────→       │
    │                                            │
    │  (2MSL 대기)                          [CLOSED]
    ↓
[CLOSED]
단계 방향 의미
1단계 클라이언트 → 서버 FIN: “나는 더 보낼 데이터가 없습니다”
2단계 서버 → 클라이언트 ACK: “알겠습니다” (서버는 아직 보낼 수 있음)
3단계 서버 → 클라이언트 FIN: “나도 더 보낼 데이터가 없습니다”
4단계 클라이언트 → 서버 ACK: “알겠습니다”

2단계와 3단계 사이에 서버가 남은 데이터를 보낼 수 있습니다. 이것이 반이중 종료(Half-Close)입니다. 한쪽은 송신을 끝냈지만 수신은 계속하는 상태입니다.


CLOSE_WAIT 상태

CLOSE_WAIT은 상대방이 FIN을 보냈지만 자신은 아직 close()를 호출하지 않은 상태입니다.

1
2
3
4
$ netstat -an | grep CLOSE_WAIT
tcp  0  0  192.168.1.100:8080  10.0.0.1:52000  CLOSE_WAIT
tcp  0  0  192.168.1.100:8080  10.0.0.2:52001  CLOSE_WAIT
... (수백 개)

CLOSE_WAIT이 쌓이면 애플리케이션 버그의 징후입니다.

원인 설명
close() 누락 소켓을 열고 닫지 않는 코드
예외 처리 미흡 try 블록에서 예외 발생 시 close()를 건너뜀
리소스 누수 소켓 객체가 가비지 컬렉션되지 않음

CLOSE_WAIT은 커널이 아닌 애플리케이션이 해결해야 하는 문제입니다. 커널은 애플리케이션이 close()를 호출할 때까지 기다릴 수밖에 없습니다. 파일 디스크립터가 고갈되면 새 연결을 받을 수 없게 됩니다.


TIME_WAIT: 왜 바로 닫지 않는가

능동 종료 측은 마지막 ACK를 보낸 후 TIME_WAIT 상태에서 일정 시간 대기합니다.

용어 의미
MSL Maximum Segment Lifetime, 패킷이 네트워크에서 살아있을 수 있는 최대 시간 보통 60초
2MSL TIME_WAIT 대기 시간 2분

왜 바로 CLOSED로 가지 않고 2분이나 기다릴까요?


이유 1: 지연된 패킷으로부터 새 연결 보호

Part 1에서 살펴본 5-tuple을 떠올려 봅시다. 연결이 종료되어도 네트워크에 해당 연결의 패킷이 떠돌 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
시간 →

[연결 1: (TCP, A:5000, B:80)]
    │
    │─── 데이터 (seq=1000) ───→ X    ← 패킷이 네트워크에서 지연
    │
    종료
                                     [연결 2: (TCP, A:5000, B:80)]  ← 같은 5-tuple로 새 연결
                                         │
                      지연된 패킷 도착 →  │  ← 연결 2의 데이터로 오해!
                                         │
                                     데이터 오염

TIME_WAIT 동안 같은 5-tuple로 새 연결을 맺지 않으면 지연된 패킷이 만료될 시간을 확보합니다.


이유 2: 마지막 ACK 손실 대비

1
2
3
4
5
6
7
8
9
10
11
A (능동 종료)                         B (수동 종료)
    │                                    │
    │←─── FIN ───────────────────────   │ [LAST_ACK]
    │                                    │
    │──── ACK ──────────────────→ X     │  ← 마지막 ACK 손실
    │                                    │
[CLOSED]?                                │  (ACK 안 옴, FIN 재전송)
    │                                    │
    │←─── FIN (재전송) ──────────────   │
    │                                    │
    ???                                  │  ← A가 CLOSED면 응답 불가

A가 TIME_WAIT에서 대기하면 재전송된 FIN에 ACK로 응답할 수 있습니다. B가 정상적으로 CLOSED 상태로 전이하도록 보장합니다.

이유 TIME_WAIT가 해결하는 문제
지연된 패킷 같은 5-tuple의 새 연결에 오래된 데이터가 섞이는 것 방지
ACK 손실 상대방이 FIN을 재전송할 경우 응답 가능


TIME_WAIT와 서버 재시작 문제

서버가 연결을 먼저 끊으면(능동 종료) 서버 측에 TIME_WAIT이 쌓입니다. 이 상태에서 서버를 재시작하면 문제가 생깁니다.

1
2
3
4
$ ./server  # 8080 포트에서 실행
^C          # 종료 (연결된 클라이언트에 FIN 전송 → TIME_WAIT)
$ ./server  # 즉시 재시작
Error: Address already in use

TIME_WAIT 상태의 소켓이 8080 포트를 점유하고 있어서 새 서버가 bind()할 수 없습니다.

SO_REUSEADDR 옵션이 이 문제를 해결합니다.

1
2
3
int optval = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
bind(sockfd, ...);
옵션 효과
SO_REUSEADDR TIME_WAIT 상태의 주소에도 바인딩 허용

이 옵션은 앞서 설명한 TIME_WAIT의 안전장치를 우회하는 것이 아닙니다. 새 연결의 시퀀스 번호가 이전 연결과 다르기 때문에 지연된 패킷은 여전히 구분됩니다. 서버 개발에서 거의 필수적으로 사용됩니다.


2MSL의 수학적 근거

왜 하필 2MSL일까요? 최악의 시나리오를 고려합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
시간 →
─────────────────────────────────────────────────────────→

A                                                       B
│                                                       │
│──── ACK ─────────────────────────────────────→ X     │
│     (거의 1MSL 직전에 손실)                           │
│                                                       │
│                              ← FIN 재전송 ────────────│
│     (거의 1MSL 걸려서 도착)                           │
│                                                       │
├─────────────── 1MSL ──────────┼─────── 1MSL ─────────┤
                               = 2MSL
구간 소요 시간 상황
A → B 최대 1MSL ACK가 거의 도착할 때쯤 손실
B → A 최대 1MSL B가 FIN 재전송, A에 도착
합계 2MSL A가 재전송된 FIN을 받을 수 있는 최대 시간

A는 2MSL 동안 대기하면 B의 FIN 재전송을 받을 수 있습니다.


동시 연결과 동시 종료

드물지만 양쪽이 동시에 연결을 시작하거나 종료할 수 있습니다. TCP는 이런 상황도 처리할 수 있도록 설계되어 있습니다.

동시 열기(Simultaneous Open)

양쪽이 동시에 connect()를 호출하면 발생합니다. P2P 애플리케이션에서 NAT 홀 펀칭 시 나타날 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
A                                B
[SYN_SENT]                       [SYN_SENT]
│                                │
│─── SYN (seq=x) ─────────────→ │
│                                │
│ ←───────────── SYN (seq=y) ───│
│                                │
[SYN_RCVD]                       [SYN_RCVD]
│                                │
│─── SYN+ACK (ack=y+1) ───────→ │
│                                │
│ ←─────── SYN+ACK (ack=x+1) ───│
│                                │
[ESTABLISHED]                    [ESTABLISHED]

일반적인 3-Way Handshake와 달리 4개의 세그먼트가 교환됩니다. 양쪽 모두 클라이언트이자 서버 역할을 합니다.


동시 닫기(Simultaneous Close)

양쪽이 동시에 close()를 호출하면 발생합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
A                                B
[FIN_WAIT_1]                     [FIN_WAIT_1]
│                                │
│─── FIN ─────────────────────→ │
│                                │
│ ←───────────────────── FIN ───│
│                                │
[CLOSING]                        [CLOSING]    ← ACK 대신 FIN을 먼저 받음
│                                │
│─── ACK ─────────────────────→ │
│                                │
│ ←───────────────────── ACK ───│
│                                │
[TIME_WAIT]                      [TIME_WAIT]
│                                │
(2MSL 대기)                      (2MSL 대기)
│                                │
[CLOSED]                         [CLOSED]

CLOSING 상태는 FIN을 보낸 후 ACK 대신 상대방의 FIN을 먼저 받았을 때 진입합니다. 일반적인 4-Way Handshake에서는 나타나지 않습니다.


소켓 버퍼: 데이터의 대기실

Part 1에서 write()read()가 소켓 파일 디스크립터를 통해 데이터를 주고받는다고 했습니다. 하지만 write()가 반환되었다고 데이터가 네트워크로 전송된 것은 아닙니다.

커널의 소켓 구조에는 버퍼가 있습니다. 각 연결마다 송신 버퍼수신 버퍼가 존재합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌─────────────────────────────────────────────────────────────────────┐
│ 사용자 공간                                                          │
│  ┌───────────┐                                                      │
│  │  프로세스  │                                                      │
│  └─────┬─────┘                                                      │
│        │ write() / read()                                           │
├────────┼────────────────────────────────────────────────────────────┤
│ 커널 공간 │                                                          │
│        ↓                                                            │
│  ┌─────────────────────────────────────┐                            │
│  │            소켓 버퍼                 │                            │
│  │  ┌─────────────────┐                │                            │
│  │  │ 송신 버퍼        │ ──→ TCP 세그먼트로 전송 ──→ 네트워크        │
│  │  │ [전송 대기 데이터]│                │                            │
│  │  └─────────────────┘                │                            │
│  │  ┌─────────────────┐                │                            │
│  │  │ 수신 버퍼        │ ←── 도착한 세그먼트 ←─── 네트워크           │
│  │  │ [도착한 데이터]   │                │                            │
│  │  └─────────────────┘                │                            │
│  └─────────────────────────────────────┘                            │
└─────────────────────────────────────────────────────────────────────┘

애플리케이션과 네트워크는 서로 다른 속도로 동작합니다. 버퍼가 이 속도 차이를 흡수합니다.


송신 버퍼

write()를 호출하면 데이터가 송신 버퍼에 복사됩니다.

1
2
3
4
5
6
7
8
9
애플리케이션: write(fd, data, 1000)
                    │
                    ↓
            ┌───────────────────────────────┐
송신 버퍼:   │ data ████████░░░░░░░░░░░░░░░░ │  ← 복사 완료, write() 반환
            └───────────────────────────────┘
                    │
                    ↓ (커널이 적절한 시점에)
            TCP 세그먼트로 전송

write()가 반환되어도 데이터가 상대방에게 도착한 것이 아닙니다. 버퍼에 복사되었을 뿐입니다.

송신 버퍼 상태 blocking 모드 non-blocking 모드
여유 공간 있음 복사 후 즉시 반환 복사 후 즉시 반환
가득 참 공간이 생길 때까지 대기 EAGAIN 에러 반환


수신 버퍼

네트워크에서 데이터가 도착하면 커널이 수신 버퍼에 저장합니다. read()를 호출하면 버퍼에서 데이터를 꺼내옵니다.

1
2
3
4
5
6
7
8
9
            TCP 세그먼트 도착
                    │
                    ↓ (커널이 자동으로)
            ┌───────────────────────────────┐
수신 버퍼:   │ data ████████░░░░░░░░░░░░░░░░ │  ← 애플리케이션이 read() 호출 전
            └───────────────────────────────┘
                    │
                    ↓ read(fd, buf, 1000)
            애플리케이션: buf에 데이터 복사
수신 버퍼 상태 blocking 모드 non-blocking 모드
데이터 있음 데이터 복사 후 반환 데이터 복사 후 반환
비어 있음 데이터가 도착할 때까지 대기 EAGAIN 에러 반환


버퍼와 윈도우 크기

TCP 흐름 제어에서 사용하는 윈도우 크기(Window Size)는 수신 버퍼의 여유 공간입니다.

1
2
3
4
5
6
7
수신측 버퍼 (64KB)
┌────────────────────────────────────────────────────────────────┐
│████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│
│← 사용 중: 20KB →  │←          여유 공간: 44KB                →│
└────────────────────────────────────────────────────────────────┘
                                    ↓
                    TCP 헤더의 Window 필드로 광고: "44KB 더 보내도 됨"

수신측은 매 ACK에 현재 윈도우 크기를 포함합니다. 송신측은 이 값을 보고 전송량을 조절합니다.

상황 윈도우 크기 송신측 동작
수신측이 빠르게 처리 계속 전송
수신측이 느리게 처리 작아짐 전송 속도 감소
수신 버퍼 가득 참 0 전송 중단 (Zero Window)


버퍼 크기 조정

버퍼 크기는 소켓 옵션으로 조정할 수 있습니다.

1
2
3
int bufsize = 1024 * 1024;  // 1MB
setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize));  // 송신 버퍼
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize));  // 수신 버퍼

고대역폭 장거리 연결에서는 큰 버퍼가 필요합니다. BDP(Bandwidth-Delay Product)는 “파이프에 채울 수 있는 데이터량”입니다.

1
2
3
4
BDP = 대역폭 × RTT (왕복 시간)

예: 1Gbps 링크, RTT 100ms
BDP = 1Gbps × 0.1s = 100Mb = 12.5MB

송신측은 ACK를 받기 전까지 BDP만큼의 데이터를 “날려 보내놓을” 수 있어야 합니다. 버퍼가 BDP보다 작으면 ACK를 기다리느라 대역폭을 완전히 활용하지 못합니다.

1
2
3
4
5
버퍼 < BDP:  전송 ──→ [대기] ──→ ACK 도착 ──→ 전송 ──→ [대기] ...
                      ↑ 대역폭 낭비

버퍼 ≥ BDP:  전송 ──→ 전송 ──→ 전송 ──→ ACK 도착 ──→ 전송 ──→ ...
             ↑ 연속 전송으로 대역폭 최대 활용

마무리

TCP 연결은 물리적 회선이 아니라 상태의 동기화입니다.

Part 1에서 소켓이 5-tuple로 식별되는 통신 끝점이라고 했습니다. 이번 글에서는 그 끝점들이 어떻게 상태를 맞추고, 유지하고, 정리하는지 살펴보았습니다.

주제 핵심 내용
11가지 상태 연결 수립 → 데이터 전송 → 연결 종료 과정의 상태 전이
3-Way Handshake 시퀀스 번호 동기화, 오래된 연결 요청 구분
4-Way Handshake 양방향 데이터 흐름을 독립적으로 종료 (Half-Close)
TIME_WAIT 지연된 패킷과 손실된 ACK에 대비 (2MSL 대기)
CLOSE_WAIT 애플리케이션이 close()를 호출해야 해결되는 상태
소켓 버퍼 애플리케이션과 네트워크 사이의 속도 차이 흡수

이 모든 상태 전이는 커널에서 자동으로 처리됩니다. 애플리케이션은 connect(), accept(), close()만 호출하면 됩니다.

1
2
3
4
5
애플리케이션:  connect() ─────────────────────────────→ close()
                  │                                        │
커널:         SYN → SYN+ACK → ACK    ...    FIN → ACK → FIN → ACK
                  │                                        │
상태:        SYN_SENT → ESTABLISHED ─────────→ FIN_WAIT → CLOSED

Part 3에서는 멀티플렉싱과 패킷 흐름을 살펴봅니다. 하나의 네트워크 인터페이스가 어떻게 수천 개의 연결을 동시에 처리하는지, 애플리케이션 데이터가 어떻게 패킷이 되어 네트워크로 나가는지 알아봅니다.



관련 글

시리즈

Tags: TCP, 네트워크, 상태머신, 소켓

Categories: ,