Loading content...
If sequence numbers allow the sender to label data, then acknowledgment numbers allow the receiver to confirm what has been received. This 32-bit field, occupying bits 64–95 of the TCP header, is the return channel that transforms one-way data transmission into a verified, reliable exchange.
Without acknowledgments, the sender would have no way to know whether its data arrived. It would be forced to either:
TCP's acknowledgment mechanism strikes an elegant balance: the receiver periodically confirms batches of received data, allowing the sender to release old data from buffers and retransmit anything that hasn't been acknowledged within expected timeframes.
By the end of this page, you will understand the precise semantics of acknowledgment numbers, cumulative acknowledgment and its implications, the relationship between ACK numbers and receive buffer management, how acknowledgments drive retransmission and flow control, delayed acknowledgment optimization, and advanced mechanisms like Selective Acknowledgment (SACK).
The acknowledgment number field immediately follows the sequence number, forming the second 32-bit numeric field in the TCP header. Together, these fields constitute the core of TCP's data tracking mechanism.
| Bit Position | Field | Size | Description |
|---|---|---|---|
| 0–15 | Source Port | 16 bits | Port of sending process |
| 16–31 | Destination Port | 16 bits | Port of receiving process |
| 32–63 | Sequence Number | 32 bits | Position of first data byte in segment |
| 64–95 | Acknowledgment Number | 32 bits | Next expected byte from sender |
| 96–99 | Data Offset | 4 bits | Header length in 32-bit words |
The ACK Flag Dependency:
The acknowledgment number field is only meaningful when the ACK control flag (bit 4 of the flags field) is set. In segments without the ACK flag, this field should be ignored—it may contain stale or arbitrary values.
ACK flag = 1: Acknowledgment number field is valid, must be processed
ACK flag = 0: Acknowledgment number field is undefined, ignore it
When is ACK not set?
In modern TCP, the ACK flag is almost always set after the initial SYN segment:
| Segment Type | ACK Flag | Acknowledgment Number |
|---|---|---|
| Initial SYN | 0 | Undefined |
| SYN-ACK | 1 | Client's ISN + 1 |
| All subsequent segments | 1 | Valid acknowledgment |
The only segment without ACK is the very first SYN from the connection initiator, which has nothing to acknowledge yet.
The acknowledgment number field occupies its 32 bits in every TCP segment, regardless of whether the ACK flag is set. This fixed header layout simplifies parsing. When ACK=0, the field is simply not interpreted, but it still consumes its allocated space.
TCP employs cumulative acknowledgment: each acknowledgment number confirms receipt of all bytes up to but not including that number. This is fundamentally different from acknowledging individual segments or ranges.
Precise Definition:
Acknowledgment Number = Next Expected Sequence Number
= Highest Contiguous Sequence Received + 1
When a receiver sends ACK = 500, it asserts:
Why Cumulative Acknowledgment?
Cumulative ACKs provide critical properties:
| Property | Benefit |
|---|---|
| Robustness | Lost ACKs don't cause problems—later ACKs cover all previous data |
| Simplicity | Sender tracks single value (oldest unacked byte) rather than complex ranges |
| Efficiency | One ACK can confirm megabytes of data received across many segments |
| Compression | Reduces ACK traffic—no need for per-segment acknowledgment |
Example Scenario:
Sender transmits segments with sequence numbers 100, 200, 300, 400 (100 bytes each). ACKs may be spaced:
Receiver receives Seq=100,200,300,400 (in order)
Receiver sends: ACK=500 (acknowledges all 400 bytes at once)
Sender interprets: All bytes through 499 confirmed, buffer freed
If the receiver sends ACK=300, then ACK=500, but the first ACK is lost in the network, the sender still receives ACK=500. Since 500 > 300, the sender knows all data through byte 499 was received. This makes TCP acknowledgment highly resilient to ACK loss.
While cumulative acknowledgment is robust and efficient, it has a significant limitation: it cannot express "I received some data after a gap." This creates the acknowledgment gap problem.
The Scenario:
Sender transmits: Seq=100, Seq=200, Seq=300, Seq=400 (100 bytes each)
Network loses: Seq=200
Receiver receives: Seq=100, Seq=300, Seq=400
ReceiverState:
Contiguous: 100-199 ✓
Gap: 200-299 ✗
Buffered (out of order): 300-399 ✓, 400-499 ✓
With pure cumulative ACK, the receiver can only send ACK=200, indicating it expects byte 200 next. It cannot inform the sender that bytes 300–499 were successfully received.
Duplicate ACKs as a Signal:
To partially mitigate this, TCP receivers send duplicate ACKs. When data arrives out of order, the receiver immediately re-sends its current ACK number:
Receiver receives Seq=100 → sends ACK=200
Receiver receives Seq=300 → sends ACK=200 (duplicate!)
Receiver receives Seq=400 → sends ACK=200 (duplicate!)
The sender interprets three duplicate ACKs as a strong signal that something was lost, triggering fast retransmit of sequence 200 without waiting for timeout.
The threshold of three duplicate ACKs (i.e., four ACKs for the same sequence) was chosen empirically. With fewer duplicates, packet reordering in the network could trigger spurious retransmissions. Three duplicates strongly indicate loss rather than reordering.
Selective Acknowledgment (SACK), defined in RFC 2018, extends TCP to overcome the cumulative acknowledgment limitation. SACK allows the receiver to report non-contiguous blocks of data that have been received, enabling precise retransmission of only the missing segments.
SACK Mechanism:
SACK uses a TCP option (negotiated during connection setup) to convey received ranges:
Cumulative ACK: 200 (all bytes through 199 received)
SACK blocks:
- Block 1: 300–399 (received)
- Block 2: 500–599 (received)
Implication: Only bytes 200–299 and 400–499 are missing
SACK Option Format:
Kind (1 byte): 5 (SACK)
Length (1 byte): 2 + 8n (where n = number of blocks)
Block 1: Left Edge (4 bytes), Right Edge (4 bytes)
Block 2: Left Edge (4 bytes), Right Edge (4 bytes)
... up to 4 blocks in typical implementations
Each block specifies a contiguous range [Left Edge, Right Edge) of received sequence numbers. The left edge is the first sequence number in the block; the right edge is the sequence number after the last byte in the block.
| Scenario | Standard TCP | TCP with SACK |
|---|---|---|
| Single segment loss | Retransmit after timeout or 3 dupACKs | Same behavior |
| Multiple segment loss | May retransmit already-received data | Retransmit only missing segments |
| Bursty loss (N segments) | Go-Back-N behavior possible | Selective retransmit of N segments |
| High-latency links | Multiple RTTs to recover | Single RTT recovery possible |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
import structfrom typing import List, Tuple def parse_sack_option(option_bytes: bytes) -> List[Tuple[int, int]]: """ Parse SACK option from TCP segment. Returns list of (left_edge, right_edge) tuples. """ if len(option_bytes) < 2: return [] kind = option_bytes[0] if kind != 5: # SACK option kind return [] length = option_bytes[1] num_blocks = (length - 2) // 8 blocks = [] offset = 2 for _ in range(num_blocks): left_edge = struct.unpack('!I', option_bytes[offset:offset+4])[0] right_edge = struct.unpack('!I', option_bytes[offset+4:offset+8])[0] blocks.append((left_edge, right_edge)) offset += 8 return blocks def compute_missing_ranges( cumulative_ack: int, sack_blocks: List[Tuple[int, int]], bytes_in_flight: int) -> List[Tuple[int, int]]: """ Determine which byte ranges need retransmission. """ # Sort SACK blocks by left edge sorted_blocks = sorted(sack_blocks, key=lambda b: b[0]) missing = [] current = cumulative_ack for left, right in sorted_blocks: if left > current: missing.append((current, left)) # Gap before this block current = max(current, right) # Gap after last SACK block to end of sent data end_of_sent = cumulative_ack + bytes_in_flight if current < end_of_sent: missing.append((current, end_of_sent)) return missing # Example usagecumulative_ack = 1000sack_blocks = [(1200, 1300), (1400, 1600)]bytes_in_flight = 1000 # Sent 1000-1999 missing = compute_missing_ranges(cumulative_ack, sack_blocks, bytes_in_flight)print(f"Missing ranges: {missing}")# Output: Missing ranges: [(1000, 1200), (1300, 1400), (1600, 2000)]# Only retransmit these specific ranges, not the SACK'd blocksSACK must be negotiated during the three-way handshake using the SACK-Permitted option (Kind=4). Both endpoints must support SACK for it to be used. Once negotiated, the receiver may include SACK blocks in any ACK segment.
Sending an ACK for every received segment would generate substantial overhead. Delayed acknowledgment reduces this by allowing the receiver to wait briefly before acknowledging, potentially combining multiple acknowledgments into one.
The Delayed ACK Algorithm (RFC 1122/5861):
When in-order data arrives and no ACK is pending:
If more data arrives before timer expires:
If timer expires:
Every second full-sized segment SHOULD be ACK'd immediately:
| Event | Action | Rationale |
|---|---|---|
| First segment arrives | Start delay timer | Anticipate second segment |
| Second segment arrives | ACK immediately | Two-segment rule |
| Timer expires | ACK immediately | Maximum delay reached |
| Out-of-order segment | ACK immediately | Signal gap to sender |
| FIN received | ACK immediately | Timely connection close |
| Data to send | Piggyback ACK | Combine ACK with outgoing data |
Delayed ACK Benefits:
Delayed ACK Pitfalls:
When Nagle's algorithm delays sending small writes until ACK arrives, and delayed ACK delays the ACK waiting for more data, neither side sends anything for up to 200ms. This 'Nagle's algorithm interaction' requires TCP_NODELAY to resolve in latency-sensitive applications.
12345678910111213141516171819
# View current delayed ACK settings on Linuxsysctl net.ipv4.tcp_delack_min # On many systems, DelayedAck timer is hardcoded around 40-200ms # Application-level: Disable Nagle's algorithm (not delayed ACK) to avoid interaction# Python example:# sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) # Linux: TCP_QUICKACK socket option for immediate ACK# Sets socket to send ACKs immediately for the next packet# Python: sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, 1) # Note: TCP_QUICKACK is not persistent - must be re-enabled after each receive # View ACK-related statisticsnetstat -s | grep -i ack# ORcat /proc/net/snmp | grep TcpAcknowledgment numbers drive critical buffer management decisions on both sender and receiver sides. Understanding this relationship is essential for TCP performance tuning and debugging.
Sender Side Buffer Management:
The sender maintains a retransmission buffer containing all unacknowledged data:
Retransmission Buffer:
[Byte 1000]...[Byte 1500] - Sent but unacknowledged
When ACK=1300 arrives:
- Release bytes 1000-1299 from buffer (confirmed received)
- Retain bytes 1300-1500 (still outstanding)
- Buffer memory freed for new outgoing data
Sender Buffer Variables:
| Variable | Meaning |
|---|---|
| SND.UNA | Oldest unacknowledged sequence number |
| SND.NXT | Next sequence number to send |
| SND.WND | Send window (from receiver's advertisement) |
| SND.WL1/WL2 | Window update tracking sequence/ack numbers |
Usable Window Calculation:
Usable Window = SND.UNA + SND.WND - SND.NXT
= Window Size - Bytes In Flight
As ACKs arrive, SND.UNA advances, freeing buffer space and potentially allowing more data transmission.
Receiver Side Buffer Management:
The receiver maintains a reassembly buffer for incoming data:
Reassembly Buffer (RCV.NXT = 1000):
[1000-1099: Empty] [1100-1199: Data] [1200-1299: Empty] [1300-1399: Data]
When segment Seq=1000 arrives:
- Fill bytes 1000-1099
- Now 1000-1199 is contiguous
- Advance RCV.NXT to 1200
- Deliver 1000-1199 to application
- Send ACK=1200
Receiver Buffer Variables:
| Variable | Meaning |
|---|---|
| RCV.NXT | Next expected sequence number |
| RCV.WND | Receive window (advertised to sender) |
| RCV.UP | Urgent pointer value |
For maximum throughput, buffer size should be at least the Bandwidth-Delay Product (BDP = bandwidth × RTT). A 100 Mbps link with 100ms RTT needs at least 1.25 MB buffers to fully utilize capacity. Undersized buffers limit throughput regardless of network capacity.
The acknowledgment number field is intimately connected to TCP's retransmission mechanism. Every retransmission decision is ultimately based on the sender's interpretation of received acknowledgments.
Two Retransmission Triggers:
Timeout-Based Retransmission: If no ACK arrives for data before the Retransmission Timeout (RTO) expires, the sender retransmits.
Fast Retransmit: If three duplicate ACKs (four identical ACKs total) arrive, the sender immediately retransmits the next expected byte without waiting for timeout.
How ACKs Inform Retransmission:
Sender state: SND.UNA = 1000, sent through 1500
Receiver sends: ACK=1000, ACK=1000, ACK=1000, ACK=1000 (3 duplicates)
Interpretation:
- Byte 1000 never arrived (receiver keeps asking for it)
- Bytes 1001-1500 may or may not have arrived
- Action: Immediately retransmit starting at sequence 1000
The ACK Clock:
ACKs arriving at the sender act as a "clock" driving data transmission:
Network paths can reorder packets. A single or even two duplicate ACKs may just indicate reordering, not loss. The threshold of three duplicates was empirically chosen to distinguish genuine loss from reordering. Some modern TCP variants use dynamic thresholds based on observed reordering.
We've explored the acknowledgment number field—the essential feedback mechanism that enables TCP's reliable delivery guarantee.
What's Next:
With sequence and acknowledgment numbers tracking data in both directions, TCP needs control mechanisms to manage the connection itself. The next page examines TCP flags—the six control bits (SYN, ACK, FIN, RST, PSH, URG) that govern connection establishment, data transfer behavior, and connection termination.
You now possess comprehensive understanding of TCP acknowledgment numbers: their position in the header, cumulative semantics, the gap problem and SACK solution, delayed acknowledgment optimization, buffer management relationships, and their role in driving retransmission. This prepares you for understanding how TCP flags control the connection lifecycle.