Home/Learn/Computer Networks/TCP 3-Way Handshake

TCP 3-Way Handshake

Beginner
Transport Layer

TCP establishes a reliable connection using a 3-way handshake (SYN → SYN-ACK → ACK) before any data is exchanged, and closes it with a 4-way FIN sequence.

Overview

TCP (Transmission Control Protocol) is connection-oriented — before data flows, both sides must agree to communicate and synchronise their sequence numbers. This is achieved through the 3-way handshake: the client sends SYN (synchronise), the server responds with SYN-ACK (synchronise-acknowledge), and the client replies with ACK (acknowledge). Only then is the connection established and data can flow. The handshake adds one full RTT of latency before the first byte of application data is sent — a key reason why HTTP/2 and QUIC were designed to reduce handshake overhead. Connection teardown uses a 4-way FIN sequence because each side must independently close its half of the connection. TCP also maintains state for each connection (SYN_SENT, ESTABLISHED, TIME_WAIT, etc.) — understanding this is critical for diagnosing connection exhaustion in high-traffic servers.

The 3-Way Handshake

The handshake synchronises initial sequence numbers (ISNs) on both sides. Sequence numbers ensure ordered, reliable delivery — each byte of data is numbered so the receiver can reorder or request retransmission.

TCP 3-way handshake flow and Java socket
// TCP 3-Way Handshake:
//
// Client                          Server
//   │                                │
//   │──── SYN (seq=x) ──────────────>│  Client picks random ISN x
//   │                                │  Server receives, picks ISN y
//   │<─── SYN-ACK (seq=y, ack=x+1) ─│  Server acks client's ISN
//   │                                │
//   │──── ACK (ack=y+1) ────────────>│  Client acks server's ISN
//   │                                │
//   │══════ Connection ESTABLISHED ══│
//   │──── HTTP GET /api/users ──────>│  Data can now flow

// In Java — the handshake is invisible but implicit:
Socket socket = new Socket("api.example.com", 443);
// ↑ This line triggers the full 3-way handshake before returning
// The OS TCP stack handles SYN/SYN-ACK/ACK transparently

// Cost: 1 RTT before first byte of data
// On a 100ms RTT link, every new TCP connection costs 100ms upfront

Connection Teardown — 4-Way FIN

TCP is full-duplex — each side has an independent send/receive stream. Teardown requires each side to independently close its stream, resulting in 4 messages. The TIME_WAIT state prevents delayed packets from a closed connection being misinterpreted by a new connection.

TCP 4-way FIN and TIME_WAIT handling
// TCP 4-Way Teardown:
//
// Client                          Server
//   │──── FIN ──────────────────────>│  Client done sending
//   │<─── ACK ───────────────────────│  Server acks
//   │                                │  Server may still send data...
//   │<─── FIN ───────────────────────│  Server done sending
//   │──── ACK ──────────────────────>│  Client acks
//   │                                │
//   │  [Client enters TIME_WAIT]     │  Waits 2×MSL (~60-120s)

// TIME_WAIT issues in high-traffic servers:
// Each closed connection occupies a port in TIME_WAIT for ~60s
// If you make 1000 new connections/sec, you exhaust ephemeral ports (64k)

// Fix — enable TCP connection reuse:
// SO_REUSEADDR allows binding to a port in TIME_WAIT
ServerSocket server = new ServerSocket();
server.setReuseAddress(true);          // SO_REUSEADDR
server.bind(new InetSocketAddress(8080));

// Better fix — use connection pooling to avoid teardown/handshake on every request
// HikariCP (DB), OkHttp (HTTP), Lettuce (Redis) all maintain persistent connections

TCP States

A TCP connection transitions through well-defined states. Understanding these states is essential for diagnosing connection issues with netstat or ss.

TCP states and diagnosing connection issues
// TCP state machine (key states):
// LISTEN      — server waiting for incoming connections
// SYN_SENT    — client sent SYN, waiting for SYN-ACK
// SYN_RECEIVED — server received SYN, sent SYN-ACK
// ESTABLISHED — connection active, data flowing
// FIN_WAIT_1  — sent FIN, waiting for ACK
// FIN_WAIT_2  — received ACK of FIN, waiting for server FIN
// TIME_WAIT   — waiting 2×MSL to ensure last ACK was received
// CLOSE_WAIT  — received FIN, waiting for app to close
// LAST_ACK    — sent FIN, waiting for final ACK

// Diagnose with:
// ss -tan state established  → all ESTABLISHED connections
// ss -tan state time-wait    → connections in TIME_WAIT
// netstat -an | grep CLOSE_WAIT → leak if many CLOSE_WAIT (app not closing sockets)

// CLOSE_WAIT leak in Java — common bug:
// HttpURLConnection conn = ...;
// // forgot to call conn.disconnect() or close InputStream
// // Connection stays in CLOSE_WAIT; server eventually runs out of file descriptors

Key Points to Remember

  • 1TCP handshake: SYN → SYN-ACK → ACK. Costs 1 RTT before data flows.
  • 2Sequence numbers synchronised during handshake enable ordered, reliable delivery.
  • 3Teardown is 4-way (FIN/ACK/FIN/ACK) because each direction closes independently.
  • 4TIME_WAIT lasts ~60–120s; high connection rates can exhaust ephemeral ports.
  • 5CLOSE_WAIT lingering usually means the application is not closing its sockets.
  • 6Connection pooling amortises handshake cost — essential for databases, HTTP, and Redis clients.

Interview Questions

Sign in to ask Aria
1

Explain the TCP 3-way handshake step by step.

EasyTCS
2

What is TIME_WAIT and why does it exist?

MediumAmazon
3

What is the difference between FIN_WAIT and CLOSE_WAIT?

MediumFlipkart
4

Why do we use connection pooling for database connections?

MediumThoughtWorks
5

How would you diagnose a server running out of file descriptors due to too many open sockets?

HardNetflix

Ask Aria about TCP 3-Way Handshake

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…