Linux 学习笔记(四十七):TCP 状态机与 tcpdump 抓包排雷实战

📌 核心结论(TL;DR):

  1. 三次握手:防止历史失效连接请求到达服务器,确认双方收发能力。

  2. 四次挥手:TCP 全双工,被动方先回 ACK,等数据发完再发 FIN。

  3. CLOSE_WAIT 坑点 :如果服务器出现大量 CLOSE_WAIT,说明代码里忘记调用 close(),导致连接未彻底释放,文件描述符泄漏。

  4. 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。

  1. 保证最后一次握手成功 :如果最后一个 ACK 丢了,对方重发 FIN,此时我还在等待,可以重新补发 ACK。

  2. 防止旧数据包干扰新连接:让旧连接中迷路的报文在网络中彻底消亡,保证下一个相同端口的新连接不会收到脏数据。

自我实战操作

咱们用两个极其常用的神器: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); 注释掉,重新编译运行。

  1. 启动服务器,连上客户端。

  2. 强杀客户端(Ctrl+C)。

  3. 新开终端,敲:netstat -napt | grep 9999

👉 现象 :你会看到服务器的 9999 端口对应的连接状态变成了 CLOSE_WAIT。

这就是你面试时回答"如何排查网络连接泄漏"的最佳素材。

相关推荐
车载中间件通信2 小时前
[TC8测试]TCP_FLAGS_INVALID_13_Flag SYN,ACK
网络·网络协议·tcp/ip
爱和冰阔落2 小时前
【Linux网络】UDP 聊天室怎么实现?从在线用户管理、消息广播到双线程收发与线程池
linux·运维·网络·udp
蒸蒸yyyyzwd2 小时前
从面经中学到的 5 个知识
笔记
车载中间件通信2 小时前
[TC8测试]TCP_FLAGS_PROCESSING_06
数据库·网络协议·tcp/ip·中间件·tc8
雪落漂泊2 小时前
Linux进程(三)概念:进程优先级与进程切换篇
linux·运维·服务器
拂拉氏2 小时前
【知识讲解】 Linux信号与可重入函数的认识
linux·运维·服务器
cynicism??2 小时前
Linux 与 ZXISOS 驱动开发架构对比
linux·驱动开发·架构
pikapika~~~3 小时前
计算机网络八股
服务器·网络·计算机网络
東隅已逝,桑榆非晚3 小时前
红黑树:从概念到实现
数据结构·c++·笔记·学习