TCP โ The Reliable Workhorse
TCP (Transmission Control Protocol) is the most widely used transport protocol. It builds a reliable, ordered, error-checked byte stream on top of the unreliable IP layer. When you browse a website, send an email, or SSH into a server โ you are using TCP.
TCP achieves reliability through sequence numbers, acknowledgments, and retransmission. It achieves ordering by numbering bytes and reordering them at the receiver. It prevents network overload through flow control and congestion control.
TCP’s header is more complex than UDP’s โ it carries all the information needed for reliable, ordered delivery.
| TCP Header | |||
|---|---|---|---|
| Source Port (16 bits) | Destination Port (16 bits) | ||
| Sequence Number (32 bits) โ byte offset of first byte in this segment | |||
| Acknowledgment Number (32 bits) โ next expected byte from other side | |||
| Data Offset (4b) | Reserved | Control Flags (9 bits) URG ACK PSH RST SYN FIN |
Window Size (16 bits) flow control buffer size |
| Checksum (16 bits) | Urgent Pointer (16 bits) | ||
| Options (variable) + Padding | |||
| Data (variable) | |||
SYN โ Synchronize: used in connection setupACK โ Acknowledgment: the ACK number field is validFIN โ Finish: sender has no more data to sendRST โ Reset: abort connection immediatelyPSH โ Push: deliver data to application immediately, don’t bufferBefore TCP can send data, it must establish a connection. This uses a 3-way handshake: 3 messages are exchanged to agree on initial sequence numbers and confirm both sides are ready.
state: SYN_SENT
state: ESTABLISHED
โ Data flows โ
state: SYN_RCVD
state: ESTABLISHED
โข Client picks a random Initial Sequence Number (ISN) = x
โข Server picks its own ISN = y
โข Both sides now know each other’s sequence number base
โข After step 3, both sides are in ESTABLISHED state and can exchange data
Closing a TCP connection is a 4-step process (because each side must close independently). Either side can initiate the close.
| Step | Who sends | Packet | Meaning |
|---|---|---|---|
| 1 | Active closer (e.g., client) | FIN | “I have no more data to send” |
| 2 | Passive closer (server) | ACK | “Got it. I may still send data.” |
| 3 | Passive closer (server) | FIN | “Now I’m done sending too.” |
| 4 | Active closer (client) | ACK | “Goodbye!” โ enters TIME_WAIT |
This is why you sometimes see “Address already in use” when restarting a server quickly โ the old socket is still in TIME_WAIT. Fix: use SO_REUSEADDR socket option.
Every byte sent is numbered. The receiver can detect missing bytes (gap in sequence numbers) and request retransmission.
Receiver sends ACK saying “I have received everything up to byte N, send me N+1 next.” ACK is cumulative โ one ACK covers all prior bytes.
If sender doesn’t get an ACK within the timeout, it retransmits the segment. RTO is dynamically calculated based on measured RTT (Round Trip Time).
If receiver gets a duplicate segment (already acknowledged), it discards it silently and re-sends the ACK. Sequence numbers identify duplicates.
Imagine a fast sender filling up a slow receiver’s buffer. The buffer overflows, packets get dropped, and everything has to be retransmitted. Flow control prevents this.
TCP uses a sliding window mechanism. The receiver tells the sender: “I have this many bytes of buffer space available (window size).” The sender must not have more than window_size bytes of unacknowledged data in flight.
When the receiver’s buffer fills up, it advertises window_size = 0. The sender stops. When space frees up, receiver sends a window update. This is called a zero-window probe situation.
Flow control protects the receiver. Congestion control protects the network. If too many senders push too much data, routers get overwhelmed, queues fill up, and packets get dropped. TCP detects this and slows down.
Connection begins with a small congestion window (cwnd). For each ACK received, cwnd doubles. Grow exponentially until a threshold or packet loss.
After reaching the slow-start threshold (ssthresh), cwnd grows linearly (by 1 MSS per RTT) instead of doubling. Cautious but steady growth.
If 3 duplicate ACKs are received (3ร ACK for same byte), TCP retransmits that segment immediately โ without waiting for the timeout. Faster recovery from isolated packet loss.
After fast retransmit: halve cwnd (set ssthresh = cwnd/2), skip slow start, go directly to congestion avoidance. Less drastic than full slow-start reset.
This is a critical concept that confuses many beginners. When you call send() on a TCP socket, TCP does not send it as one unit. It may be split into multiple segments, or combined with other data. On the receive side, one recv() may return only part of what was sent.
/* PROBLEM: Application-level framing */
/* Sender sends two messages: */
send(fd, "Hello", 5, 0);
send(fd, "World", 5, 0);
/* Receiver might get: */
recv(fd, buf, 1024, 0);
/* Could return: "Hello" OR "HelloWorld" OR "Hel" โ anything! */
/* TCP gives no message boundaries. */
/* SOLUTION: Define your own protocol with length prefix */
/* Sender: send 4-byte length first, then data */
void send_msg(int fd, const char *msg, int len) {
uint32_t net_len = htonl(len);
send(fd, &net_len, 4, 0); /* length */
send(fd, msg, len, 0); /* data */
}
/* Receiver: read length first, then read exactly that many bytes */
int recv_exactly(int fd, char *buf, int total) {
int received = 0;
while (received < total) {
int n = recv(fd, buf + received, total - received, 0);
if (n <= 0) return n;
received += n;
}
return received;
}
void recv_msg(int fd) {
uint32_t net_len;
recv_exactly(fd, (char *)&net_len, 4);
int len = ntohl(net_len);
char buf[len + 1];
recv_exactly(fd, buf, len);
buf[len] = '\0';
printf("Message: %s\n", buf);
}
recv() returns exactly what send() sent. This is wrong for TCP. Always loop on recv() until you have all the bytes you expect.A: (1) Client sends SYN (seq=x) โ requests connection, announces initial sequence number.
(2) Server replies with SYN-ACK (seq=y, ack=x+1) โ accepts connection, announces its ISN, acknowledges client’s SYN.
(3) Client sends ACK (ack=y+1) โ acknowledges server’s SYN. Both sides are now ESTABLISHED.
Purpose: both sides synchronize sequence numbers, confirm each side can send and receive, and verify the network path is bidirectional.
A: Security and correctness. If ISN were always 0, an attacker could predict sequence numbers and inject forged packets into a connection (TCP hijacking). Also, random ISNs prevent old delayed packets from a previous connection (same port pair) from being misinterpreted as belonging to the new connection.
A: Flow control prevents a fast sender from overwhelming a slow receiver’s buffer. It is end-to-end (sender โ receiver). Mechanism: receiver advertises its buffer space as a window size.
Congestion control prevents too much traffic from overwhelming the network itself (routers and links). Mechanism: TCP uses a congestion window (cwnd) and algorithms like slow start, congestion avoidance, fast retransmit, and fast recovery to rate-limit data based on detected network congestion (packet loss or ECN).
A: After a TCP connection closes (active closer sends final ACK), the socket stays in TIME_WAIT for 2รMSL (typically 60โ120 seconds). Two reasons: (1) Ensure the final ACK reaches the peer โ if the ACK is lost, the peer retransmits FIN and the active closer can re-send the ACK. (2) Allow old duplicate packets to expire โ prevents them from confusing a new connection using the same port pair. TIME_WAIT can be worked around using the SO_REUSEADDR socket option on the server.
A: TCP delivers a continuous stream of bytes with no inherent message boundaries. recv() may return fewer bytes than sent, or combine multiple sends. Applications must implement their own framing protocol on top of TCP. Common approaches: (1) fixed-length messages, (2) length prefix (4-byte length before each message), (3) delimiter-based (e.g., HTTP uses “\r\n\r\n” to end headers).
A: Receiving 3 duplicate ACKs for the same byte. When a segment is lost but later segments arrive, the receiver keeps sending ACK for the last in-order byte received (duplicate ACKs). After 3 duplicates, the sender retransmits the missing segment immediately without waiting for the retransmission timeout (RTO). This is much faster than waiting for the RTO which could be seconds.
