TCP Connection Management
Every time a browser loads a page, two machines that have never spoken before must agree to talk. They may be separated by a dozen routers. Unpredictable delay and a network that silently drops packets add further complications. Before a single byte of the HTTP request is sent, both sides need synchronized sequence numbers. They also need agreed buffer sizes. Both must share an understanding of when the conversation is over. This is the job of TCP connection management. It includes the handshake that opens a connection. It includes the state machine that keeps both ends honest while data flows. It also includes the teardown protocol that closes the connection without losing data or leaking resources. This machinery matters because TCP is the substrate underneath almost all reliable internet communication. HTTP, HTTPS, SSH, email, and database replication all ride on it. A subtle bug in connection setup or teardown does not just crash one flow. It can exhaust file descriptors on a server. It can leave sockets stuck in limbo. It can even open a security hole that lets an attacker impersonate a stranger. The infamous SYN flood denial-of-service attack is one direct consequence. The equally infamous "TIME_WAIT exhaustion," which has taken down production load balancers, is another. Both stem from how this state machine is designed and used. The big picture is that TCP layers a connection-oriented, reliable byte stream on top of IP. IP itself is a connectionless, unreliable packet-delivery service. Reliability requires state: each side must remember what has been sent. Each side must also remember what has been acknowledged. Each side must know what the peer is willing to receive. Connection management is the protocol for establishing that shared state (the three-way handshake). It also maintains that state correctly against reordering, duplication, and loss (the TCP finite state machine). Finally, it tears the state down cleanly. This ensures neither side is fooled by stray packets from a dead connection (four-way close with TIME_WAIT). This article builds the segment format from the header up. It walks through the handshake and teardown step by step, using pseudocode and a state-machine diagram. It proves why the three-way handshake is the minimum needed for correctness. It also traces a real packet exchange end to end. This article builds directly on Principles of Reliable Data Transfer. It sets up the mechanisms — window advertisement and congestion signaling — that later drive TCP Flow Control and TCP Congestion Control.
- TCP segments carry sequence and acknowledgment numbers, control flags (SYN, ACK, FIN, RST), and a receive window in every header.
- Connections open with a three-way handshake: SYN, SYN-ACK, ACK, which synchronizes initial sequence numbers in both directions.
- The three-way handshake is provably the minimum exchange that lets both sides confirm a live, mutually acknowledged connection.
- Closing uses a four-way exchange (FIN/ACK each direction) because TCP connections are full-duplex and each half closes independently.
- The TCP finite state machine (CLOSED → SYN_SENT/LISTEN → ESTABLISHED → …→ CLOSED) formalizes every legal transition a connection can take.
- TIME_WAIT is not a bug: it exists to absorb stray duplicate segments and guarantee the passive closer receives the final ACK.
TCP connection management uses a three-way handshake to synchronize state before any data flows, and a careful multi-step teardown to guarantee no side is misled by a connection that only looks dead.
The idea
A TCP connection is not a physical circuit; it is agreed-upon state held independently in the operating system kernels of two endpoints, each represented by a socket identified by a 4-tuple: (source IP, source port, destination IP, destination port). Because IP offers no ordering, no reliability, and no duplicate suppression, TCP must reconstruct all three properties itself using sequence numbers — a running count of bytes in the stream, not of segments — and acknowledgment numbers, which state the next byte a receiver expects. Connection management is the sub-protocol responsible for three specific moments in a connection's life: establishing agreement on initial sequence numbers (connection setup), classifying every possible received segment against the connection's current state (the TCP state machine), and shutting the connection down so that both sides free resources without either losing pending data or being confused by delayed duplicate packets (connection teardown). The header fields that matter for this article are: the 32-bit Sequence Number, the 32-bit Acknowledgment Number (valid only when the ACK flag is set), and six single-bit control flags — SYN (synchronize, used to open), ACK (acknowledgment valid), FIN (finish, no more data from sender), RST (reset, abort abnormally), PSH (push buffered data to the application now), and URG (urgent pointer valid). A segment can carry zero or more of these flags simultaneously; a SYN and an ACK routinely travel together in the same segment during setup. Every connection endpoint also tracks a receive window, advertised in every segment, which foreshadows TCP Flow Control but is irrelevant to the correctness arguments in this article — here we focus purely on how the sequence-number and flag machinery establishes and destroys shared state. The core intellectual content of connection management is a distributed agreement problem under an unreliable channel: two parties must reach mutual, verified knowledge of a shared initial state (for setup) or a shared decision to stop (for teardown), while messages can be lost, delayed, duplicated, or reordered. TCP's solutions — the three-way handshake and the four-message close with a timed wait state — are specific, minimal, and provably necessary protocols for this class of problem, not arbitrary engineering choices.
How it works
Segment structure relevant to connection management. Each TCP segment header includes: ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Port | Destination Port | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Acknowledgment Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Offset|Rsvd|U|A|P|R|S|F| Receive Window | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ```
Three-way handshake (connection setup). The client picks an Initial Sequence Number (ISN), call it `client_isn`, chosen pseudo-randomly (historically to prevent sequence-number-guessing hijack attacks). The server independently picks `server_isn`. ``` CLIENT SERVER ------ ------ state: CLOSED state: LISTEN 1) send SYN, seq = client_isn state -> SYN_SENT -------------> receive SYN state -> SYN_RCVD 2) <------------- send SYN+ACK, seq = server_isn, ack = client_isn + 1 receive SYN+ACK state -> ESTABLISHED 3) send ACK, seq = client_isn + 1, ack = server_isn + 1 -------------> receive ACK state -> ESTABLISHED ``` Both sides now know: their own ISN, the peer's ISN, and — crucially — that the peer has acknowledged their ISN, meaning the peer really received it (not a stale duplicate from an old incarnation of the connection). Data may piggyback on segment 3 already, since the client already knows the connection is live at that point (this is sometimes done, e.g. in TCP Fast Open extensions, though ordinary TCP still waits).
Connection teardown (four-way close, "graceful close"). Because a TCP connection is full-duplex, closing is really two independent half-closes: ``` CLIENT SERVER ------ ------ state: ESTABLISHED state: ESTABLISHED 1) send FIN, seq = x state -> FIN_WAIT_1 -------------> receive FIN state -> CLOSE_WAIT 2) <------------- send ACK, ack = x+1 receive ACK state -> FIN_WAIT_2 ... server's application finishes sending remaining data ... state -> LAST_ACK 3) <------------- send FIN, seq = y receive FIN state -> TIME_WAIT 4) send ACK, ack = y+1 -------------> receive ACK state -> CLOSED (wait 2·MSL) state -> CLOSED ```
Pseudocode: TCP connection-management state machine. This models the per-socket state transition function; it is the heart of the mechanism and is what a real TCP stack implements almost verbatim. ``` enum State { CLOSED, LISTEN, SYN_SENT, SYN_RCVD, ESTABLISHED, FIN_WAIT_1, FIN_WAIT_2, CLOSING, TIME_WAIT, CLOSE_WAIT, LAST_ACK } struct Connection { State state = CLOSED int send_next // next sequence number this side will send int send_una // oldest unacknowledged sequence number int recv_next // next sequence number expected from peer } // --- Active open: application calls connect() --- function ACTIVE_OPEN(conn): conn.send_next = choose_random_isn() send_segment(SYN, seq = conn.send_next) conn.send_next += 1 // SYN consumes one sequence number conn.state = SYN_SENT // --- Passive open: application calls listen() --- function PASSIVE_OPEN(conn): conn.state = LISTEN // --- Central receive handler --- function ON_SEGMENT_RECEIVED(conn, seg): switch conn.state: case LISTEN: if seg.SYN and not seg.ACK: conn.recv_next = seg.seq + 1 conn.send_next = choose_random_isn() send_segment(SYN | ACK, seq = conn.send_next, ack = conn.recv_next) conn.send_next += 1 conn.state = SYN_RCVD case SYN_SENT: if seg.SYN and seg.ACK and seg.ack == conn.send_next: conn.recv_next = seg.seq + 1 conn.send_una = seg.ack send_segment(ACK, seq = conn.send_next, ack = conn.recv_next) conn.state = ESTABLISHED else if seg.SYN and not seg.ACK: // simultaneous open conn.recv_next = seg.seq + 1 send_segment(SYN | ACK, seq = conn.send_next - 1, ack = conn.recv_next) conn.state = SYN_RCVD case SYN_RCVD: if seg.ACK and seg.ack == conn.send_next: conn.send_una = seg.ack conn.state = ESTABLISHED else if seg.FIN: conn.state = CLOSE_WAIT case ESTABLISHED: if seg.FIN: conn.recv_next = seg.seq + 1 send_segment(ACK, ack = conn.recv_next) conn.state = CLOSE_WAIT else: deliver_data_and_ack(conn, seg) // ordinary data transfer case FIN_WAIT_1: if seg.ACK and seg.ack == conn.send_next: // our FIN was ACKed conn.state = (seg.FIN) ? TIME_WAIT : FIN_WAIT_2 if seg.FIN: conn.recv_next = seg.seq + 1 send_segment(ACK, ack = conn.recv_next) start_timer(2 MSL, () -> conn.state = CLOSED) else if seg.FIN: // simultaneous close conn.recv_next = seg.seq + 1 send_segment(ACK, ack = conn.recv_next) conn.state = CLOSING case FIN_WAIT_2: if seg.FIN: conn.recv_next = seg.seq + 1 send_segment(ACK, ack = conn.recv_next) conn.state = TIME_WAIT start_timer(2 MSL, () -> conn.state = CLOSED) case CLOSING: if seg.ACK and seg.ack == conn.send_next: conn.state = TIME_WAIT start_timer(2 MSL, () -> conn.state = CLOSED) case CLOSE_WAIT: // waiting for local application to call close() pass case LAST_ACK: if seg.ACK and seg.ack == conn.send_next: conn.state = CLOSED free(conn) case TIME_WAIT: if seg.FIN: // peer retransmitted its FIN send_segment(ACK, ack = conn.recv_next) // re-ACK, restart timer restart_timer(2 MSL) // --- Application calls close() --- function APP_CLOSE(conn): switch conn.state: case ESTABLISHED: send_segment(FIN, seq = conn.send_next) conn.send_next += 1 conn.state = FIN_WAIT_1 case CLOSE_WAIT: send_segment(FIN, seq = conn.send_next) conn.send_next += 1 conn.state = LAST_ACK ```
Complexity & correctness
Message and time complexity of setup. The handshake uses exactly 3 messages and completes in 1.5 round-trip times (RTT) in the best case (client sends SYN at t=0, receives SYN+ACK at t=RTT/2·2, sends ACK immediately). This is messages, independent of any data size — the cost that matters for short-lived connections (this is precisely why protocols layered over TCP, such as TLS and QUIC, work hard to reduce or eliminate this fixed 1–2 RTT tax via session resumption or 0-RTT modes).
Why exactly three messages, not two or four — a correctness argument. Model the problem as: both parties must reach mutual belief that both ISNs are known and acknowledged as received, over a channel where messages can be lost.
- Two messages are insufficient. Suppose the client sends SYN and the server replies SYN+ACK, and the connection is declared ESTABLISHED at the server upon sending its reply. The server has no proof the client ever received the SYN+ACK. If that segment is lost, the client will retransmit SYN (timing out in SYN_SENT), but the server already believes the connection is live and may have released resources or accepted data it thinks is acknowledged — a desynchronization. A third message is required so the server also gets confirmation that the client received its ISN.
- Three messages suffice. After step 3 (ACK), the client knows: its own ISN, the server's ISN (learned from step 2, itself carrying an ACK proving the server got the client's ISN). The server, upon receiving step 3, knows the client has the server's ISN and is acknowledging it. Both sides simultaneously hold a mutually confirmed pair of ISNs. This is the well-known result that common knowledge cannot be established with certainty over a lossy channel in a bounded number of messages (the Two Generals problem) — but TCP does not need certainty, only that if the connection reaches ESTABLISHED on both sides, both hold consistent state; any handshake message can still be lost, in which case the initiator simply times out and retries, at worst wasting a retry, never corrupting state.
- A stale/duplicate SYN is rejected safely. If an old, delayed SYN from a previous, already-closed connection arrives at a listening server, the server replies SYN+ACK with a new ISN. The original (long-gone) client either never responds (timeout, no state ever committed) or, if the real client's OS still holds vestigial state, correctly RSTs the spurious SYN+ACK because the acknowledgment number does not match anything it sent. Randomized ISNs also make it computationally hard for an off-path attacker to forge the sequence numbers needed to inject data into someone else's connection.
Why teardown needs four messages, not two. TCP is full-duplex: a FIN only says "I will send no more data," not "you may send no more either." The half-close design (client can FIN while still able to receive server data, as in `FIN_WAIT_2`) is a deliberate feature — e.g., a client that has finished uploading a request body but still expects a long streamed response. Each direction's FIN must be independently sent and independently acknowledged, giving a lower bound of four control messages (though two are commonly piggybacked, collapsing the visible exchange).
Why TIME_WAIT must exist — proof sketch. Consider the passive closer's final ACK (step 4) being lost. The active closer, sitting in FIN_WAIT_2 or having just sent its ACK, has no way to know this without retransmission from the peer. TCP's fix: whichever side sends the final ACK enters TIME_WAIT for 2·MSL (Maximum Segment Lifetime, conventionally assumed ≤ 30–120 s depending on implementation), instead of transitioning immediately to CLOSED. Two guarantees follow: 1. Reliable final ACK delivery. If the passive closer's FIN retransmission arrives because the original ACK was lost, the active closer (still in TIME_WAIT, not yet CLOSED) can resend the ACK. Without TIME_WAIT, that ACK-request would hit a CLOSED socket and elicit a RST, corrupting the peer's clean close. 2. No cross-talk between incarnations. MSL bounds the maximum time any stray segment from this connection can still be wandering the network. Waiting 2·MSL (one MSL for a stray segment to arrive, one more for a possible reply to expire) guarantees that when a new connection later reuses the same 4-tuple, no old duplicate segment from the dead connection can still arrive and be misinterpreted as part of the new one.
Space complexity. Each open or closing connection requires per-connection kernel state (a Transmission Control Block: sequence counters, window, timers, state enum) — independent of how much data has been transferred, since sequence numbers, not full byte history, encode progress. The practical cost is that a server fielding many short-lived connections accumulates many TIME_WAIT sockets simultaneously; since each still consumes a TCB and a port, aggregate memory is , which is the operational pain point addressed by tuning `SO_REUSEADDR`, shortening MSL assumptions, or moving to connection pooling.
Honest tradeoffs. The setup cost (1–2 RTT) is a real, unavoidable latency tax for short requests — this is precisely the pain point that motivates TLS session resumption and QUIC's 0-RTT/1-RTT handshakes. TIME_WAIT trades a small amount of server memory and local port pressure for a strong safety guarantee; disabling or shortening it (as some tuned high-throughput servers do) reintroduces the very corruption risk it was designed to prevent, and is only safe if paired with other precautions (e.g., strict timestamp-based duplicate rejection).
Worked example
Client `203.0.113.5:51000` opens an HTTPS-style connection to server `198.51.100.7:443`. Assume RTT ≈ 40 ms (20 ms one-way), MSS negotiation omitted for brevity, MSL = 30 s.
| t (ms) | Direction | Segment | Flags | Seq | Ack | Sender state after |
|---|---|---|---|---|---|---|
| 0 | C→S | 1 | SYN | 5000 | — | SYN_SENT |
| 20 | S→C | 2 | SYN, ACK | 9000 | 5001 | SYN_RCVD |
| 40 | C→S | 3 | ACK | 5001 | 9001 | ESTABLISHED |
| 40 | (server receives 3) | — | — | — | — | ESTABLISHED |
| 45 | C→S | 4 (data) | ACK, PSH | 5001 | 9001 | ESTABLISHED (request sent, 300 B) |
| 65 | S→C | 5 (data) | ACK | 9001 | 5301 | ESTABLISHED (response begins) |
| … | … | … (data transfer, out of scope here) | … | … | … | … |
| 500 | C→S | 20 | FIN, ACK | 5301 | 90001 | FIN_WAIT_1 |
| 520 | S→C | 21 | ACK | 90001 | 5302 | server → CLOSE_WAIT |
| 560 | S→C | 22 | FIN, ACK | 90001 | 5302 | server → LAST_ACK |
| 560 | (client receives 22) | — | — | — | — | client: FIN_WAIT_2 → TIME_WAIT |
| 580 | C→S | 23 | ACK | 5302 | 90002 | client stays TIME_WAIT |
| 580 | (server receives 23) | — | — | — | — | server: LAST_ACK → CLOSED |
| 30580 | (client's 2·MSL timer fires: 2×30000 ms after entering TIME_WAIT) | — | — | — | — | client: TIME_WAIT → CLOSED |
Trace the invariants explicitly: at t=40 both sides believe `client_isn = 5000` and `server_isn = 9000`, and both have proof the other side received it (client's proof: segment 2's ack=5001; server's proof: segment 3's ack=9001). During teardown, notice the client's FIN at t=500 and the server's ACK+FIN are not merged — the server had 40 ms of remaining data logic to run in CLOSE_WAIT (t=520 to 560) before it, too, was ready to close; this is the half-close behavior in action. The client sits in TIME_WAIT for 2×30 s not because anything is wrong, but purely as an insurance policy against segment 22 or 23 needing retransmission, and to guarantee no duplicate of this connection's segments can arrive after the client reuses port 51000.
Common pitfalls & traps
| Pitfall | Why it happens | Consequence |
|---|---|---|
| Treating FIN as "connection is dead" | Confusing full-duplex half-close with a hard stop | Data sent by the still-open half after a FIN is dropped or mishandled by naive code |
| Forgetting simultaneous-close/open cases | Textbook diagrams show only the common asymmetric path | Real implementations must also handle both sides sending SYN or FIN at once (states CLOSING, simultaneous SYN_SENT→SYN_RCVD) |
| "TIME_WAIT is a bug, disable it" | High-traffic server sees thousands of TIME_WAIT sockets and assumes leak | Disabling it can reintroduce data corruption between connection incarnations sharing a 4-tuple |
| Off-by-one on sequence numbers | SYN and FIN each consume one sequence number even though they carry no payload | ACK numbers computed as `seq` instead of `seq+1` desynchronize both endpoints permanently |
| SYN flood mistaken for normal load | Attacker sends many SYNs, never completing step 3 | Server's SYN_RCVD backlog queue fills, legitimate connections refused — fixed via SYN cookies |
| Reusing a port immediately after close | Assuming CLOSED state is instantaneous everywhere | Stray duplicate segments from the old connection can be delivered to the new one if TIME_WAIT is skipped |
Pioneers & history
TCP's connection-management design descends directly from the need to make an unreliable packet network behave reliably — a problem first solved in full generality by Vint Cerf and Robert Kahn's original internetworking proposal, later split into IP (unreliable delivery) and TCP (reliability, ordering, connection state) by Jon Postel's editorship of the RFCs that formalized the handshake and finite-state-machine specification still followed today.
Origins · 1 minRobert E. Kahn and how this was discovered
Where this matters
Every HTTP/1.1 and HTTP/2 connection, every SSH session, every database client library, and every message queue that uses TCP sockets relies on this exact handshake and teardown machinery underneath its application protocol. Load balancers and reverse proxies (NGINX, HAProxy, cloud load balancers) explicitly tune TIME_WAIT behavior and connection-reuse policies because TCP connection churn is often the dominant scalability bottleneck at high request rates. Firewalls and intrusion-detection systems inspect SYN/ACK/FIN/RST flag sequences to distinguish legitimate traffic from port scans and SYN-flood attacks, and operating system kernels expose tunables (`net.ipv4.tcp_fin_timeout`, SYN backlog size, SYN cookies) that are direct knobs on this state machine. Modern transport protocols such as QUIC deliberately redesign this exact handshake to cut round trips, a design choice only understandable once the classical three-way handshake's costs and guarantees — covered here — are clear; the same state-machine discipline reappears, with new twists, in TCP Flow Control and TCP Congestion Control.
Ask the tutor: How does TCP's handshake compare to QUIC's combined transport-and-crypto handshake, and why can QUIC achieve 0-RTT reconnection safely?
Why does TCP need a handshake at all — why not just start sending data?
Without a handshake, neither side has an agreed starting sequence number, so acknowledgments would be meaningless — a receiver couldn't tell which byte an ACK referred to. The handshake also protects against a delayed duplicate SYN from a long-dead prior connection attempt being mistaken for a fresh one, since the fresh connection's ISN is freshly randomized and unrelated to the old one. It additionally lets the server refuse gracefully (via RST) if no process is listening on the destination port, rather than silently accepting data into nowhere. Finally, it allows both sides to negotiate options — maximum segment size, window scaling, selective acknowledgment support — that must be fixed before real data transfer begins. All of this must happen atomically as a paired exchange, which is exactly what the three-way handshake achieves in the minimum number of round trips.
Is the three-way handshake vulnerable to attack, and if so how is it defended?
Yes — a SYN flood attack exploits the gap between SYN_RCVD (server commits a Transmission Control Block and adds it to a backlog queue) and the completing ACK, which an attacker never sends. Sending many spoofed SYNs exhausts the server's backlog, denying service to legitimate clients. The standard defense is SYN cookies: instead of storing state after step 1, the server encodes the necessary state (a hash of the connection's identifying fields plus a timestamp and secret) directly into the ISN it sends back in step 2, and reconstructs it only if a valid ACK ever arrives in step 3. This makes SYN_RCVD effectively stateless from the server's perspective until the handshake actually completes, at some cost to optional negotiated features like window scaling.
Why is TIME_WAIT set to 2×MSL specifically, rather than 1×MSL or some other value?
MSL bounds how long any IP packet, including a retransmitted TCP segment, can plausibly still be in transit in the network before being dropped by routers (via the IP TTL field, roughly). The peer that sends the final ACK must survive two distinct risk windows: one MSL for a duplicate of the peer's last FIN to still arrive (if the ACK was lost, the peer resends FIN), and one more MSL for the peer to receive the retransmitted ACK and for that exchange to fully settle in the network. Waiting exactly one MSL would only cover the first risk, not guarantee the resent ACK itself has vanished from the network by the time this connection's ports are reused.
What happens if the server never receives the client's final ACK in the four-way close?
The server, sitting in LAST_ACK, has an active retransmission timer for its own FIN. If the ACK never arrives, the timer expires and the server retransmits its FIN. If the client is still in TIME_WAIT (which is exactly the point of that state), it receives the retransmitted FIN, resends the ACK, and restarts its own TIME_WAIT timer. If the client's TIME_WAIT had already expired and its socket were fully CLOSED, the retransmitted FIN would arrive at a socket with no matching connection, and the client's OS would respond with an RST — which the server would interpret as an abnormal reset rather than a clean close, an inconsistency the TIME_WAIT window is specifically sized to prevent under normal network conditions.
How does a "simultaneous open" or "simultaneous close" differ from the textbook diagram?
In the common case, one side is passively listening (server) while the other actively connects (client), giving the clean asymmetric SYN → SYN+ACK → ACK sequence shown above. TCP also defines a symmetric path: if both sides independently send SYN segments to each other at essentially the same time (both in SYN_SENT), each receiving side transitions to SYN_RCVD and replies with its own SYN+ACK, eventually both reaching ESTABLISHED without ever visiting LISTEN. The analogous case exists for closing — if both sides send FIN before receiving the peer's FIN, both transition through the CLOSING state (visible in the state-machine diagram) instead of one going through CLOSE_WAIT. These paths are rare in client-server applications but are essential for peer-to-peer TCP usage and are part of the formally specified state machine, not an afterthought.
Does connection management add meaningful latency for real applications, and how do modern protocols address it?
Yes — for a single small HTTP request, the 1–2 RTT handshake cost can rival or exceed the time to transmit the actual payload, especially combined with TLS's own handshake layered on top (historically another 1–2 RTT). This compounding cost is precisely why HTTP keep-alive and connection pooling amortize one handshake over many requests, why TLS 1.3 supports session resumption to skip its own handshake round trip, and why QUIC (the transport underneath HTTP/3) redesigns connection setup to combine transport and cryptographic handshaking into a single round trip, or zero on reconnection. None of these optimizations eliminate the need for the underlying agreement problem TCP solves — they just apply the same reasoning about minimal-message agreement more aggressively.
Why does a RST segment bypass the normal graceful-close sequence entirely?
RST signals an abnormal termination — a connection to a port with no listening process, a violation of the state machine's expected sequence numbers, or an application explicitly aborting via a "hard close." Unlike FIN, RST carries no promise of delivering remaining buffered data and requires no acknowledgment handshake; the receiving side simply tears down its TCB immediately upon a valid RST (one whose sequence number falls within the current receive window, preventing off-path spoofing). This makes RST fast and resource-cheap, which is exactly why systems like SYN-flood mitigations, load-balancer connection draining, and misbehaving-peer detection use it deliberately instead of a full four-way close.