TCP Working: 3-Way Handshake and Reliable Communication
How two machines agree to talk — and actually trust each other
Last semester, a friend of mine was debugging why his Flask app kept throwing connection errors against a remote database. He spent two hours checking his Python code, his SQL queries, his .env file. The code was fine. The queries were fine. The credentials were fine.
The problem was a firewall rule blocking TCP port 5432.
When I explained this to him, he asked: "So TCP is just about ports?"
It's not. Not even close. TCP is one of the most carefully designed protocols in computing, and understanding it properly changes how you debug, how you design systems, and how you reason about what "reliable" actually means in networking.
This blog is about what TCP does under the hood — not at the byte level, but at the behavioral level. What it negotiates before sending a single byte of your data. How it tracks every packet. How it handles loss. How it closes gracefully.
What is TCP and Why It Exists
The internet is, at its core, unreliable. Packets travel through routers they've never met, across cables they can't predict, arriving (or not) in orders that weren't planned. The physical network doesn't guarantee anything.
TCP — Transmission Control Protocol — is the answer to this unreliability. It's a protocol that sits on top of the internet's routing layer and adds a contract between two machines: "I promise that everything I send, you will receive, in order, without corruption. And if something goes wrong in transit, I'll fix it without you even knowing."
That's a big promise. Keeping it requires a lot of machinery — handshakes, sequence numbers, acknowledgements, retransmission timers. Let's go through each.
Problems TCP Is Designed to Solve
Before understanding the solution, let's understand the specific problems.
Packets arrive out of order. The internet routes packets independently. Packet 3 might arrive before packet 1 if it took a faster route. Your application would read garbled data if nobody reassembled them in the right sequence.
Packets get lost. Routers drop packets when congested. Wireless connections lose packets to interference. There's no built-in retry at the network layer.
Packets arrive corrupted. Bit flips happen. If a single bit flips in a file you're downloading, you want to detect it and get a clean copy — not silently accept broken data.
Sender overwhelms the receiver. If a powerful server blasts data at a slow device, the device's buffer overflows and packets are dropped. Someone needs to regulate speed.
No connection state. Without TCP, there's no concept of "is the other side even running?" You're just firing packets into the void.
TCP solves all of these. Let's see how it starts — with the handshake.
The 3-Way Handshake: Establishing Trust
Before a single byte of your application's data moves, TCP performs a 3-step ritual between the two machines. This is called the Three-Way Handshake, and it establishes the connection with mutual agreement.
Here's the best analogy I've found. Imagine you call a friend on the phone:
You: "Hey, can you hear me?" (SYN)
Friend: "Yeah, I can hear you. Can you hear me?" (SYN-ACK)
You: "Yep, all good. Let's talk." (ACK)
Now both of you know the channel works in both directions. You didn't just assume. You verified. The conversation can begin.
TCP does exactly this, except both sides also exchange sequence numbers — a starting point for tracking packets — during this handshake.
Step-by-Step: SYN, SYN-ACK, ACK
Let's walk through it concretely. Your browser wants to connect to a web server.
Step 1 — SYN (Synchronise)
Your browser sends a TCP segment to the server with the SYN flag set. Alongside this, it sends its Initial Sequence Number (ISN) — a randomly chosen number, let's call it x.
What this says: "I want to connect. My sequence numbers will start at x. Are you there?"
The server receives this. The connection is now half-open on the server's side — it knows the client wants to connect, but hasn't confirmed it can yet.
Step 2 — SYN-ACK (Synchronise-Acknowledge)
The server responds with both the SYN and ACK flags set. It does two things:
Acknowledges your SYN: "I received your request, and I'm expecting your next byte to have sequence number
x+1."Sends its own Initial Sequence Number:
y. "My sequence numbers will start aty."
What this says: "Got you. I'm here. Let's synchronise. Your data starts at x+1, mine starts at y."
Step 3 — ACK (Acknowledge)
Your browser sends back a final ACK:
- Acknowledges the server's SYN: "Got your sequence number
y. Expectingy+1next from you."
What this says: "Perfect. I heard you. We're both synchronised. Connection established."
Three messages. Both sides have confirmed the channel works in both directions. Both sides know each other's starting sequence numbers. The connection is live.
Client Server
| |
| ------ SYN (seq=x) ------> |
| |
| <-- SYN-ACK (seq=y,ack=x+1) |
| |
| ------ ACK (ack=y+1) -----> |
| |
| Connection Established |
This is why TCP connections have latency before data flows. The round trip of these three messages adds time. On a high-latency connection, this is measurable. It's one reason HTTP/3 moved to QUIC — QUIC can sometimes establish connections with zero round trips for returning clients.
How Data Transfer Works in TCP
Now the connection exists. Your browser starts sending your HTTP request. How does TCP handle the actual data?
Sequence Numbers and Acknowledgements
Every byte TCP sends has a sequence number. If the browser sends 1,000 bytes starting from sequence number 100, those bytes are numbered 100 through 1,099.
The receiver — the server — sends back an ACK for every segment received. The ACK number says: "I've received everything up to this point. Send me the next byte starting from this number."
If the server received bytes 100–1,099 successfully, it sends ACK 1100: "Got everything. Send me 1100 next."
This continuous loop — send, acknowledge, send, acknowledge — is how TCP tracks that data arrived safely.
Windowing: Not Waiting After Every Packet
Here's something that would otherwise make TCP impractically slow: if the sender had to wait for an ACK after every single packet, the round-trip time would dominate everything. You'd be waiting constantly.
TCP uses a sliding window to solve this. The sender can have multiple packets "in flight" simultaneously — sent but not yet acknowledged — up to a limit called the window size.
Think of it like ordering at a restaurant. You don't place one item, wait for it to arrive, eat it, then order the next. You order everything at once and it comes as it's ready. The window is how many dishes can be on their way to your table at the same time.
The window size can grow over time as the connection proves reliable — TCP starts conservatively and speeds up. This is called slow start, and it's the algorithm behind why your download often accelerates after the first second.
How TCP Ensures Reliability, Order, and Correctness
Handling Lost Packets: Retransmission
What happens if a packet is lost? The receiver simply doesn't send an ACK for it.
The sender has a retransmission timer ticking for every unacknowledged packet. If the timer expires and no ACK arrived, the sender assumes the packet was lost and resends it.
There's also a faster mechanism: duplicate ACKs. If the receiver gets packet 5 after already receiving packets 1, 2, 3, it ACKs the last successfully received in-order packet — say, ACK 3. If the sender gets three of these duplicate ACKs, it doesn't wait for the timer. It knows packet 4 is missing and retransmits immediately. This is called fast retransmit.
Your application never sees this. As far as HTTP or your database driver is concerned, the bytes just arrived. TCP handled the loss and recovery silently.
Ordering
Because every byte has a sequence number, the receiver can always put packets back in the right order regardless of when they arrived. Got packet 5 before packet 3? Buffer packet 5, wait for packet 3, reassemble in order, deliver to the application in sequence.
Error Detection: Checksums
Every TCP segment includes a checksum — a number computed from the data in that segment. The receiver recalculates this checksum and compares it to the received value. If they don't match, the segment is silently discarded. The sender's retransmission timer handles the rest.
Corruption detected. Bad data never reaches your application.
Flow Control
The receiver tells the sender how much buffer space it has left using a field called the receive window. If the receiver's buffer is filling up, it shrinks this window, signalling the sender to slow down. If the buffer empties, it expands the window.
This prevents a fast sender from overwhelming a slow receiver. The sender adapts its pace dynamically based on what the receiver can handle.
How a TCP Connection Is Closed
Closing a TCP connection is also a negotiated process — and it's four steps, not three. This is called the 4-Way Teardown (or FIN handshake).
Why four steps? Because TCP connections are full-duplex — both sides send data independently. Closing one direction doesn't close the other. Each side has to independently signal that it's done sending.
Client Server
| |
| ------- FIN ------------> | "I'm done sending"
| |
| <------ ACK ------------- | "Got it"
| |
| <------ FIN ------------- | "I'm done sending too"
| |
| ------- ACK ------------> | "Got it. Goodbye."
| |
| Connection Closed |
After the client sends FIN, it enters a TIME_WAIT state before fully closing. This wait (typically 2 minutes) ensures any delayed packets from the old connection don't confuse a new one using the same port. If you've ever restarted a server and gotten "Address already in use", that's TIME_WAIT.
The server can also send data after receiving a FIN from the client — that's what full-duplex means. The client said "I'm done sending", not "stop everything." The server might still have data to finish sending before it sends its own FIN.
Putting It All Together
Here's the full lifecycle of a TCP connection for a single HTTP request, start to finish:
1. 3-Way Handshake
SYN → SYN-ACK → ACK
(Connection established)
2. Data Transfer
HTTP request sent with sequence numbers
Server ACKs received segments
Server sends HTTP response
Client ACKs received segments
(Retransmission happens if anything is lost)
3. 4-Way Teardown
FIN → ACK → FIN → ACK
(Connection closed gracefully)
Every HTTP request your browser makes goes through this. Every database query your application sends goes through this. Every SSH command you type goes through this.
What This Changes About How You Debug
When I told my friend his problem was a firewall blocking port 5432, he understood it differently after learning about the handshake. The firewall was dropping the SYN packet before it reached the database server. The handshake never completed. The connection never established. The Python driver kept timing out waiting for a SYN-ACK that would never come.
Once you understand the handshake, errors like "connection refused", "connection timed out", and "connection reset" stop being mysterious.
Connection refused: The port is reachable but nothing is listening on it. The OS sends back a RST (reset) immediately.
Connection timed out: The SYN went out but no SYN-ACK ever arrived. Firewall, wrong IP, server unreachable.
Connection reset: Something sent a RST mid-connection — often a firewall or a server that forcibly closed the connection.
The handshake is the diagnostic entry point. If it doesn't complete, nothing else matters. And now you know exactly what to look for.
TCP is one of those protocols that rewards understanding. You don't interact with it directly as an application developer — it's invisible by design. But when things go wrong at the network level, it's almost always the handshake that tells you why.