📌 核心结论(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(看报文)。
cd ~/linux_study/network_test实战 1:用tcpdump亲眼看看三次握手和四次挥手
你需要开3 个终端:
终端 A(抓包):
# 抓取 9999 端口的 TCP 包,-n 表示不解析域名,-i any 表示所有网卡 sudo tcpdump -i any -n tcp port 9999终端 B(服务端):运行你之前写的
./tcp_server。终端 C(客户端):运行
./tcp_client。输入一句话发送,然后按Ctrl+C退出客户端。
👉看终端 A 的现象:
# 三次握手 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。
这就是你面试时回答“如何排查网络连接泄漏”的最佳素材。