Understanding TCP Connection Termination: Four-Way Handshake, Half‑Close, Shutdown, and TIME_WAIT
This article explains how TCP connections are gracefully closed using the four‑way handshake, describes half‑close and half‑open states, compares the close and shutdown system calls, details the TIME_WAIT state and its impact, and offers practical techniques to reduce or avoid TIME_WAIT accumulation.
TCP connection termination requires a four‑way handshake because TCP is full‑duplex, so each direction must be closed independently, resulting in four packets being exchanged.
Four‑step termination sequence:
Client calls close() , sends FIN, enters FIN_WAIT1 .
Server receives FIN, sends ACK, enters CLOSE_WAIT awaiting its own close.
Client receives ACK, moves to FIN_WAIT2 (half‑close state).
Server finishes sending data, calls close() , sends FIN, enters LAST_ACK .
Client receives server FIN, sends ACK, enters TIME_WAIT .
Half‑close (Half‑Open) occurs when one side has closed its sending direction (FIN sent) but can still receive data from the peer. It may be unreliable if firewalls enforce a timeout.
close() function
#include <unistd.h>
int close(int fd);Calling close() decrements the socket’s reference count; when it reaches zero the kernel releases the socket and terminates both data streams.
shutdown() function
#include <sys/socket.h>
int shutdown(int sockfd, int how);
The shutdown() call causes all or part of a full‑duplex connection on the socket associated with sockfd to be shut down.
If how is SHUT_RD, further receptions are disallowed.
If how is SHUT_WR, further transmissions are disallowed.
If how is SHUT_RDWR, both receptions and transmissions are disallowed.Options for how :
SHUT_RD : disables reading; reads return EOF and incoming data is discarded.
SHUT_WR : disables writing; the socket sends a FIN, enters half‑close, and further writes fail.
SHUT_RDWR : combines both effects.
Differences between close and shutdown :
close closes the file descriptor; shutdown only disables traffic directions and still requires a subsequent close .
close may not immediately terminate the connection if other processes hold the descriptor; shutdown forces a FIN regardless of other references.
TIME_WAIT state
After an active close, the socket remains in TIME_WAIT for 2 MSL (Maximum Segment Lifetime), typically 60 seconds on Linux, to ensure delayed packets are discarded and to prevent old packets from being misinterpreted by new connections.
Impact: a large number of TIME_WAIT sockets can exhaust the pool of available client ports.
Ways to mitigate TIME_WAIT
Configure the 2 MSL value (if the OS allows).
Enable SO_REUSEADDR to reuse local addresses quickly.
Use SO_LINGER with l_onoff=1 and l_linger=0 to discard buffers and send RST, avoiding TIME_WAIT.
Design applications to reuse connections rather than frequently open/close them.
Employ connection pools in high‑concurrency environments.
Tune kernel TCP parameters (e.g., reduce timeout values).
struct linger {
int l_onoff; /* linger active */
int l_linger; /* how many seconds to linger for */
};Setting l_onoff to non‑zero and l_linger to zero causes close() to discard pending data and send an RST, effectively bypassing TIME_WAIT.
Cognitive Technology Team
Cognitive Technology Team regularly delivers the latest IT news, original content, programming tutorials and experience sharing, with daily perks awaiting you.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.