TCP protocol itself have several different mechanisms for handling bottlenecks.
For example (google details):
- Slow start
- Flow control
- Congestion control
Shortly: TCP stack uses windows for avoid sending more data to the peer than it can store to receiving queue.
When a sender try to send faster than network/receiver can handle,
the output queue of TCP stack of the sender will fill up.
When the queue is full, then the sender notices this by few ways:
- with blocking socket:
send()
call will block.
- with unblocking socket:
send()
call will fail with errno EAGAIN or EWOULDBLOCK.
- whith
select()
call: writefd-set won't indicate writing possibility.
This way the TCP stack can automatically slow down the sender, and so minimize packet lost in network/receiving end.
EDIT:
Tcpdump example, where server (B) does not call recv() after accept():
~ # tcpdump -i eth0 "tcp port 12345"
21:44:16.255183 A > B: seq 2052761822, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
21:44:16.255484 B > A: seq 2110966471, ack 2052761823, win 7300, options [mss 1460,nop,nop,sackOK], length 0
21:44:16.256065 A > B: ack 1, win 64240, length 0
21:44:20.338089 A > B: seq 1:1461, ack 1, win 64240, length 1460
21:44:20.338365 B > A: ack 1461, win 5840, length 0
21:44:20.338754 A > B: seq 1461:2921, ack 1, win 64240, length 1460
21:44:20.338978 B > A: ack 2921, win 5840, length 0
21:44:20.339357 A > B: seq 2921:4381, ack 1, win 64240, length 1460
21:44:20.339759 A > B: seq 4381:5841, ack 1, win 64240, length 1460
21:44:20.340175 A > B: seq 5841:7301, ack 1, win 64240, length 1460
21:44:20.340571 A > B: seq 7301:8761, ack 1, win 64240, length 1460
21:44:20.373395 B > A: ack 8761, win 1460, length 0
21:44:20.374367 A > B: seq 8761:10221, ack 1, win 64240, length 1460
21:44:20.413398 B > A: ack 10221, win 0, length 0
21:44:20.714460 A > B: seq 10221:10222, ack 1, win 64240, length 1
21:44:20.714725 B > A: ack 10221, win 0, length 0
21:44:21.314796 A > B: seq 10221:10222, ack 1, win 64240, length 1
21:44:21.315055 B > A: ack 10221, win 0, length 0
21:44:22.515652 A > B: seq 10221:10222, ack 1, win 64240, length 1
21:44:22.515925 B > A: ack 10221, win 0, length 0
21:44:24.917211 A > B: seq 10221:10222, ack 1, win 64240, length 1
21:44:24.917473 B > A: ack 10221, win 0, length 0
21:44:29.718352 A > B: seq 10221:10222, ack 1, win 64240, length 1
21:44:29.718612 B > A: ack 10221, win 0, length 0
21:44:39.331520 A > B: seq 10221:10222, ack 1, win 64240, length 1
21:44:39.331789 B > A: ack 10221, win 0, length 0
The client (A) tries to send big segments to server (B) until the server starts advertise window size 0. After this point the client (A) start using segment size 1 byte, and period between re-transmission start increasing. It looks like, that TCP stack tries to minimize traffic that is needed for polling the window.
RFC-793 says the following (Later RFCs may specify this better):
The sending TCP must be prepared to accept from the user and send at
least one octet of new data even if the send window is zero. The
sending TCP must regularly retransmit to the receiving TCP even when
the window is zero. Two minutes is recommended for the retransmission
interval when the window is zero. This retransmission is essential to
guarantee that when either TCP has a zero window the re-opening of the
window will be reliably reported to the other.
When the receiving TCP has a zero window and a segment arrives it
must still send an acknowledgment showing its next expected sequence
number and current window (zero).