Skip to main content

Flow Control

A fast sender can overwhelm a slow receiver. If bytes arrive faster than the receiving application reads them, the receiver's buffer fills up and further bytes have nowhere to go. Flow control is how TCP keeps the sender from overflowing the receiver.

The receive buffer and rwnd

Every TCP connection has a receive buffer on each side. Arriving in-order bytes sit there until the application calls read() and drains them. The receiver constantly tells the sender how much free space is left in that buffer

  • a value called the receive window, or rwnd:

rwnd = buffer capacity - unread bytes currently buffered

The rwnd rides along in the header of every ACK the receiver sends. The rule for the sender is simple: never have more unacknowledged bytes in flight than the last advertised rwnd. When rwnd reaches 0, the sender must stop and wait for the receiver to drain its buffer and advertise room again.

Worked example: rwnd over a few segments

Say the receive buffer is 64 KB and starts empty, so the first ACK advertises rwnd = 65536. Now trace a few round trips where the sender pushes data faster than the application reads it:

EventUnread bytes bufferedAdvertised rwnd
Start065536
Sender sends 20,000 bytes, app hasn't read yet20,00045536
Sender sends another 30,000 bytes50,00015536
App finally reads 40,000 bytes10,00055536
Sender sends 20,000 more (allowed, rwnd covers it)30,00035536

If the application stalls completely while the buffer is at 50,000 unread bytes, rwnd drops to 15,536 and the sender may only push 15,536 more unacknowledged bytes before it must stop - regardless of how much bandwidth the network path has. A zero window (rwnd = 0) freezes the sender entirely until a later ACK reopens it; the sender periodically probes with a 1-byte zero-window probe so it notices the reopening even if that ACK is lost.

Watch it fill and drain

The sender wants to push a steady stream. Drag the slider to set how fast the application reads. Read slowly and the buffer fills, rwnd shrinks toward 0, and the sender gets throttled. Speed the reader up and the window reopens.

buffered bytesfree space (rwnd)
Receive buffer (16 B)
rwnd 12
Buffered4 B
Advertised rwnd12 B
Delivered this tick4 B
Sender flowing freely
The receiver advertises rwnd = 12 - the free space left in its buffer. The sender may never have more un-read bytes in flight than that.
tick 0 / 11
info

Flow control is about the receiver - one endpoint protecting its own buffer. It is a completely separate mechanism from congestion control, which is about protecting the network in the middle from overload. The sender obeys both at once: the bytes it may send is limited by min(rwnd, cwnd) - the smaller of the receiver's window and the congestion window. The next lesson covers cwnd.

Why not just a fixed rate?

The receiver's read speed is not fixed - it depends on what the application is doing, CPU load, disk speed, and more. A static rate would either waste bandwidth (too slow) or overflow the buffer (too fast). By advertising rwnd on every ACK, the receiver adapts the sender's pace in real time to exactly the speed it can currently absorb.

A small receive buffer silently caps throughput on high-BDP links

Even with unlimited bandwidth, the maximum in-flight data is bounded by rwnd, and rwnd can never exceed the receive buffer's total size. On a long, fast path - say a 100 Mbps link with a 100 ms round-trip time, the bandwidth-delay product (BDP) is 100 Mbps × 0.1 s ≈ 1.25 MB of data that should be in flight at once to fill the pipe. If the OS default receive buffer is only 64 KB, rwnd tops out at 64 KB and the connection can never push more than roughly 64 KB / 0.1 s ≈ 640 KB/s, no matter how fast the NICs and routers are. This shows up as mysteriously capped throughput on long-haul or satellite links and is usually fixed by enabling TCP window scaling (RFC 1323) and raising the OS's socket buffer size limits - not by touching the network at all.