:TCP 状态机与 tcpdump 抓包排雷实战)
核心结论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.]SYNACK同意连接。[.]ACK确认报文。[P.]PSHACK包含业务数据的报文。[F.]FINACK请求断开连接。二、 为什么是三次握手客户端发送SYN后如果网络拥塞这个报文滞留了然后客户端超时重发了一个新的SYN并成功建立连接、传输数据、最后断开。此时那个滞留的旧SYN终于到达了服务器。如果只有两次握手服务器就会直接建立连接并在那里傻等造成资源浪费。三次握手能让服务器确认客户端确实收到了自己的SYNACK证明客户端存活且没有用历史报文来捣乱。三、 为什么是四次挥手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。输入一句话发送然后按CtrlC退出客户端。看终端 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.] # 服务端发 SYNACK 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.] # 客户端发数据 (PSHACK) 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);注释掉重新编译运行。启动服务器连上客户端。强杀客户端CtrlC。新开终端敲netstat -napt | grep 9999现象你会看到服务器的9999端口对应的连接状态变成了CLOSE_WAIT。这就是你面试时回答“如何排查网络连接泄漏”的最佳素材。