When two computers talk to each other over a network, they don’t just fire data at each other and hope for the best. They need a reliable way to confirm that data actually arrived, in the right order, without corruption. This is where TCP (Transmission Control Protocol) comes in, and at the heart of its reliability mechanism lies the concept of acknowledgements.
In this article, we will explore how TCP handles data transfer, what acknowledgements are, and the difference between two important techniques: piggybacking and pure acknowledgement. We will build this understanding from first principles, using simple language, real-world analogies, diagrams, and practical examples from Linux, Cisco, and Python.
By the end of this article, you will understand:
- How TCP achieves reliable, ordered data delivery
- What acknowledgements are and why they matter
- The difference between piggybacked and pure ACKs
- How these concepts appear in real systems (Linux, Cisco routers, Python sockets)
- Common troubleshooting scenarios and best practices
1. A Quick Refresher: What Is TCP?
TCP is a connection-oriented, reliable, byte-stream transport layer protocol defined in RFC 793 and updated by many later RFCs. It sits at Layer 4 of the OSI model and Layer 4 (Transport) of the TCP/IP model.
Unlike UDP, which just sends packets and forgets about them, TCP guarantees:
- Reliable delivery — lost packets are retransmitted.
- Ordered delivery — packets are reassembled in the correct sequence even if they arrive out of order.
- Flow control — the sender doesn’t overwhelm the receiver’s buffer.
- Congestion control — the sender backs off when the network is congested.
All of these features depend on one core mechanism: acknowledgement (ACK) of received data.
2. Why Acknowledgements Are Necessary
Imagine you are sending a stack of letters to a friend through a postal service that sometimes loses mail. If your friend never tells you which letters arrived, you have no way of knowing whether to resend a lost letter. Acknowledgements solve exactly this problem in networking.
In TCP, every byte of data sent is assigned a sequence number. When the receiver gets a segment, it sends back an acknowledgement number, which tells the sender “I have successfully received all bytes up to this point; please send the next byte from here.”
This is called cumulative acknowledgement — a single ACK confirms receipt of everything up to a given byte, not just one specific packet.
The ACK Flag and Acknowledgement Number
Every TCP segment contains a header with:
- Sequence Number (32 bits) — position of the first byte of data in this segment relative to the start of the byte stream.
- Acknowledgement Number (32 bits) — valid only if the ACK flag is set; it indicates the next byte the receiver expects.
- ACK flag (1 bit) — when set to 1, it means the acknowledgement number field is valid.
3. Pure Acknowledgement
A pure acknowledgement is a TCP segment that contains only the ACK flag and acknowledgement number — no actual payload/data of its own. It exists purely to inform the sender that data has been received.
When Does Pure ACK Happen?
Pure ACKs typically occur when:
- The receiver has nothing to send back to the sender at that moment (one-way data flow).
- A delayed ACK timer expires and no outgoing data is available to attach it to.
- The connection is mostly one-directional, such as a large file download where the client has nothing to say except “got it.”
Characteristics of a Pure ACK
| Property | Value |
|---|---|
| Contains payload? | No |
| Header size | 20 bytes (no options) to 60 bytes (with options) |
| ACK flag | Set to 1 |
| Sequence number | Same as previous segment sent (no new data) |
| Purpose | Confirm receipt of previous segment(s) |
Real-World Analogy
Think of pure ACK like a text message that just says “Got it 👍” — it doesn’t carry any new information for the other person, it simply confirms receipt.
4. Piggybacking
Piggybacking is an optimization technique where the acknowledgement for received data is attached to (piggybacked on) an outgoing data segment, instead of being sent as a separate, empty ACK packet.
This happens naturally in full-duplex, bidirectional communication — for example, in a Telnet or SSH session, or in an HTTP/2 stream where both client and server are exchanging data continuously.
Why Piggybacking Matters
Sending a separate ACK for every data segment wastes bandwidth and processing resources, especially in bidirectional traffic where both parties are already sending data anyway. Piggybacking reduces the number of packets on the wire by combining the “I got your data” message with “here is my data” message into a single TCP segment.
How Piggybacking Works — Step by Step
- Host A sends data to Host B.
- Host B receives Host A’s data and needs to send some data back to Host A anyway.
- Instead of sending a pure ACK immediately, Host B waits briefly (or immediately, if data is ready) and sends a single TCP segment containing:
- Its own data (payload)
- The ACK number acknowledging Host A’s data
This single segment now serves two purposes at once.
Mermaid Diagram: Piggybacking vs Pure ACK
sequenceDiagram
participant A as Host A
participant B as Host B
Note over A,B: Pure Acknowledgement Scenario
A->>B: Data Segment (Seq=100, Len=50)
B-->>A: Pure ACK (Ack=150, no data)
Note over A,B: Piggybacking Scenario
A->>B: Data Segment (Seq=100, Len=50)
B->>A: Data + ACK (Seq=200, Ack=150, Len=30)In the first scenario, Host B has nothing to send, so it replies with an empty ACK. In the second scenario, Host B has data to send anyway, so it combines the ACK with its own data segment — this is piggybacking.
5. Delayed Acknowledgement and Its Relationship with Piggybacking
TCP implementations often use a technique called delayed ACK to increase the chances of piggybacking. Instead of acknowledging every single segment the instant it arrives, the receiver waits for a short period (typically up to 200ms, governed by a timer) to see if it has any outgoing data to piggyback the ACK onto.
- If outgoing data becomes available within this window, the ACK is piggybacked.
- If the timer expires with no outgoing data, a pure ACK is sent.
- If two full-sized segments arrive before the timer expires, most implementations send an immediate ACK (this is part of TCP’s “ACK every other segment” heuristic, described in RFC 1122).
Why Not Always Delay?
Delaying ACKs indefinitely would hurt performance because the sender’s congestion window depends on receiving timely ACKs to keep sending more data (this is closely tied to TCP’s sliding window mechanism). So there’s a tradeoff between efficiency (fewer packets via piggybacking) and responsiveness (timely ACKs to keep data flowing).
6. Sequence and Acknowledgement Numbers in Action
Let’s trace through a real example. Suppose Host A wants to send 300 bytes of data to Host B, using a Maximum Segment Size (MSS) of 100 bytes.
| Step | Sender (A) Action | Seq No | Data Length | Receiver (B) Action | Ack No |
|---|---|---|---|---|---|
| 1 | Send Segment 1 | 1000 | 100 | Receives, buffers | — |
| 2 | Send Segment 2 | 1100 | 100 | Receives, buffers | — |
| 3 | Send Segment 3 | 1200 | 100 | Receives all, sends ACK | 1300 |
Here, Host B sent a cumulative ACK of 1300, meaning “I have received everything up to byte 1299; send me byte 1300 next.” If Host B had data of its own to send back at this point, it would attach ACK=1300 to that outgoing segment — piggybacking in action.
7. Linux Example: Observing ACKs with tcpdump
You can actually observe pure ACKs and piggybacked ACKs on a Linux system using tcpdump.
# Capture TCP traffic on interface eth0, showing sequence and ack numbers
sudo tcpdump -i eth0 -S -n tcp port 22Sample output during an SSH session:
12:01:01.123456 IP 192.168.1.10.54321 > 192.168.1.20.22: Flags [P.], seq 1001:1051, ack 501, win 64240, length 50
12:01:01.223456 IP 192.168.1.20.22 > 192.168.1.10.54321: Flags [.], ack 1051, win 64240, length 0
12:01:01.323456 IP 192.168.1.20.22 > 192.168.1.10.54321: Flags [P.], seq 501:531, ack 1051, win 64240, length 30Notice:
- The second line has
length 0— this is a pure ACK (no payload). - The third line has both
length 30andack 1051— this is a piggybacked ACK, since the segment carries data (30 bytes) while also acknowledging the previous data from the other side.
You can also inspect TCP statistics on Linux using:
# View TCP connection info including retransmissions and ACK stats
ss -tior
# Kernel-level TCP statistics counters
nstat -az | grep -i "Tcp\|DelayedACK"8. Cisco Example: Observing TCP Behavior on a Router
While Cisco IOS devices are primarily concerned with routing rather than being TCP endpoints themselves, they do run TCP for management protocols like SSH, and you can inspect TCP-related behavior when the router itself is a TCP endpoint (e.g., for BGP sessions, which run over TCP port 179).
Router# show tcp brief all
TCB Local Address Foreign Address (state)
6534A1F0 10.1.1.1.179 10.1.1.2.11002 ESTABTo see detailed TCP statistics, including acknowledgements sent and received:
Router# show tcp statistics
Rcvd: 1200 total, 0 no port
0 checksum error, 0 bad offset, 0 too short
1150 packets (98000 bytes) in sequence
50 dup packets (2000 bytes)
0 out-of-order packets
Sent: 1180 total, 1150 data packets (97000 bytes)
30 ack-only packets (0 data)Notice the last line: “30 ack-only packets” — these are the pure acknowledgements. In contrast, most of the 1150 data packets that were sent to an established BGP peer would carry piggybacked ACKs, since BGP is bidirectional and both routers exchange routing updates continuously.
This matters in real-world scenarios like BGP session stability — if a router is sending many “ack-only” packets, it might indicate a mostly one-directional flow of routing updates or an asymmetric session load.
9. Python Example: Simulating Piggybacking and Pure ACK Behavior
While raw sockets in Python can be used to construct custom TCP-like behavior, a simpler educational approach is to build a small simulation using regular TCP sockets to observe how piggybacking naturally happens when both sides exchange data.
import socket
import threading
import time
def server():
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(("127.0.0.1", 9090))
srv.listen(1)
conn, addr = srv.accept()
print("Server: connection established with", addr)
data = conn.recv(1024)
print("Server received:", data.decode())
# Server has data to send back — this reply, at the TCP layer,
# will carry the ACK for the client's data piggybacked in its header.
conn.send(b"Hello back from server (piggybacked ACK at TCP layer)")
conn.close()
srv.close()
def client():
time.sleep(1)
cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cli.connect(("127.0.0.1", 9090))
cli.send(b"Hello from client")
reply = cli.recv(1024)
print("Client received:", reply.decode())
cli.close()
t1 = threading.Thread(target=server)
t2 = threading.Thread(target=client)
t1.start()
t2.start()
t1.join()
t2.join()
Although the Python socket module operates above the TCP layer and doesn’t let us directly inspect ACK numbers (the OS kernel handles that), running this script while simultaneously capturing traffic with tcpdump -i lo -n tcp port 9090 will show you real piggybacked ACKs in the kernel’s TCP implementation — exactly like the Linux example in Section 7.
10. Comparison Table: Piggybacking vs Pure Acknowledgement
| Aspect | Piggybacking | Pure Acknowledgement |
|---|---|---|
| Contains data payload | Yes | No |
| Extra packet overhead | Low (combined into one) | Higher (separate packet needed) |
| Best suited for | Bidirectional/full-duplex traffic | Unidirectional traffic |
| Typical use case | Chat apps, SSH, BGP sessions | File downloads, one-way streaming |
| Delay involved | Sometimes uses delayed ACK timer | Sent as soon as timer expires or immediately |
| Bandwidth efficiency | Higher | Lower |
| Header overhead relative to data | Amortized over payload | Full 20+ byte header for zero data |
11. Real-World Scenarios
Scenario 1: Web Browsing (Mostly Unidirectional)
When you download a large web page or file, your browser (client) sends very little data (just the HTTP GET request), while the server sends large amounts of data back. Since the client has little to say after the initial request, most of its outgoing segments are pure ACKs.
Scenario 2: SSH Interactive Session (Bidirectional)
In an interactive SSH session, you type commands and the server sends back command output continuously. Because both sides are frequently sending small chunks of data, piggybacking is very common — your keystrokes’ ACKs often ride along with terminal output being sent back.
Scenario 3: BGP Routing Sessions
As shown in the Cisco example, BGP peers exchange route updates over a long-lived TCP session. Since both routers are typically sending updates to each other, ACKs are frequently piggybacked onto route update messages, reducing unnecessary “ack-only” packets on the wire.
12. Best Practices
- Don’t disable delayed ACK blindly. While disabling delayed ACK can reduce latency in some real-time applications, it can also increase the number of pure ACK packets, adding overhead. Test before changing kernel parameters like
TCP_QUICKACKon Linux. - Monitor ACK-only packet counts on routers and servers (as shown in the Cisco
show tcp statisticsexample) to understand traffic directionality. - Use Nagle’s algorithm awareness. Nagle’s algorithm (which batches small outgoing packets) can interact with delayed ACK in a way that causes latency stalls; disable it (
TCP_NODELAY) for latency-sensitive apps like gaming or real-time trading systems. - Use packet capture tools (
tcpdump, Wireshark) regularly during development to validate that your bidirectional protocols are actually benefiting from piggybacking, not generating excessive pure ACKs. - Consider MSS and window sizing carefully, since larger data segments mean fewer overall segments and therefore fewer separate ACKs are needed in the first place.
13. Troubleshooting Common Issues
Issue: Excessive Pure ACK Traffic Slowing Down the Network
Symptom: Wireshark capture shows a huge number of zero-length ACK packets.
Cause: Often occurs in mostly unidirectional flows, or when delayed ACK is disabled, causing an ACK to be sent for almost every segment.
Fix: Verify whether TCP_QUICKACK is enabled unnecessarily on Linux:
# Check current quickack status is per-socket, but you can test with:
cat /proc/sys/net/ipv4/tcp_delack_minRe-enable delayed ACK behavior for the application if latency isn’t critical, allowing more piggybacking opportunities.
Issue: Interactive Application Feels Laggy (e.g., SSH, Telnet)
Symptom: Noticeable delay between typing a character and seeing it echoed back.
Cause: This is the classic Nagle’s algorithm + delayed ACK interaction problem, where the sender waits for an ACK before sending small packets, and the receiver delays the ACK waiting for outgoing data.
Fix: Disable Nagle’s algorithm on latency-sensitive sockets:
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)Issue: BGP Session Flapping Correlated with High “ack-only” Packet Counts
Symptom: show tcp statistics on a Cisco router shows an unusually high ratio of ack-only packets to data packets.
Cause: May indicate one peer is not sending regular routing updates, causing the other side to fall back to pure ACKs, and potentially signaling a misconfigured route filter or a stalled BGP process.
Fix: Check show ip bgp summary for the peer’s state and verify route advertisement policies with show ip bgp neighbors advertised-routes.
14. Conclusion
TCP’s reliability hinges on the humble acknowledgement, but how that acknowledgement is sent — as a standalone pure ACK or bundled via piggybacking onto outgoing data — has real implications for network efficiency. Piggybacking reduces overhead in bidirectional communication by combining ACKs with data, while pure ACKs are necessary when there’s genuinely nothing else to send. Understanding this distinction helps network engineers, developers, and system administrators diagnose performance issues, tune delayed ACK behavior, and design more efficient network applications.
