Why TCP Can Close Connections in 2, 3, or 4 Handshakes – Hidden Mechanics Explained
This article explores TCP’s connection termination process, detailing the classic four‑way handshake, scenarios that reduce it to three or two handshakes, the concept of self‑connections, simultaneous opens, and practical implications for developers, including code examples and troubleshooting tips.
TCP is a connection‑oriented, reliable, byte‑stream transport protocol. Establishing, using, and releasing a connection correspond to the three‑way handshake, data transfer, and four‑way handshake respectively.
TCP Four‑Way Handshake
When data transmission is finished, either side can initiate the four‑way handshake to release the connection. The active side sends a FIN (via close() or shutdown()), indicating it will no longer send data.
The passive side acknowledges with an ACK. If the passive side still has data to send, it may transmit it before sending its own FIN. Finally the active side acknowledges the second FIN with an ACK.
Is a FIN always generated by close() or shutdown()?
Not necessarily. Calling close() or shutdown() on a socket will generate a FIN, but the FIN is also sent when a process exits (whether normally or via kill), regardless of which side initiates the exit.
FIN means "I will not send more data"; therefore shutdown() on the read side does not send a FIN, while shutting down the write side does.
Why might FIN‑WAIT‑2 be abundant on a machine?
FIN‑WAIT‑2 is the state of the active side waiting for the third handshake ( FIN) from the passive side. If the passive side never calls close() to send that FIN, the active side remains in FIN‑WAIT‑2, and the peer will accumulate CLOSE_WAIT sockets.
What happens to data received after the active side calls close()?
If the receive buffer still holds data, the kernel may send a RST. If the send buffer still has data, the kernel will wait for it to be transmitted before sending the first FIN. Because TCP is full‑duplex, close() closes both send and receive directions.
Can data be transferred between the second and third handshakes?
Yes. Using shutdown() with SHUT_WR performs a half‑close, allowing the application to continue receiving data while no more data is sent.
int shutdown(int sock, int howto);howto can be: SHUT_RD – close read side. SHUT_WR – close write side (pending data is still sent). SHUT_RDWR – close both sides (equivalent to close() ).
How to detect whether the peer used close() or shutdown()?
The passive side only sees a FIN, regardless of which method the active side used. If the passive side later reads and receives EOF, it knows the active side has closed its write side.
What if the passive side never sends the third handshake?
If the active side used shutdown(SHUT_WR), it stays in FIN‑WAIT‑2 until the peer finally sends FIN. If it used close(), it remains in FIN‑WAIT‑2 for net.ipv4.tcp_fin_timeout (typically 60 s) before moving to CLOSED without entering TIME‑WAIT.
# cat /proc/sys/net/ipv4/tcp_fin_timeout
60TCP Three‑Way Handshake Variants
When the passive side has no data to send after the first FIN, the second and third handshakes can be merged, resulting in a three‑handshake termination.
What about delayed ACK?
Delayed ACK allows the receiver to combine acknowledgments, which can further merge the second and third handshakes.
TCP Two‑Way Handshake (Self‑Connection)
If both ends use the same IP and port, the connection is a TCP self‑connection. The handshake is three‑way, but the termination can be done in just two steps.
Reproducing a self‑connection with code
Binding a client socket to a specific port and then connecting to the same address creates a self‑connection.
# cat /etc/os-release
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)" # nc -p 6666 127.0.0.1 6666 # netstat -nt | grep 6666
tcp 0 0 127.0.0.1:6666 127.0.0.1:6666 ESTABLISHEDA C program that performs the same steps is also provided (binding, connect, read/write, close).
Self‑connection troubleshooting
Ensure client and server ports differ (avoid the local port range 32768‑60999 for the server). Alternatively, detect self‑connections in Go’s net library and retry.
TCP Simultaneous Open
Two clients can connect to each other without a listening server, using simultaneous open. This also follows a four‑way handshake.
Reproducing simultaneous open
while true; do nc -p 2224 127.0.0.1 2223 -v; done
while true; do nc -p 2223 127.0.0.1 2224 -v; doneAfter retries, both sockets reach ESTABLISHED state.
Summary
TCP termination can involve four, three, or even two handshakes depending on data flow and use of shutdown().
FIN‑WAIT‑2 accumulation usually indicates the peer is not sending its final FIN. close() closes both directions; shutdown() can close only read or write.
Self‑connections use two‑handshake termination; simultaneous opens use four‑handshake establishment without a server.
NiuNiu MaTe
Joined Tencent (nicknamed "Goose Factory") through campus recruitment at a second‑tier university. Career path: Tencent → foreign firm → ByteDance → Tencent. Started as an interviewer at the foreign firm and hopes to help others.
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.
