ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

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

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(看报文)。

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);注释掉,重新编译运行。

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

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

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

👉现象:你会看到服务器的9999端口对应的连接状态变成了CLOSE_WAIT。
这就是你面试时回答“如何排查网络连接泄漏”的最佳素材。

返回列表