📌 核心结论(TL;DR):
三次握手:防止历史失效连接请求到达服务器,确认双方收发能力。
四次挥手:TCP 全双工,被动方先回 ACK,等数据发完再发 FIN。
CLOSE_WAIT 坑点 :如果服务器出现大量 CLOSE_WAIT,说明代码里忘记调用
close(),导致连接未彻底释放,文件描述符泄漏。TIME_WAIT 机制:主动关闭方必须等 2MSL(约60秒),保证最后一个 ACK 可靠到达,并清理网络上滞留给旧连接的数据包。
一、 抓包实战:连接的生老病死
(插入 tcpdump 的抓包截图)
使用 sudo tcpdump -i any -n tcp port 9999 可以清晰看到 TCP 报文的交互。报文中的标志位极其关键:
-
[S]:SYN(请求建立连接)。 -
[S.]:SYN+ACK(同意连接)。 -
[.]:ACK(确认报文)。 -
[P.]:PSH+ACK(包含业务数据的报文)。 -
[F.]:FIN+ACK(请求断开连接)。
二、 为什么是三次握手?
客户端发送 SYN 后,如果网络拥塞,这个报文滞留了,然后客户端超时重发了一个新的 SYN 并成功建立连接、传输数据、最后断开。此时,那个滞留的旧 SYN 终于到达了服务器。如果只有两次握手,服务器就会直接建立连接并在那里傻等,造成资源浪费。三次握手能让服务器确认客户端确实收到了自己的 SYN+ACK,证明客户端存活且没有用历史报文来捣乱。
三、 为什么是四次挥手?
TCP 连接是全双工的。当主动方发送 FIN 报文时,仅仅表示主动方没有数据要发送了,但被动方可能还有数据没有发完。
因此,被动方收到 FIN 后,必须先回复一个 ACK,告诉主动方:"我知道你要关闭了,但我还有点话要说。" 等被动方把数据全部发完之后,再单独发送一个 FIN 给主动方。

四、 致命排查:CLOSE_WAIT 泄漏 Bug
(插入 netstat 抓到大量 CLOSE_WAIT 的截图)
-
实战复现 :在之前的 TCP 服务器代码中,注释掉
close(conn_fd);。 -
现象 :客户端断开连接后,用
netstat -napt | grep 9999发现服务器端大量连接卡在CLOSE_WAIT状态。 -
原因 :服务器收到了客户端的
FIN并回ACK,但服务器代码没有调用close(),导致 TCP 协议栈没有发送FIN,连接无法闭环。 -
危害 :连接无法释放,服务器文件描述符耗尽,报
Too many open files错误。
五、 TIME_WAIT 状态的意义
主动断开方在发完最后一个 ACK 后,会进入 TIME_WAIT 状态,等待 2MSL。
-
保证最后一次握手成功 :如果最后一个
ACK丢了,对方重发FIN,此时我还在等待,可以重新补发ACK。 -
防止旧数据包干扰新连接:让旧连接中迷路的报文在网络中彻底消亡,保证下一个相同端口的新连接不会收到脏数据。
自我实战操作
咱们用两个极其常用的神器:netstat(看状态)和 tcpdump(看报文)。
bash
cd ~/linux_study/network_test
实战 1:用 tcpdump 亲眼看看三次握手和四次挥手
你需要开 3 个终端:
-
终端 A(抓包):
bash# 抓取 9999 端口的 TCP 包,-n 表示不解析域名,-i any 表示所有网卡 sudo tcpdump -i any -n tcp port 9999 -
终端 B(服务端) :运行你之前写的
./tcp_server。 -
终端 C(客户端) :运行
./tcp_client。输入一句话发送,然后按Ctrl+C退出客户端。
👉 看终端 A 的现象:
cpp
# 三次握手
IP 127.0.0.1.xxx > 127.0.0.1.9999: Flags [S] # 客户端发 SYN
IP 127.0.0.1.9999 > 127.0.0.1.xxx: Flags [S.] # 服务端发 SYN+ACK
IP 127.0.0.1.xxx > 127.0.0.1.9999: Flags [.] # 客户端发 ACK
# 传输数据
IP 127.0.0.1.xxx > 127.0.0.1.9999: Flags [P.] # 客户端发数据 (PSH+ACK)
IP 127.0.0.1.9999 > 127.0.0.1.xxx: Flags [P.] # 服务端回数据
IP 127.0.0.1.xxx > 127.0.0.1.9999: Flags [.] # 客户端确认收到
# 四次挥手
IP 127.0.0.1.xxx > 127.0.0.1.9999: Flags [F.] # 客户端发 FIN
IP 127.0.0.1.9999 > 127.0.0.1.xxx: Flags [.] # 服务端发 ACK
IP 127.0.0.1.9999 > 127.0.0.1.xxx: Flags [F.] # 服务端发 FIN
IP 127.0.0.1.xxx > 127.0.0.1.9999: Flags [.] # 客户端发 ACK
(注:有时候你看不到四次,因为服务端发 ACK 和 FIN 如果隔得很近,会变成 [F.] 合在了一起,这就是"捎带应答"。)


实战 2:制造并排查 CLOSE_WAIT 泄漏 Bug
打开你的多线程 tcp_server_thread.c 代码。
把里面的 close(conn_fd); 注释掉,重新编译运行。
-
启动服务器,连上客户端。
-
强杀客户端(
Ctrl+C)。 -
新开终端,敲:netstat -napt | grep 9999
👉 现象 :你会看到服务器的 9999 端口对应的连接状态变成了 CLOSE_WAIT。
这就是你面试时回答"如何排查网络连接泄漏"的最佳素材。

