How TCP Three-Way Handshake and Four-Way Wave Work: A Complete Guide to Connection Lifecycle

The TCP three-way handshake establishes a full-duplex connection using SYN, SYN-ACK, and ACK packets, while the four-way wave terminates it gracefully with FIN and ACK exchanges to close each direction independently.

TCP (Transmission Control Protocol) is a connection-oriented, reliable transport-layer protocol that requires explicit connection establishment and teardown. According to the Snailclimb/JavaGuide repository, the mechanics of these control packets are documented in docs/cs-basics/network/tcp-connection-and-disconnection.md, which provides detailed state diagrams and kernel-level implementation details.

TCP Three-Way Handshake: Establishing the Connection

Before application data flows, client and server must agree on Initial Sequence Numbers (ISN) and verify bidirectional reachability through three synchronized steps:

  1. SYN – Client → Server: The client sends a SYN packet with its initial sequence number (seq = ISN_C), entering the SYN_SENT state.
  2. SYN+ACK – Server → Client: The server acknowledges with ACK (setting ack = ISN_C+1) and synchronizes its own ISN (seq = ISN_S), entering SYN_RCVD.
  3. ACK – Client → Server: The client acknowledges the server's ISN (ack = ISN_S+1), moving both endpoints to the ESTABLISHED state.

This exchange ensures both parties have confirmed each other's ISN and validated that the network path is operational in both directions. The design prevents "ghost" connections from delayed duplicate SYNs by requiring a closed-loop confirmation before transitioning to ESTABLISHED.

TCP Four-Way Wave: Terminating the Connection

Because TCP is full-duplex, each transmission direction must close independently, requiring four distinct packets:

  1. FIN – Initiator → Peer: When one side finishes sending data, it transmits a FIN packet (seq = u) and enters FIN_WAIT_1.
  2. ACK – Peer → Initiator: The peer acknowledges the FIN (ack = u+1) and enters CLOSE_WAIT, while the initiator moves to FIN_WAIT_2.
  3. FIN – Peer → Initiator: After transmitting remaining data, the peer sends its own FIN packet (seq = y) and enters LAST_ACK.
  4. ACK – Initiator → Peer: The initiator acknowledges the final FIN (ack = y+1), entering TIME_WAIT before eventually reaching CLOSED after 2×MSL (Maximum Segment Lifetime).

The TIME_WAIT state ensures that stray duplicate FINs or delayed packets from the closed connection are safely discarded, preventing data corruption in subsequent connections that might reuse the same port numbers.

Kernel Implementation: SYN and Accept Queues

The Snailclimb/JavaGuide source highlights critical kernel data structures that manage the handshake process:

  • Half-connection queue (SYN Queue): Stores connections that have completed the first two handshake steps (received client's SYN and sent SYN-ACK) but are awaiting the final ACK. This queue is bounded by net.ipv4.tcp_max_syn_backlog.
  • Full-connection queue (Accept Queue): Holds connections that have finished the three-way handshake and are waiting for the application to call accept(). When this queue fills, the kernel may drop incoming connections or enable SYN cookies to prevent SYN flood attacks.

Practical TCP in Java

While the TCP state machine operates at the OS level, Java developers interact with these mechanisms through standard socket APIs. The following examples demonstrate how the three-way handshake and four-way wave manifest in application code.

TCP Server Implementation

import java.io.*;
import java.net.*;

public class TcpServer {
    public static void main(String[] args) throws IOException {
        try (ServerSocket server = new ServerSocket(8080)) {
            System.out.println("Server listening on 8080...");
            
            // accept() blocks until the 3-way handshake completes
            Socket client = server.accept();  // State: ESTABLISHED
            
            BufferedWriter out = new BufferedWriter(
                new OutputStreamWriter(client.getOutputStream()));
            out.write("Connection established via 3-way handshake\n");
            out.flush();
            
            // close() initiates the 4-way wave
            client.close();  // State transitions: FIN_WAIT_1 → TIME_WAIT
        }
    }
}

TCP Client Implementation

import java.io.*;
import java.net.*;

public class TcpClient {
    public static void main(String[] args) throws IOException {
        // Constructor performs the 3-way handshake (SYN → SYN-ACK → ACK)
        try (Socket socket = new Socket("localhost", 8080)) {
            BufferedReader in = new BufferedReader(
                new InputStreamReader(socket.getInputStream()));
            System.out.println("Server response: " + in.readLine());
            
            // Exiting try-with-resources triggers socket.close()
            // Initiates FIN → ACK → FIN → ACK sequence
        }
    }
}

Under the hood, new Socket() triggers the operating system to transmit the SYN and wait for SYN-ACK before returning, while socket.close() sends the first FIN packet to begin the termination sequence documented in docs/cs-basics/network/tcp-connection-and-disconnection.md.

Summary

  • The three-way handshake (SYN, SYN-ACK, ACK) establishes a full-duplex TCP connection by synchronizing ISNs and verifying bidirectional reachability.
  • The four-way wave (FIN, ACK, FIN, ACK) terminates connections independently in each direction, allowing residual data transmission while preventing data loss.
  • Kernel SYN queues and accept queues manage connection state during the handshake, with parameters like tcp_max_syn_backlog controlling resource limits.
  • The TIME_WAIT state (2×MSL) ensures reliable connection teardown by absorbing delayed packets from the previous session.

Frequently Asked Questions

Why does TCP use a three-way handshake instead of two?

A two-way handshake would only confirm that the server can reach the client, but not vice versa. The third ACK packet verifies that the client received the server's ISN and guarantees bidirectional reachability. This prevents "ghost" connections caused by delayed SYN packets from previous connection attempts that could erroneously establish a connection without both parties actively participating.

What happens if the final ACK in the four-way wave is lost?

If the final ACK from the initiator is lost, the peer remains in the LAST_ACK state and will retransmit its FIN packet after a timeout. The initiator, already in TIME_WAIT, will receive the duplicate FIN and retransmit the final ACK. The 2×MSL TIME_WAIT duration is specifically calculated to allow enough time for this retransmission cycle to complete, ensuring both sides eventually reach the CLOSED state.

How do SYN cookies prevent SYN flood attacks?

When the half-connection queue (SYN Queue) fills, the kernel enables SYN cookies by encoding connection state (MSS, timestamp, server IP/port) into the TCP sequence number sent in the SYN-ACK rather than storing state in memory. If the client is legitimate and returns the final ACK, the server can reconstruct the connection context from the acknowledgment number, allowing connection establishment without consuming queue resources for spoofed SYN requests.

Can application data be sent during the TCP handshake?

No. The TCP specification requires that the three-way handshake complete and both endpoints reach the ESTABLISHED state before transmitting application payload. However, TCP Fast Open (TFO) extensions allow data in the initial SYN packet under specific conditions, though this requires cryptographic cookies and is not the standard behavior described in the Snailclimb/JavaGuide documentation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →