[Daily morning study] TCP 흐름 제어(Flow Control)와 슬라이딩 윈도우 프로토콜
#daily morning study
흐름 제어란
TCP는 신뢰성 있는 데이터 전송을 보장하는데, 이를 위해 두 가지 속도 제어 메커니즘을 사용한다.
- 흐름 제어(Flow Control): 수신자의 처리 능력에 맞게 송신 속도를 조절
- 혼잡 제어(Congestion Control): 네트워크 전체의 혼잡 상태에 맞게 속도를 조절
혼잡 제어가 네트워크 관점이라면, 흐름 제어는 송신자 ↔ 수신자 사이의 end-to-end 속도 조절이다. 수신자가 처리하지 못한 데이터가 버퍼에 쌓여 오버플로우가 발생하는 상황을 막는 게 핵심 목적이다.
수신 버퍼와 rwnd
수신자는 수신 버퍼(receive buffer)를 가지고 있고, 애플리케이션이 데이터를 읽어가는 속도보다 패킷이 더 빠르게 들어오면 버퍼가 꽉 찬다.
TCP는 이를 해결하기 위해 수신 윈도우(rwnd, receiver window) 값을 ACK 패킷의 헤더에 담아 송신자에게 전달한다.
TCP 헤더 구조 (일부)
+------------------+------------------+
| Source Port | Destination Port |
+------------------+------------------+
| Sequence Number |
+-------------------------------------+
| Acknowledgment Number |
+------------------+------------------+
| Offset | Flags | Window Size | ← rwnd
+------------------+------------------+
송신자는 ACK를 받을 때마다 rwnd 값을 확인하고, 이 값을 초과하지 않는 범위에서 데이터를 전송한다.
송신 가능한 데이터 양 = min(cwnd, rwnd)
- cwnd: 혼잡 제어 윈도우
- rwnd: 흐름 제어 윈도우
슬라이딩 윈도우 프로토콜
초기에는 데이터를 하나 보내고 ACK를 받으면 다음 데이터를 보내는 Stop-and-Wait 방식이 있었다. 간단하지만 왕복 시간(RTT)만큼 대기해야 하므로 효율이 매우 낮다.
Stop-and-Wait:
송신자: [data1] ------>
수신자: [ACK1] ------>
송신자: [data2] ------>
수신자: [ACK2] ------>
슬라이딩 윈도우는 ACK를 기다리지 않고 윈도우 크기만큼 연속으로 데이터를 전송할 수 있게 한다.
윈도우 크기 = 4인 경우:
송신자: [1][2][3][4] ------> (동시에 4개 전송)
수신자: [ACK2] ------> (1,2 수신 확인)
송신자: [3][4][5][6] ------> (윈도우 슬라이드)
윈도우가 오른쪽으로 이동(슬라이드)하며 전송 가능한 범위가 갱신되는 것이 핵심이다.
윈도우 상태 분류
TCP 송신자 측에서 데이터는 아래 4가지 상태 중 하나다.
|---- 전송 완료 + ACK 받음 ----|---- 전송 완료 + ACK 대기 ----|---- 전송 가능 ----|---- 전송 불가 ----|
Already ACKed Sent, Not ACKed Can Send Cannot Send
(in-flight) (in window)
|<----------------- 윈도우 범위 ----------------->|
- Already ACKed: 수신자가 이미 확인한 데이터 — 버퍼에서 제거 가능
- Sent, Not ACKed: 전송했지만 ACK 대기 중인 데이터 (in-flight)
- Can Send: 윈도우 내에 있어 즉시 전송 가능한 데이터
- Cannot Send: rwnd를 초과하여 현재 전송 불가한 데이터
Zero Window와 Window Probe
수신자의 버퍼가 꽉 찼을 때 rwnd = 0을 전송한다. 이 상태를 Zero Window라고 한다.
문제는 rwnd = 0을 받은 송신자가 전송을 멈추고, 수신자가 버퍼를 비워도 이를 알릴 방법이 없다는 것이다. 수신자가 rwnd > 0인 ACK를 보내지 않으면 데드락 상태가 된다.
이를 해결하기 위해 TCP는 Window Probe 패킷을 사용한다.
수신자: rwnd=0 → 송신자: 전송 중단
(수신자 버퍼 비워짐)
송신자: Window Probe 전송 (작은 패킷으로 rwnd 재확인 요청)
수신자: rwnd=8192 (버퍼 여유 생김) → 송신자: 전송 재개
Window Probe는 일정 간격으로 계속 전송되며, Persist Timer에 의해 관리된다.
Silly Window Syndrome
슬라이딩 윈도우에서 발생할 수 있는 비효율적인 동작이다.
수신 측 SWS: 수신 버퍼가 1바이트만 비워졌는데 rwnd=1을 즉시 알리는 경우. 송신자는 1바이트만 보내고, 이를 위해 40바이트 TCP/IP 헤더가 붙어 오버헤드가 커진다.
송신 측 SWS: 애플리케이션이 1바이트씩만 데이터를 쓰는데, TCP가 1바이트 패킷을 즉시 보내는 경우.
해결책
| 문제 | 해결책 | 설명 |
|---|---|---|
| 수신 측 SWS | Clark 해법 | 버퍼 여유가 MSS 또는 버퍼 절반 이상일 때만 rwnd 갱신 |
| 송신 측 SWS | Nagle 알고리즘 | ACK를 받기 전까지 소규모 데이터를 버퍼링해 모아서 전송 |
Nagle 알고리즘 동작:
if 전송할 데이터 >= MSS:
즉시 전송
elif 미확인 패킷 없음:
즉시 전송
else:
버퍼에 쌓아두고 ACK 받을 때 전송
단, 실시간 애플리케이션(게임, SSH 등)은 지연을 허용하지 않으므로 TCP_NODELAY 옵션으로 Nagle 알고리즘을 비활성화한다.
흐름 제어 vs 혼잡 제어
| 구분 | 흐름 제어 | 혼잡 제어 |
|---|---|---|
| 목적 | 수신자 버퍼 오버플로우 방지 | 네트워크 혼잡 방지 |
| 제어 기준 | 수신자의 rwnd | 네트워크 상태(패킷 손실 등) |
| 신호 | ACK 헤더의 Window Size | 패킷 손실, ACK 지연 |
| 윈도우 | rwnd | cwnd |
| 알고리즘 | 슬라이딩 윈도우 | Slow Start, AIMD, CUBIC 등 |
실제 전송 가능한 데이터 양은 두 윈도우 중 작은 값으로 결정된다.
effective window = min(rwnd, cwnd)
실제 동작 예시
초기 상태:
- 수신 버퍼: 65535 bytes
- rwnd: 65535
데이터 전송 시작:
- 송신자: 16384 bytes 전송
- 수신자: rwnd = 65535 - 16384 = 49151 → ACK에 포함
수신자 처리 완료:
- 애플리케이션이 8192 bytes 읽음
- 수신자: rwnd = 49151 + 8192 = 57343 → 다음 ACK에 포함
버퍼 포화:
- rwnd = 0 전송 → 송신자 일시 정지
- Window Probe로 상태 재확인 후 전송 재개
정리
- 흐름 제어는 수신자 버퍼 용량에 맞게 송신 속도를 조절하는 메커니즘이다.
- 슬라이딩 윈도우를 통해 ACK를 기다리지 않고 연속으로 데이터를 전송할 수 있다.
- rwnd가 0이 되면 Window Probe로 수신자 상태를 주기적으로 확인한다.
- Silly Window Syndrome 방지를 위해 Clark 해법과 Nagle 알고리즘을 사용한다.
- 실제 전송량은 흐름 제어 윈도우(rwnd)와 혼잡 제어 윈도우(cwnd) 중 작은 값으로 결정된다.