Loading learning content...
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.
TCP maintains two window values that independently limit transmission:
1. Receive Window (rwnd): The receiver's advertised window
2. Congestion Window (cwnd): The sender's inferred network capacity
| 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.
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.
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:
How cwnd changes:
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 |
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).
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):
2. Bulk transfers to fast receivers:
For large file transfers where the receiver can keep up:
3. Streaming to slow consumers:
When sending data to a receiver that processes slowly (e.g., video player with full buffer):
4. High-BDP paths:
For long-haul, high-bandwidth connections:
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.
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:
| 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.
Understanding the effective window enables targeted performance tuning. Here are practical recommendations:
Tuning rwnd (receiver-side):
net.core.rmem_max and net.ipv4.tcp_rmem. On Windows, use netsh int tcp set global autotuninglevel=experimental.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.
When TCP throughput is lower than expected, the effective window is often involved. Here's a systematic diagnostic approach:
Step 1: Calculate expected throughput
Expected = min(Link_Bandwidth, Effective_Window / RTT)
If achieved throughput < expected, investigate.
Step 2: Identify the bottleneck constraint
| Observation | Likely Cause | Action |
|---|---|---|
| Advertised window in ACKs is small | rwnd limiting (receiver) | Check receiver buffer, application read rate |
| Advertised window large, but throughput matches cwnd/RTT | cwnd limiting (network) | Check packet loss, CC algorithm, path capacity |
| Both windows large, throughput still low | Other bottleneck | Check link bandwidth, CPU, NIC offload |
| Advertised window drops to 0 | Zero window (receiver overwhelmed) | Application not reading; receiver issue |
| Window scaling not present in handshake | Scaling disabled/stripped | Check middleboxes, verify OS settings |
| Throughput sags then recovers periodically | Packet loss → cwnd reduction | Investigate loss cause, improve path quality |
Step 3: Use diagnostic tools
| Tool | What It Shows |
|---|---|
ss -i (Linux) | Per-connection cwnd, rwnd, RTT, retransmits |
netstat -bt (Windows) | Connection states and bytes transferred |
| Wireshark | Window values in every packet, scaling, SACK |
tcptrace | Graphical timeline of window and sequence evolution |
iperf3 / nuttcp | Controlled bandwidth testing with window options |
123456789101112131415161718
# Show detailed info for all established connectionsss -tin state established # Example output:# ESTAB 0 0 192.168.1.100:55432 93.184.216.34:443# cubic wscale:7,7 rto:208 rtt:104/52 ato:40 mss:1460# cwnd:45 ssthresh:35 bytes_acked:5242880 bytes_received:131072# segs_out:3600 segs_in:90## Key metrics:# - cwnd:45 means congestion window = 45 segments = ~65 KB# - wscale:7,7 means both directions using scale factor 7# - rtt:104 means RTT is ~104ms# - Effective throughput ceiling = cwnd × MSS / RTT# = 45 × 1460 / 0.104 = ~630 KB/s ≈ 5 Mbps # To see rwnd, observe the advertised window in packet captures# or look at the recv-Q column when buffer is filling upA useful heuristic: if achieved throughput is less than 10% of link bandwidth on a high-BDP path, suspect window issues. Calculate BDP, check that both rwnd and cwnd exceed it, and verify window scaling is active. Most low-throughput mysteries on fast links trace to one of these.
We've brought together the full picture of TCP's transmission constraints. The effective window—the minimum of rwnd and cwnd—determines how much data can be in flight, and thus the maximum achievable throughput. Let's consolidate the key concepts:
min(rwnd, cwnd) - bytes_in_flight bytes immediately.ss, Wireshark, and throughput analysis to determine whether rwnd or cwnd is the bottleneck.Module complete:
This concludes our exploration of the TCP sliding window mechanism. We've covered:
Together, these mechanisms form TCP's flow control subsystem—one half of the twin governors that ensure reliable, efficient data transfer. The other half—congestion control—is the subject of subsequent modules.
Congratulations! You now have a comprehensive understanding of TCP's sliding window mechanism—from basic concepts through implementation details to practical tuning. This knowledge is essential for diagnosing performance issues, tuning high-performance systems, and understanding TCP behavior at a deep level. Next, explore the Congestion Control modules to complete your understanding of TCP's dual flow governance.