Sliding Window
Effective Window
Read aloud is unavailable in this browser.
Two Governors, One Throttle
Throughout this module, we've examined the sliding window from multiple perspectives: the sender's view, the receiver's view, and the scaling mechanisms that extend its range. But we've been studying only half the picture. TCP transmission isn't governed solely by the receiver's capacity—it's also governed by the network's capacity.
Imagine you're a trucking company. Your customer (the receiver) says, "I can accept 100 trucks per day." But the highway between you (the network) can only handle 50 trucks per day without causing gridlock. If you send 100 trucks, 50 will be stuck in traffic jams, deliveries will fail, and you'll waste fuel on retries.
The effective window is the smaller of these two limits: what the receiver can accept and what the network can carry. It represents the actual transmission budget—how many bytes the sender can have "in flight" at any moment without overwhelming either the receiver or the network.
This page brings together flow control and congestion control, showing how TCP balances these dual constraints to achieve reliable, efficient communication.
By the end of this page, you will understand: how the effective window is calculated, the relationship between rwnd and cwnd, how each constraint becomes the bottleneck in different scenarios, the interaction with bytes in flight, and practical implications for performance tuning.
The Two Windows
TCP maintains two window values that independently limit transmission:
1. Receive Window (rwnd): The receiver's advertised window
- Set by the receiver based on available buffer space
- Communicated in every ACK's Window field
- Represents receiver capacity constraint (flow control)
- Can be scaled via Window Scale option
2. Congestion Window (cwnd): The sender's inferred network capacity
- Calculated by the sender based on network feedback
- Not communicated over the network (purely local)
- Represents network capacity constraint (congestion control)
- Managed by slow start, congestion avoidance, fast recovery, etc.
| Aspect | Receive Window (rwnd) | Congestion Window (cwnd) |
|---|---|---|
| Purpose | Prevent receiver buffer overflow | Prevent network congestion |
| Set by | Receiver | Sender (algorithm) |
| Communicated | Yes, in Window field | No, internal to sender |
| Based on | Buffer available space | Packet loss, RTT, ECN signals |
| Changes | When receiver buffer state changes | Every ACK, timeout, or loss event |
| Scaling | Window Scale option | No scaling (internal 32-bit value) |
| Initial value | Receiver buffer size | Initial congestion window (10 MSS) |
| Minimum | 0 (zero window) | 1 MSS (minimum for probing) |
Neither window alone is sufficient. rwnd without cwnd would ignore network capacity, causing congestion collapse when many senders flood links. cwnd without rwnd would ignore receiver capacity, causing buffer overflow and data loss at receivers. TCP needs both to function correctly in the real Internet.
Effective Window Calculation
The effective window is simply the minimum of the two constraints:
Effective_Window = min(rwnd, cwnd)
This elegant formula captures the dual governance: the sender cannot transmit faster than either the receiver or the network allows.
The usable effective window:
The sender doesn't just look at the effective window—it considers what's already in flight:
Usable_Window = Effective_Window - Bytes_In_Flight
= min(rwnd, cwnd) - (SND.NXT - SND.UNA)
This is the amount of new data the sender can transmit right now. If usable window ≤ 0, the sender must wait for ACKs.
Scenario 1: Receiver-limited (rwnd < cwnd)--------------------------------------------rwnd = 10,000 bytes (receiver has limited buffer)cwnd = 50,000 bytes (network has capacity)Bytes in flight = 8,000 bytes Effective window = min(10000, 50000) = 10,000 bytesUsable window = 10000 - 8000 = 2,000 bytes The sender can send 2,000 more bytes. The receiver's buffer is the bottleneck. Scenario 2: Network-limited (cwnd < rwnd)--------------------------------------------rwnd = 128,000 bytes (receiver has plenty of buffer)cwnd = 20,000 bytes (congestion control limiting)Bytes in flight = 18,000 bytes Effective window = min(128000, 20000) = 20,000 bytesUsable window = 20000 - 18000 = 2,000 bytes The sender can send 2,000 more bytes. The network is the bottleneck. Scenario 3: Window exhausted--------------------------------------------rwnd = 30,000 bytescwnd = 25,000 bytesBytes in flight = 25,000 bytes Effective window = min(30000, 25000) = 25,000 bytesUsable window = 25000 - 25000 = 0 bytes Cannot send anything. Must wait for ACKs to reduce bytes in flight.Knowing which window is the bottleneck is crucial for performance tuning. If rwnd < cwnd, the receiver needs larger buffers. If cwnd < rwnd, the network path has congestion or the connection is still ramping up. Packet captures showing advertised window vs. throughput can reveal which limit applies.
Dynamic Behavior
Both rwnd and cwnd change over time, which means the effective window is constantly shifting. Understanding these dynamics is key to understanding TCP behavior.
How rwnd changes:
- Decreases when data arrives faster than the application reads
- Increases when the application reads data from the buffer
- Drops to 0 when buffer is full (zero window condition)
- Jumps up when application catches up on reads
How cwnd changes:
- Starts small (initial cwnd, typically 10 MSS = ~14KB)
- Grows exponentially during slow start (doubles each RTT)
- Grows linearly during congestion avoidance (adds ~1 MSS per RTT)
- Drops sharply on packet loss (halved or worse)
- Recovers gradually after loss event
The bottleneck can shift:
A connection might start network-limited (cwnd < rwnd during slow start), then become receiver-limited once cwnd is large enough. Or vice versa—a receiver that was keeping up might fall behind if the application stalls.
Typical life cycle of a bulk transfer:
| Phase | rwnd (typical) | cwnd | Bottleneck | Effective Window |
|---|---|---|---|---|
| Connection start | Large (64KB+) | 10 MSS (~14KB) | cwnd | ~14 KB (slow start) |
| Slow start | Large | Growing exponentially | cwnd | Doubling each RTT |
| Approaching BDP | Large | Approaching optimal | cwnd | Nearing link capacity |
| Steady state (network limited) | Large | Stable ~BDP | cwnd | Link fully utilized |
| Steady state (receiver limited) | Constrained | Large | rwnd | Limited by receiver |
| After packet loss | Stable | Reduced (halved) | cwnd | Recovery in progress |
| Zero window | 0 | Any value | rwnd=0 | Zero, waiting for receiver |
Sender-Side Algorithm
The sender's transmission logic must correctly apply the effective window. Here's the detailed algorithm:
State maintained:
SND.UNA: Oldest unacknowledged byteSND.NXT: Next byte to sendrwnd: Last advertised receive window (from latest ACK)cwnd: Current congestion window (from congestion control algorithm)
Transmission decision:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
function can_send_data(): // Calculate bytes currently in flight bytes_in_flight = SND.NXT - SND.UNA // Calculate effective window effective_window = min(rwnd, cwnd) // Calculate usable window usable_window = effective_window - bytes_in_flight // Also need data to send data_available = send_buffer_has_unsent_data() return usable_window > 0 AND data_available function determine_bytes_to_send(): bytes_in_flight = SND.NXT - SND.UNA effective_window = min(rwnd, cwnd) usable_window = effective_window - bytes_in_flight // Cannot send more than usable window max_by_window = usable_window // Cannot send more than MSS per segment max_by_mss = MSS // Cannot send more than available data max_by_data = bytes_available_in_send_buffer() // Nagle's algorithm may delay small segments if nagle_enabled AND bytes_in_flight > 0: if max_by_data < MSS: return 0 // Wait for more data or ACK return min(max_by_window, max_by_mss, max_by_data) function on_ack_received(ack): // Update rwnd from ACK rwnd = ack.window_field << snd_scale // Update SND.UNA if ack.ack_number > SND.UNA: SND.UNA = ack.ack_number // Congestion control updates cwnd cwnd = congestion_control_update(ack) // Check if we can now send more data if can_send_data(): schedule_transmission()The constraint bytes_in_flight ≤ min(rwnd, cwnd) must never be violated. Exceeding rwnd risks receiver buffer overflow. Exceeding cwnd risks network congestion. Either mistake causes problems—the former at the receiver, the latter in the network (affecting other connections too).
Which Limit Matters When?
Different scenarios cause different windows to be the bottleneck. Let's examine common cases:
1. Short connections (request-response):
For connections transferring small amounts of data (HTTP request/response, database queries):
- cwnd is almost always the limit (connection is still in slow start)
- rwnd rarely matters (small transfer completes before buffer fills)
- Throughput limited by initial cwnd and RTT
2. Bulk transfers to fast receivers:
For large file transfers where the receiver can keep up:
- cwnd grows to match network capacity
- rwnd stays large (receiver reads quickly)
- After ramp-up, cwnd is the limit (network capacity)
- Throughput = cwnd / RTT ≈ link capacity
- •Application reads data slowly
- •Small receive buffer configured
- •Receiver under memory pressure
- •Cross-layer congestion (disk I/O blocking app)
- •Receiver CPU overloaded
- •Connection in slow start (ramp-up)
- •Recent packet loss (cwnd reduced)
- •Congested network path
- •Low-bandwidth link (cwnd matches link)
- •Shared bottleneck with many flows
3. Streaming to slow consumers:
When sending data to a receiver that processes slowly (e.g., video player with full buffer):
- cwnd may be large (network has capacity)
- rwnd drops as receiver buffer fills
- Eventually rwnd becomes the limit
- Throughput = application consumption rate
4. High-BDP paths:
For long-haul, high-bandwidth connections:
- Both windows can be large
- cwnd takes many RTTs to ramp up (slow start duration = RTT × log₂(BDP/initial_cwnd))
- After ramp-up, whichever is smaller matters
- Window scaling essential for rwnd; cwnd has no scaling limit
To determine which window limits a connection: (1) Capture packets and observe advertised window in ACKs, (2) Calculate throughput achieved, (3) If throughput ≈ rwnd/RTT, receiver-limited. If throughput < rwnd/RTT, network-limited. Tools like "ss -i" on Linux show both cwnd and rwnd for active connections.
Bandwidth-Delay Product Revisited
The effective window interacts directly with the bandwidth-delay product (BDP) to determine achievable throughput.
The fundamental relationship:
Maximum Throughput = min(Effective_Window / RTT, Link_Bandwidth)
For maximum throughput:
- Effective window must be ≥ BDP
- This means both rwnd and cwnd must be ≥ BDP
- If either is less than BDP, throughput is limited below link capacity
| Network Type | Bandwidth | RTT | BDP | Required min(rwnd, cwnd) |
|---|---|---|---|---|
| LAN | 1 Gbps | 1 ms | 125 KB | ≥ 125 KB |
| Metro WAN | 1 Gbps | 10 ms | 1.25 MB | ≥ 1.25 MB |
| Cross-country | 10 Gbps | 50 ms | 62.5 MB | ≥ 62.5 MB |
| Transatlantic | 100 Gbps | 80 ms | 1 GB | ≥ 1 GB |
| Satellite (GEO) | 100 Mbps | 600 ms | 7.5 MB | ≥ 7.5 MB |
Why both windows matter:
Consider a 10 Gbps transatlantic link (BDP = 100 MB):
| Scenario | rwnd | cwnd | Effective Window | Throughput |
|---|---|---|---|---|
| Both adequate | 128 MB | 110 MB | 110 MB | ~10 Gbps |
| Receiver-limited | 20 MB | 110 MB | 20 MB | ~2 Gbps |
| Network-limited (loss) | 128 MB | 10 MB | 10 MB | ~1 Gbps |
| Both undersized | 20 MB | 10 MB | 10 MB | ~1 Gbps |
The smaller window is always the bottleneck. To achieve full throughput, both windows must exceed the BDP.
To maximize throughput: (1) Set receive buffers ≥ BDP (OS configuration), (2) Ensure window scaling is enabled, (3) Allow congestion control to grow cwnd to BDP (minimize loss), (4) Use congestion control algorithms suited to high-BDP (e.g., BBR, CUBIC). Tuning only one window while ignoring the other yields no benefit.
Performance Tuning
Understanding the effective window enables targeted performance tuning. Here are practical recommendations:
Tuning rwnd (receiver-side):
- •Increase receive buffer size: On Linux, adjust
net.core.rmem_maxandnet.ipv4.tcp_rmem. On Windows, usenetsh int tcp set global autotuninglevel=experimental. - •Enable auto-tuning: Most modern OSes auto-tune by default, but verify it's active.
- •Ensure application reads promptly: A slow application constrains rwnd regardless of buffer size.
- •Use larger reads: Frequent small reads add syscall overhead; batch them when possible.
- •Monitor delayed ACKs: Excessive delayed ACK latency slows window updates.
- •Increase initial cwnd: Linux default is 10 MSS; some admins increase for high-BDP paths.
- •Choose appropriate CC algorithm: CUBIC (Linux default), BBR, or others depending on network characteristics.
- •Minimize packet loss: Loss triggers cwnd reduction; improve link quality and avoid congestion.
- •Use larger send buffers: Ensures data is available when cwnd allows transmission.
- •Enable SACK: Selective acknowledgments improve recovery, allowing faster cwnd restoration.
123456789101112131415161718
# View current settingssysctl net.ipv4.tcp_rmemsysctl net.ipv4.tcp_wmemsysctl net.core.rmem_maxsysctl net.core.wmem_max # Increase maximum buffer sizes (example for high-BDP networks)sudo sysctl -w net.core.rmem_max=67108864 # 64 MB max receive buffersudo sysctl -w net.core.wmem_max=67108864 # 64 MB max send buffersudo sysctl -w net.ipv4.tcp_rmem="4096 131072 67108864" # min, default, maxsudo sysctl -w net.ipv4.tcp_wmem="4096 131072 67108864" # Verify window scaling is enabled (should be 1)sysctl net.ipv4.tcp_window_scaling # Check current connection windows (on Linux)ss -i | grep -A1 "ESTAB"# Output includes cwnd and rwnd for each connectionVery large buffers consume memory—especially multiplied across thousands of connections. Balance performance needs against resource availability. For a server handling 10,000 connections, a 64 MB buffer per connection would require 640 GB of RAM for receive buffers alone.
KEEP LEARNING
There’s more to understand.
You’re reading the introduction. Create a free account to read the complete lesson, save notes and keep track of your progress.