How Does TCP Guarantee Delivery?
Networks are unreliable. Packets get lost, corrupted, duplicated, or delayed. TCP solves this by using acknowledgements and retransmissions. Every segment sent must be acknowledged by the receiver. If no acknowledgement arrives in time, TCP retransmits the segment automatically.
This is what makes TCP a reliable protocol β the application does not have to worry about lost packets. TCP handles it transparently.
When the receiving TCP gets a segment without errors, it sends a positive acknowledgement (ACK) back to the sender. This ACK tells the sender: “I received everything up to byte N. Send me byte N+1 next.”
If a segment arrives with errors (checksum fails), the receiver simply discards it silently. No negative ACK is sent. The sender detects this through a timeout.
The ACK number is cumulative: ACK=1001 means “I have received all bytes up to and including byte 1000.” This is also called a cumulative acknowledgement.
Every time TCP sends a segment, it starts a retransmission timer. If an ACK for that segment does not arrive before the timer expires, TCP assumes the segment was lost and retransmits it.
The key question is: how long should the timer be? Too short β unnecessary retransmissions (wastes bandwidth). Too long β slow recovery from real losses.
TCP solves this with a dynamic RTO (Retransmission Timeout). It continuously measures the Round Trip Time (RTT) β the time from sending a segment to receiving its ACK β and adjusts the RTO accordingly.
In a normal request-response protocol (like HTTP), after receiving a request, the server sends a response right away. TCP is smart: instead of sending a separate ACK packet, the server can piggyback the ACK onto the response data it is already sending.
The Delayed ACK technique: instead of sending an ACK immediately on receiving a segment, the receiver waits a short time (typically up to 200ms) to see if there is any outgoing data to send. If there is, the ACK rides along with that data. If not, the ACK is sent on its own after the delay.
Trade-off: Delayed ACK reduces the number of packets but adds a small delay. For interactive applications (like SSH), this can be noticeable. The TCP_NODELAY socket option (Nagle algorithm) can be used to disable this behavior.
The TCP_NODELAY socket option disables the Nagle algorithm, which is related to delayed ACK. When set, small segments are sent immediately rather than being buffered. This is important for real-time or low-latency applications.
/* tcp_nodelay_example.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h> /* for TCP_NODELAY */
#include <arpa/inet.h>
int main(void)
{
int sockfd;
int optval = 1;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) { perror("socket"); exit(EXIT_FAILURE); }
/*
* TCP_NODELAY: disables Nagle's algorithm
* Without TCP_NODELAY: small writes may be buffered and batched together
* With TCP_NODELAY: each write() sends a segment immediately
*
* Use when you need low latency (SSH, game servers, telemetry)
* Avoid for bulk data transfer β Nagle reduces overhead there
*/
if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY,
&optval, sizeof(optval)) == -1) {
perror("setsockopt TCP_NODELAY");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("TCP_NODELAY enabled β segments sent immediately\n");
/* Check what value is set */
socklen_t optlen = sizeof(optval);
if (getsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY,
&optval, &optlen) == -1) {
perror("getsockopt");
} else {
printf("TCP_NODELAY is currently: %s\n", optval ? "ON" : "OFF");
}
close(sockfd);
return 0;
}
/* Checking RTO-related socket options */
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <unistd.h>
int main(void)
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct tcp_info info;
socklen_t len = sizeof(info);
if (getsockopt(sockfd, IPPROTO_TCP, TCP_INFO, &info, &len) == 0) {
printf("RTT (smoothed): %u microseconds\n", info.tcpi_rtt);
printf("RTT variance: %u microseconds\n", info.tcpi_rttvar);
printf("RTO: %u microseconds\n", info.tcpi_rto);
printf("Retransmits: %u\n", info.tcpi_retransmits);
}
close(sockfd);
return 0;
}
Note: struct tcp_info and TCP_INFO are Linux-specific. They expose the TCP stack’s internal state for a socket β very useful for debugging network performance issues.
The receiving TCP silently discards the segment. No ACK or NACK is sent. The sender detects the loss via the retransmission timer expiring and retransmits the segment.
RTO (Retransmission Timeout) is the time TCP waits for an ACK before retransmitting a segment. It is dynamically calculated using: RTO = SRTT + 4 Γ RTTVAR, where SRTT is the smoothed RTT and RTTVAR is the RTT variance. This formula (from RFC 6298) adapts to network conditions.
Delayed ACK is a technique where the receiver waits a short time (up to 200ms) before sending an ACK, to see if outgoing data can piggyback the ACK. It reduces the number of pure ACK packets in the network, lowering overhead and improving efficiency for bidirectional communication.
Piggybacking is including an acknowledgement inside a data segment going in the opposite direction. Every TCP segment has an ACK field. Instead of sending a separate ACK packet, the receiver includes the ACK number in the next data packet it sends to the original sender. This halves the number of packets in a request-response pattern.
TCP_NODELAY disables the Nagle algorithm, which batches small writes to reduce packet count. Use it when you need low-latency transmission (SSH, real-time game data, telemetry) and can’t afford the buffering delay. For bulk data transfers, keep Nagle enabled as it reduces overhead.
Yes. If the ACK was lost (not the data), the sender’s timer expires and it retransmits. The receiver may get a duplicate segment. TCP handles this by using sequence numbers to detect and discard duplicates silently.
