본 포스팅은 James Kurose, Keith Ross의 Computer Networking: A Top-Down Approach (6th Edition) 교재를 기반으로 정리한 내용입니다.
1. TCP 연결
1.1 TCP 연결의 특징
TCP 연결의 핵심 속성:
├── 연결 지향 (Connection-Oriented)
│ 데이터 전송 전에 반드시 연결 수립
├── 전이중 (Full-Duplex)
│ 양방향 동시 데이터 전송
├── 점대점 (Point-to-Point)
│ 정확히 하나의 송신자와 하나의 수신자
└── 바이트 스트림 서비스
메시지 경계 없이 연속된 바이트 흐름
1.2 3-Way Handshake
TCP 연결 수립 과정이다.
클라이언트 서버
| |
|── SYN (seq=client_isn) ──────>| 1단계: SYN
| |
|<── SYN+ACK ──────────────────| 2단계: SYN+ACK
| (seq=server_isn, |
| ack=client_isn+1) |
| |
|── ACK (ack=server_isn+1) ───>| 3단계: ACK
| (데이터 포함 가능) |
| |
각 단계 설명
1단계 - SYN:
- 클라이언트가 SYN 세그먼트 전송
- SYN 비트 = 1
- 초기 순서 번호(client_isn) 설정
- 데이터 없음
2단계 - SYN+ACK:
- 서버가 SYN+ACK 세그먼트 응답
- SYN 비트 = 1, ACK 비트 = 1
- 서버의 초기 순서 번호(server_isn) 설정
- 확인 번호 = client_isn + 1
3단계 - ACK:
- 클라이언트가 ACK 세그먼트 전송
- SYN 비트 = 0 (연결 수립 완료)
- 이 세그먼트부터 데이터 포함 가능
1.3 TCP 연결 종료 (4-Way Handshake)
클라이언트 서버
| |
|── FIN ───────────────────────>| 1단계
| |
|<── ACK ──────────────────────| 2단계
| |
|<── FIN ──────────────────────| 3단계
| |
|── ACK ───────────────────────>| 4단계
| |
| (TIME_WAIT: 30초 대기) |
| → 연결 완전 종료 |
> TIME_WAIT 상태: 마지막 ACK가 손실되었을 때 FIN 재전송에 대응하기 위해 일정 시간 대기한다.
2. TCP 세그먼트 구조
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
┌───────────────────────┬───────────────────────┐
│ 출발지 포트 (16) │ 목적지 포트 (16) │
├───────────────────────┴───────────────────────┤
│ 순서 번호 (32) │
├───────────────────────────────────────────────┤
│ 확인 번호 (32) │
├────────┬──────┬───────┬───────────────────────┤
│헤더길이 │미사용│플래그 │ 수신 윈도우 (16) │
│ (4) │ (6) │(6bits)│ │
├────────┴──────┴───────┼───────────────────────┤
│ 체크섬 (16) │ 긴급 포인터 (16) │
├───────────────────────┴───────────────────────┤
│ 옵션 (가변 길이) │
├───────────────────────────────────────────────┤
│ │
│ 데이터 (가변 길이) │
│ │
└───────────────────────────────────────────────┘
주요 필드
| 필드 | 크기 | 설명 |
| ----------- | ------- | --------------------------------- |
| 순서 번호 | 32 bits | 세그먼트 데이터의 첫 바이트 번호 |
| 확인 번호 | 32 bits | 기대하는 다음 바이트 번호 |
| 수신 윈도우 | 16 bits | 수신 가능한 바이트 수 (흐름 제어) |
| 헤더 길이 | 4 bits | TCP 헤더 길이 (4바이트 단위) |
| 플래그 비트 | 6 bits | SYN, ACK, FIN, RST, PSH, URG |
3. 순서 번호와 확인 번호
3.1 순서 번호 (Sequence Number)
세그먼트에 포함된 데이터의 **첫 번째 바이트의 바이트 스트림 번호**다.
예시: 500,000 바이트 파일, MSS = 1,000 바이트
세그먼트 1: seq = 0, 데이터 = 바이트 0~999
세그먼트 2: seq = 1000, 데이터 = 바이트 1000~1999
세그먼트 3: seq = 2000, 데이터 = 바이트 2000~2999
...
세그먼트 500: seq = 499000, 데이터 = 바이트 499000~499999
3.2 확인 번호 (Acknowledgment Number)
수신자가 **다음으로 기대하는 바이트 번호**다. 누적 확인(cumulative acknowledgment) 방식이다.
예시: 텔넷 세션에서 'C'를 입력
호스트 A 호스트 B
|── seq=42, ack=79, 'C' ───>|
| | 'C' 수신, 에코 응답
|<── seq=79, ack=43, 'C' ───|
| |
|── seq=43, ack=80 ──────── >| (ACK)
seq=42: A가 보내는 데이터의 42번째 바이트
ack=79: A가 B로부터 79번째 바이트를 기대
ack=43: B가 A로부터 43번째 바이트를 기대 (42번 수신 완료)
4. RTT 추정과 타임아웃
4.1 SampleRTT
실제 측정한 RTT 값이다. 세그먼트 전송 시점부터 ACK 수신 시점까지의 시간이다.
- 재전송된 세그먼트는 측정에서 제외
- SampleRTT는 변동이 크다
4.2 EstimatedRTT (지수 가중 이동 평균)
EstimatedRTT = (1 - alpha) * EstimatedRTT + alpha * SampleRTT
alpha = 0.125 (권장값)
→ 최근 측정값에 더 큰 가중치
→ SampleRTT의 급격한 변동을 완화
4.3 DevRTT (RTT 편차)
DevRTT = (1 - beta) * DevRTT + beta * |SampleRTT - EstimatedRTT|
beta = 0.25 (권장값)
4.4 타임아웃 간격
TimeoutInterval = EstimatedRTT + 4 * DevRTT
EstimatedRTT: 평균 RTT 추정
4 * DevRTT: 안전 마진 (변동성 고려)
RTT 추정 예시 (시간 경과에 따른 변화):
RTT
(ms)
^
│ * *
│ * * * * SampleRTT (변동 큼)
│ ** * * *
│ ___________*____ EstimatedRTT (안정적)
│ TimeoutInterval (상한선)
└──────────────────> 시간
5. TCP 신뢰적 데이터 전송
TCP는 IP의 비신뢰적 서비스 위에 신뢰적 전송을 구현한다.
5.1 TCP 송신자의 핵심 이벤트
이벤트 1: 상위 계층으로부터 데이터 수신
→ 세그먼트 생성, 순서 번호 부여
→ IP에 전달
→ 타이머가 실행 중이 아니면 시작
이벤트 2: 타이머 만료
→ 가장 오래된 미확인 세그먼트 재전송
→ 타이머 재시작
이벤트 3: ACK 수신
→ SendBase 업데이트 (누적 확인)
→ 아직 미확인 세그먼트가 있으면 타이머 재시작
5.2 TCP 재전송 시나리오
시나리오 1: ACK 손실
호스트 A 호스트 B
|── seq=92, 8바이트 ───>|
| |── ACK=100 (손실!)
| 타이머 만료 |
|── seq=92, 8바이트 ───>| (재전송)
|<── ACK=100 ───────── |
시나리오 2: 조기 타임아웃
호스트 A 호스트 B
|── seq=92, 8바이트 ───>|
|── seq=100, 20바이트 ─>|
| |── ACK=100
| 타이머 만료! |── ACK=120
| (ACK=100이 아직 도착 안 함)
|── seq=92, 8바이트 ───>| (불필요한 재전송)
|<── ACK=100 ───────── |
|<── ACK=120 ───────── | (누적 ACK로 자연 해결)
시나리오 3: 누적 ACK
호스트 A 호스트 B
|── seq=92, 8바이트 ───>|
|── seq=100, 20바이트 ─>|
| |── ACK=100 (손실!)
| |── ACK=120 (도착)
|<── ACK=120 ───────── |
ACK=120은 120 이전의 모든 바이트 확인
→ seq=92 패킷도 확인된 것!
→ 재전송 불필요
5.3 빠른 재전송 (Fast Retransmit)
타임아웃을 기다리지 않고, **3개의 중복 ACK**를 받으면 즉시 재전송한다.
호스트 A 호스트 B
|── seq=92 ────────────>| ACK=100
|── seq=100 ───────────>| (손실!)
|── seq=120 ───────────>| ACK=100 (중복 1)
|── seq=140 ───────────>| ACK=100 (중복 2)
|── seq=160 ───────────>| ACK=100 (중복 3)
| |
| 3개 중복 ACK 수신! |
| 타이머 만료 전에 즉시 재전송
|── seq=100 ───────────>| ACK=180 (누적 확인)
> 3개의 중복 ACK = 원래 ACK + 추가 3개 = 총 4번의 동일한 ACK
6. 흐름 제어 (Flow Control)
6.1 문제
송신자가 너무 빠르게 데이터를 보내면 **수신자의 버퍼가 넘칠** 수 있다.
수신 측 버퍼:
┌──────────────────────────────┐
│[데이터][데이터][ 빈 공간 ]│
└──────────────────────────────┘
│←── 사용 중 ──→│←── rwnd ────→│
수신 윈도우
6.2 수신 윈도우 (Receive Window)
TCP는 **수신 윈도우(`rwnd`)** 를 통해 흐름 제어를 수행한다.
rwnd = RcvBuffer - (LastByteRcvd - LastByteRead)
RcvBuffer: 수신 버퍼의 전체 크기
LastByteRcvd: 마지막으로 수신된 바이트 번호
LastByteRead: 상위 계층이 읽어간 마지막 바이트 번호
6.3 동작 방식
수신자: ACK에 rwnd 값을 포함하여 전송
ACK 세그먼트:
┌────────┬────────┐
│ ACK=N │ rwnd=X │ "N번째 바이트까지 받았고,
└────────┴────────┘ X 바이트만큼 더 받을 수 있어"
송신자: rwnd를 초과하지 않도록 전송량 제한
LastByteSent - LastByteAcked <= rwnd
rwnd가 줄어들면 → 전송 속도 감소
rwnd가 0이면 → 전송 중단 (단, 1바이트 프로브 패킷 전송)
rwnd = 0 일 때의 문제
문제: 수신자가 rwnd=0을 보낸 후 버퍼를 비워도,
송신자에게 알릴 방법이 없음 (보낼 데이터가 없으므로)
해결: 송신자가 주기적으로 1바이트 프로브(probe) 세그먼트 전송
→ 수신자가 현재 rwnd 값을 포함한 ACK로 응답
→ 빈 공간이 생기면 송신 재개
7. TCP 혼잡 제어 (Congestion Control)
7.1 혼잡이란
혼잡(Congestion):
네트워크 내 데이터 양이 네트워크 용량을 초과하는 상태
증상:
├── 패킷 손실 (라우터 버퍼 오버플로)
├── 긴 지연 (라우터 큐에서 대기)
└── 불필요한 재전송 (타임아웃으로 인한)
7.2 혼잡 제어 vs 흐름 제어
흐름 제어: 수신자 보호 (수신 버퍼 오버플로 방지)
→ rwnd로 제어
혼잡 제어: 네트워크 보호 (네트워크 과부하 방지)
→ cwnd(congestion window)로 제어
실제 전송량:
LastByteSent - LastByteAcked <= min(cwnd, rwnd)
7.3 AIMD (Additive Increase Multiplicative Decrease)
TCP 혼잡 제어의 기본 철학이다.
cwnd 조정 원칙:
패킷 손실 없음 (ACK 정상 수신):
→ cwnd 증가 (대역폭 탐색)
패킷 손실 발생 (타임아웃 또는 3중복 ACK):
→ cwnd 감소 (혼잡 완화)
"톱니 형태" 패턴:
cwnd
^
│ /\ /\ /\
│ / \ / \ / \
│ / \ / \ / \
│ / \/ \/ \
└──────────────────────────> 시간
7.4 슬로 스타트 (Slow Start)
연결 초기에 cwnd를 **지수적으로 증가**시킨다.
초기 cwnd = 1 MSS
각 ACK 수신 시: cwnd += 1 MSS
(한 RTT에 모든 ACK를 받으면 cwnd가 2배)
cwnd 변화:
RTT 1: cwnd = 1 MSS → 1개 세그먼트 전송
RTT 2: cwnd = 2 MSS → 2개 세그먼트 전송
RTT 3: cwnd = 4 MSS → 4개 세그먼트 전송
RTT 4: cwnd = 8 MSS → 8개 세그먼트 전송
...
지수적 증가: 1, 2, 4, 8, 16, 32, ...
슬로 스타트의 종료 조건
1. cwnd >= ssthresh (느린 시작 임계값) → 혼잡 회피로 전환
2. 타임아웃 발생 → ssthresh = cwnd/2, cwnd = 1 MSS, 슬로 스타트 재시작
3. 3개 중복 ACK → ssthresh = cwnd/2, cwnd = ssthresh + 3, 빠른 회복
7.5 혼잡 회피 (Congestion Avoidance)
cwnd가 ssthresh에 도달한 후 **선형적으로 증가**시킨다.
각 RTT마다: cwnd += 1 MSS
(ACK 하나당: cwnd += MSS * MSS / cwnd)
cwnd 변화 (MSS 단위):
RTT n: cwnd = 10
RTT n+1: cwnd = 11
RTT n+2: cwnd = 12
...
선형 증가: 10, 11, 12, 13, ...
혼잡 이벤트 시 동작
타임아웃 발생:
ssthresh = cwnd / 2
cwnd = 1 MSS
→ 슬로 스타트로 복귀
3개 중복 ACK:
ssthresh = cwnd / 2
cwnd = ssthresh + 3 MSS
→ 빠른 회복으로 전환
7.6 빠른 회복 (Fast Recovery)
3개의 중복 ACK를 받았을 때의 상태다. TCP Reno에서 도입되었다.
빠른 회복 동작:
1. 중복 ACK 수신할 때마다: cwnd += 1 MSS
2. 새 ACK 수신 (손실 복구 완료):
cwnd = ssthresh
→ 혼잡 회피로 전환
3. 타임아웃 발생:
ssthresh = cwnd / 2
cwnd = 1 MSS
→ 슬로 스타트로 복귀
7.7 TCP 혼잡 제어 전체 상태 다이어그램
┌─────────────┐
시작 ───────>│ 슬로 스타트 │
│ cwnd 지수 증가│
└──────┬──────┘
│
cwnd >= ssthresh
│
┌──────▼──────┐
┌────>│ 혼잡 회피 │<────┐
│ │ cwnd 선형 증가│ │
│ └──────┬──────┘ │
│ │ │
새 ACK 3중복 ACK 타임아웃
(복구완료) │ │
│ ┌──────▼──────┐ │
│ │ 빠른 회복 │ │
└─────│ cwnd 조정 │ │
└─────────────┘ │
│ │
타임아웃 │
└────────────┘
ssthresh=cwnd/2
cwnd=1 MSS
→ 슬로 스타트
7.8 TCP Tahoe vs TCP Reno
cwnd
^
│ *
│ * *
│ * * TCP Reno
│ * * /
│ * * *
│ * * * *
│ * ** *
│ * *
│ *
│ * TCP Tahoe: 항상 cwnd=1로 복귀
│ *
└─────────────────────────────> 시간
↑ ↑
손실1 손실2
| 이벤트 | TCP Tahoe | TCP Reno |
| --------- | ------------ | ------------------------- |
| 타임아웃 | cwnd = 1 MSS | cwnd = 1 MSS |
| 3중복 ACK | cwnd = 1 MSS | cwnd = cwnd/2 (빠른 회복) |
8. TCP 공정성 (Fairness)
8.1 공정성의 정의
용량 `R`인 병목 링크를 `K`개의 TCP 연결이 공유할 때, 각 연결이 `R/K`의 처리량을 얻는 것이 이상적이다.
8.2 TCP가 공정한 이유
두 연결이 대역폭 R을 공유하는 경우:
처리량2
^
│ ╲ 공정 지점
│ ╲ (R/2, R/2)
│ ╲ *
R │ ╲ / \
│ * *
│ / ╲ \
│ / * *
│ / / ╲ \
│ / / * * → 공정 지점으로 수렴
└──────────────────> 처리량1
0 R
AIMD가 공정 지점으로 수렴하는 과정:
1. 두 연결이 균등하게 cwnd 증가 (Additive Increase)
→ 45도 방향으로 이동 (총합 = R 선을 향해)
2. 손실 발생 시 각각 cwnd를 절반으로 (Multiplicative Decrease)
→ 원점 방향으로 이동
3. 반복하면 공정 지점(R/2, R/2)으로 수렴
8.3 공정성의 현실적 한계
UDP는 혼잡 제어를 하지 않음:
→ TCP가 양보한 대역폭을 UDP가 차지
→ TCP에 불공정
병렬 TCP 연결:
→ 하나의 애플리케이션이 여러 TCP 연결을 열면
→ 단일 연결 애플리케이션보다 더 많은 대역폭 차지
→ 예: 브라우저가 동시에 여러 연결로 웹 객체 다운로드
9. 정리
TCP 핵심 메커니즘 요약:
연결 관리:
├── 3-way handshake (SYN → SYN+ACK → ACK)
└── 4-way handshake 종료 (FIN → ACK → FIN → ACK)
신뢰적 전송:
├── 순서 번호 + 확인 번호 (누적 ACK)
├── 타이머 기반 재전송
├── 빠른 재전송 (3중복 ACK)
└── RTT 추정 (EWMA)
흐름 제어:
└── rwnd (수신 윈도우)로 수신자 보호
혼잡 제어:
├── 슬로 스타트: 지수적 증가
├── 혼잡 회피: 선형적 증가
├── 빠른 회복: 3중복 ACK 시 cwnd 절반
└── AIMD → 공정한 대역폭 공유
10. 확인 문제
1번 (SYN): 클라이언트가 연결을 요청하고 자신의 초기 순서 번호를 알린다.
2번 (SYN+ACK): 서버가 연결을 수락하고 자신의 초기 순서 번호를 알리며, 클라이언트의 순서 번호를 확인한다.
3번 (ACK): 클라이언트가 서버의 순서 번호를 확인한다.
이 과정을 통해 양측 모두 상대방의 초기 순서 번호를 확인하고, 연결 의사를 확인한다. 2번만으로는 서버가 보낸 SYN+ACK를 클라이언트가 수신했는지 서버가 알 수 없다.
- **흐름 제어**: **수신자**를 보호한다. 수신 버퍼가 넘치지 않도록 `rwnd`(수신 윈도우)를 사용하여 송신자의 전송량을 제한한다.
- **혼잡 제어**: **네트워크**를 보호한다. 네트워크가 과부하되지 않도록 `cwnd`(혼잡 윈도우)를 사용하여 송신자의 전송량을 제한한다.
실제 전송량은 `min(cwnd, rwnd)`에 의해 결정된다.
"느린 시작"이라는 이름은 초기 `cwnd`가 **1 MSS**로 매우 작게 시작한다는 데서 유래한다. TCP 이전의 프로토콜들은 연결 시작 시 허용된 최대 윈도우 크기로 바로 데이터를 쏟아부었는데, 이는 네트워크 혼잡을 유발했다. 슬로 스타트는 이에 비해 "느리게" 1 MSS부터 시작한다는 의미다. 물론 지수적 증가이므로 실제로는 빠르게 증가한다.
3중복 ACK는 특정 세그먼트만 손실되었지만 **그 이후의 세그먼트는 도착했다**는 의미다. 네트워크가 여전히 일부 패킷을 전달하고 있으므로 혼잡이 심각하지 않다고 판단하여 `cwnd`를 절반으로만 줄인다 (빠른 회복).
반면 타임아웃은 **아무 ACK도 오지 않는** 상황으로, 네트워크 혼잡이 심각하다고 판단하여 `cwnd`를 1 MSS로 초기화하고 슬로 스타트부터 다시 시작한다.
현재 단락 (1/352)
본 포스팅은 James Kurose, Keith Ross의 Computer Networking: A Top-Down Approach (6th Edition) 교재를 기반으로 정리한...