Networking Tutorial · Core Protocols · Lesson 9 of 28

TCP

How TCP delivers a reliable, ordered byte stream: the three-way handshake, acknowledgements, and closing connections.

Create a free account to save this page, take notes and track your progress. Sign in

What TCP gives you

TCP (Transmission Control Protocol) turns unreliable packets into a reliable, ordered byte stream:

  • Lost packets are retransmitted.
  • Data arrives in order, without duplicates.
  • Flow control stops a fast sender from overwhelming a slow receiver.
  • Congestion control slows down when the network is overloaded.

HTTP/1.1, HTTP/2, database protocols, SSH and SMTP all run over TCP.

The three-way handshake

Before any data flows, client and server exchange three packets:

client -> server   SYN        "let's talk; my sequence number starts at x"
server -> client   SYN-ACK    "OK; mine starts at y, I got x"
client -> server   ACK        "got y"

That costs one round trip before the first byte of the request is sent, and TLS adds more. That is why reusing connections (keep-alive, connection pools) makes such a difference.

A client's TCP connection, state by stateTry it yourself
STATES = {
    ("CLOSED", "connect"): ("SYN_SENT", "send SYN"),
    ("SYN_SENT", "recv SYN-ACK"): ("ESTABLISHED", "send ACK"),
    ("ESTABLISHED", "close"): ("FIN_WAIT", "send FIN"),
    ("FIN_WAIT", "recv FIN+ACK"): ("TIME_WAIT", "send ACK, wait 2 x MSL"),
    ("TIME_WAIT", "timer"): ("CLOSED", "connection forgotten"),
}

state = "CLOSED"
for event in ["connect", "recv SYN-ACK", "close", "recv FIN+ACK", "timer"]:
    state, action = STATES[(state, event)]
    print(f"{event:14} -> {state:12} ({action})")

Output

connect        -> SYN_SENT     (send SYN)
recv SYN-ACK   -> ESTABLISHED  (send ACK)
close          -> FIN_WAIT     (send FIN)
recv FIN+ACK   -> TIME_WAIT    (send ACK, wait 2 x MSL)
timer          -> CLOSED       (connection forgotten)

Closing and TIME_WAIT

Each side sends FIN to close its direction. The side that closes first keeps the connection in TIME_WAIT for a while (60 seconds on Linux) so that late packets from the old connection can't be mistaken for a new one. A client that opens and closes thousands of short connections per second can run out of ephemeral ports this way. Reusing connections avoids it.

Timeouts you'll configure

  • Connect timeout: how long to wait for the handshake (keep it short, around 1 to 3 seconds).
  • Read timeout: how long to wait for response data.
  • Idle timeout: how long an unused connection stays open. Load balancers, NAT gateways and firewalls all have one, and when they disagree with your app's keep-alive settings you get mysterious resets.
Why connection reuse mattersTry it yourself
rtt_ms = 90                      # e.g. a user in Europe calling a server in the eastern US
requests = 30

new_connection_each_time = requests * (1 + 1 + 1) * rtt_ms   # TCP handshake + TLS 1.3 + request
reused_connection = (1 + 1) * rtt_ms + requests * rtt_ms     # handshakes once, then one RTT per request
print(f"new connection per request: {new_connection_each_time / 1000:.1f} s")
print(f"one reused connection:      {reused_connection / 1000:.1f} s")

Output

new connection per request: 8.1 s
one reused connection:      2.9 s

Test yourself

What is the correct order of the TCP three-way handshake?

  1. SYN, ACK, FIN
  2. SYN, SYN-ACK, ACK
  3. ACK, SYN, SYN-ACK
  4. HELLO, SYN, ACK
Show answer

Answer: SYN, SYN-ACK, ACK

The client sends SYN, the server answers SYN-ACK, and the client confirms with ACK.

Was this page helpful?