从一次 curl 抓包,彻底搞懂 HTTP 请求的完整生命周期
前言:很多人学网络协议都是"背概念",但如果你亲手抓一次包,把 DNS、TCP 三次握手、HTTP 请求/响应、TCP 四次挥手全部看一遍,那些概念就真的活了。本文通过一次真实的
curl http://www.baidu.com抓包,逐包拆解整个网络通信过程。
一、抓包环境
操作系统:macOS
抓包工具:Wireshark
命令:curl http://www.baidu.com
客户端 IPv6:2409:8a00:3133:1:3486:518f:3418:d42b
百度服务端 IPv6:2409:8c00:6c21:11eb:0:ff:b0bf:59ca
提示:因为系统优先使用 IPv6,所以 curl 通过 IPv6 和百度通信。如果你只想看 IPv4,可以用
curl -4 http://www.baidu.com。抓包时在 Wireshark 显示过滤器中输入
ip.addr == 2409:8c00:6c21:11eb:0:ff:b0bf:59ca or dns,可以过滤掉无关流量。
二、第一步:DNS 查询 —— "百度在哪?"
在发 HTTP 请求之前,浏览器(curl)需要先知道 www.baidu.com 对应的 IP 地址。这一步由 DNS 协议完成。
2.1 发出 DNS 查询
curl 同时发出了两个 DNS 查询:
No.87 22:33:11.941 客户端 → 路由器
DNS Standard query A www.baidu.com
(查询 IPv4 地址)
No.88 22:33:11.941 客户端 → 路由器
DNS Standard query AAAA www.baidu.com
(查询 IPv6 地址)
- A 记录:查询域名对应的 IPv4 地址
- AAAA 记录:查询域名对应的 IPv6 地址(因为名称里有四个 A,代表比 A 记录更长的地址)
两个查询在同一毫秒发出,这就是所谓的 Happy Eyeballs(快乐眼球) 算法 —— 同时查询 IPv4 和 IPv6,谁先回来就用谁。
2.2 收到 DNS 响应
No.92 22:33:11.964 路由器 → 客户端
DNS Standard query response A www.baidu.com CNAME www.a.shifen.com
A 39.156.70.46
A 39.156.70.239
No.93 22:33:11.967 路由器 → 客户端
DNS Standard query response AAAA www.baidu.com CNAME www.a.shifen.com
AAAA 2409:8c00:6c21:11eb:0:ff:b0bf:59ca
AAAA 2409:8c00:6c21:118b:0:ff:b0e8:f003
这里有一个细节值得注意:
www.baidu.com并没有直接返回 IP,而是通过 CNAME 指向了www.a.shifen.com(百度的 CDN 域名)- 最终返回了 两个 IPv4 + 两个 IPv6 地址,这是为了负载均衡和高可用
⏱️ DNS 查询耗时:23ms(从 11.941 到 11.964)
DNS 小结
客户端 路由器(DNS)
│ │
│── A 记录查询 ─────────────────→│
│── AAAA 记录查询 ──────────────→│
│ │
│←── 返回 IPv4 地址 ────────────│
│←── 返回 IPv6 地址 ────────────│
│ │
│ ✅ 拿到百度的 IP 地址了! │
三、第二步:TCP 三次握手 —— "建立连接"
拿到 IP 后,curl 需要和百度服务器建立一个可靠的 TCP 连接。这个过程就是经典的 三次握手。
No.94 22:33:11.968 客户端 → 百度:80
[SYN, ECE, CWR] Seq=0 Win=65535 Len=0
MSS=1440 WS=64 SACK_PERM
No.95 22:33:11.978 百度:80 → 客户端
[SYN, ACK, ECE, CWR] Seq=0 Ack=1 Win=8192 Len=0
MSS=1380 WS=32 SACK_PERM
No.96 22:33:11.978 客户端 → 百度:80
[ACK] Seq=1 Ack=1 Win=262144 Len=0
逐包解读:
第一次握手 [SYN]:
SYN= Synchronize,"我要和你建立连接"Seq=0:序列号从 0 开始MSS=1440:我这边能接收的最大分段大小是 1440 字节(IPv6 比 IPv4 的 1460 小一些)WS=64:Window Scale = 64,窗口缩放因子(用于 TCP 窗口扩大)SACK_PERM:允许选择性确认
第二次握手 [SYN, ACK]:
SYN:我也想和你建立连接ACK=1:我确认收到了你的 SYN(Ack = 你的 Seq + 1)MSS=1380:百度那边的最大分段是 1380 字节WS=32:百度的窗口缩放因子是 32
第三次握手 [ACK]:
ACK=1:确认收到百度的 SYNWin=262144:我现在的接收窗口是 262144 字节(比最初的 65535 大了,因为 WS 协商后窗口扩大了)
⏱️ 三次握手耗时:1.6ms(11.968 → 11.978)
💡 你可能注意到标志位里有
ECE, CWR,这是 ECN(显式拥塞通知),表示双方都支持在路由器拥塞时提前降速,而不是等到丢包才处理。
三次握手小结
客户端 百度服务器
│ │
│──── SYN (Seq=0) ────────────→ │ ① 你好,我要连接你
│ │
│←─── SYN+ACK (Ack=1) ─────────│ ② 好的,我也准备好了
│ │
│──── ACK (Ack=1) ────────────→ │ ③ 确认,连接建立!
│ │
│ ✅ TCP 连接建立成功 │
四、第三步:HTTP 请求 —— "给我首页"
连接建立后,curl 发出 HTTP 请求:
No.97 22:33:11.978 客户端 → 百度:80
GET / HTTP/1.1
Host: www.baidu.com
User-Agent: curl/8.7.1
Accept: */*
展开这个包,你会看到完整的 HTTP 请求头:
GET / HTTP/1.1\r\n
Host: www.baidu.com\r\n
User-Agent: curl/8.7.1\r\n
Accept: */*\r\n
\r\n
这就是 HTTP 请求的全部内容 —— 一个 GET 方法,请求根路径 /,使用 HTTP/1.1 协议。非常简洁。
💡 注意最后一个
\r\n\r\n(空行),这是 HTTP 协议规定的请求头结束标志。
五、第四步:HTTP 响应 —— 百度返回首页
百度收到请求后,开始返回 HTML 内容。因为百度首页约 617KB,TCP 需要把它拆成很多小包来传输。
5.1 数据分片传输
No.101 22:33:11.996 百度 → 客户端
[ACK] Seq=1 Ack=77 Len=1360
[TCP PDU reassembled in 609]
No.103 22:33:11.998 百度 → 客户端
[ACK] Seq=1361 Ack=77 Len=1360
[TCP PDU reassembled in 609]
No.105 22:33:11.999 百度 → 客户端
[ACK] Seq=2721 Ack=77 Len=1360
[TCP PDU reassembled in 609]
解读:
- 每个包携带 1360 字节数据(受 IPv6 MSS=1380 限制)
Seq递增:1 → 1361 → 2721(每次加 1360)Ack=77始终不变:表示百度已经收到了客户端的 77 字节(HTTP 请求的大小)TCP PDU reassembled in 609:这是 Wireshark 的关键提示!意思是"这个包里的数据只是第 609 号完整数据的一部分"
5.2 为什么有这么多包?
从 No.101 到 No.608,百度连续发送了约 460 个 TCP 包来传输 617KB 的 HTML。这是正常的 —— TCP 就像一个快递员,货物太大就分成很多小包裹来送。
5.3 最终的 HTTP 响应
No.609 22:33:12.083 百度 → 客户端
HTTP/1.1 200 OK (text/html)
在 Wireshark 中右键这个包 → Follow → TCP Stream,就能看到完整的 HTTP 响应:
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: max-age=86400
Connection: keep-alive
Content-Length: 631716
Content-Type: text/html
Date: Mon, 06 Apr 2026 14:33:11 GMT
Etag: "..."
Last-Modified: Mon, 23 Jun 2025 02:52:02 GMT
Server: BWS/1.1
<!DOCTYPE html>
<html>...(百度首页 HTML)
关键字段解读:
| 响应头 | 含义 |
|---|---|
HTTP/1.1 200 OK | 请求成功 |
Content-Type: text/html | 返回的是 HTML 页面 |
Content-Length: 631716 | 响应体大小约 617KB |
Server: BWS/1.1 | 百度自研 Web 服务器 |
Cache-Control: max-age=86400 | 告诉客户端可以缓存 86400 秒(1天) |
Connection: keep-alive | TCP 连接保持打开(可以复用) |
⏱️ 整个数据传输耗时:87ms(11.996 → 12.083)
六、第五步:TCP 四次挥手 —— "连接关闭"
HTTP 响应传完后,虽然 Connection: keep-alive 表示连接可以复用,但 curl 默认请求完就关闭连接。
No.612 22:33:12.084 客户端 → 百度:80
[FIN, ACK] Seq=77 Ack=631794 Win=1462784 Len=0
No.613 22:33:12.093 百度:80 → 客户端
[ACK] Seq=631794 Ack=78 Win=78848 Len=0
No.614 22:33:12.096 百度:80 → 客户端
[FIN, ACK] Seq=631794 Ack=78 Win=78848 Len=0
No.615 22:33:12.096 客户端 → 百度:80
[ACK] Seq=78 Ack=631795 Win=1462784 Len=0
逐包解读:
| 包 | 方向 | 含义 |
|---|---|---|
| No.612 | 客户端 → 百度 | FIN:我说完了,准备关闭(Seq=77,和之前 HTTP 请求衔接) |
| No.613 | 百度 → 客户端 | ACK:知道了,我先确认(此时百度可能还有数据要发) |
| No.614 | 百度 → 客户端 | FIN:我也说完了,可以关闭了 |
| No.615 | 客户端 → 百度 | ACK:确认,连接正式关闭 |
⏱️ 四次挥手耗时:12ms(12.084 → 12.096)
💡 为什么需要四次而不是三次?因为 TCP 是全双工的(双方可以同时发送数据),所以每一方都需要独立地通知对方"我说完了"。
七、全景时间线
把所有关键节点串在一起:
时间戳 事件 耗时
─────────────────────────────────────────────────────────
22:33:11.941 DNS 查询发出
22:33:11.964 DNS 响应收到 23ms
22:33:11.968 TCP SYN(第一次握手)
22:33:11.978 TCP SYN+ACK(第二次握手) 1.6ms
22:33:11.978 TCP ACK(第三次握手) 0ms
22:33:11.978 HTTP GET 请求发出 0ms
22:33:11.996 百度开始传输 HTML
22:33:12.083 HTML 传输完成(HTTP 200 OK) 87ms
22:33:12.084 TCP FIN(开始关闭连接)
22:33:12.096 TCP 连接完全关闭 12ms
─────────────────────────────────────────────────────────
总耗时 ~155ms
从 DNS 查询到连接关闭,一次完整的 HTTP 请求只用了 155 毫秒。
八、知识总结
一次 HTTP 请求涉及的协议栈
应用层: DNS(域名解析)→ HTTP(请求/响应)
传输层: TCP(三次握手、可靠传输、四次挥手)
网络层: IPv6 / IPv4(路由寻址)
网络接口层: 以太网 / Wi-Fi(物理传输)
你应该记住的关键点
- DNS 先行:任何 HTTP 请求之前,必须先通过 DNS 将域名解析为 IP
- TCP 保证可靠:三次握手建立连接,数据传输有序列号和确认,四次挥手关闭连接
- HTTP 是明文:
http://下的请求和响应都是明文传输,抓包可以直接看到内容。https://则需要 TLS 加密 - 大数据分片:HTTP 响应太大时,TCP 会拆成多个包传输,Wireshark 会自动标记
TCP PDU reassembled - curl 的行为:curl 用完就关(发 FIN),即使服务器说
keep-alive。浏览器则会复用连接以提高性能
动手建议
如果你想继续深入,可以尝试以下实验:
# 对比 HTTP 和 HTTPS 的区别
curl http://www.baidu.com # 明文,能看到请求和响应内容
curl https://www.baidu.com # 加密,只能看到 TLS 握手和加密数据
# 对比 GET 和 POST
curl -X POST -d "key=value" http://httpbin.org/post
# 看重定向过程
curl http://baidu.com # 会 302 跳转到 www.baidu.com
# 模拟慢速网络(配合 Wireshark 观察 TCP 重传)
sudo ipconfig set en0 mtu 576 # 降低 MTU,观察分片变化
抓包不是目的,理解协议才是。 当你真正理解了 DNS → TCP → HTTP 这个链路,很多网络问题(DNS 劫持、连接超时、TLS 握手失败、页面加载慢)都能从抓包中找到线索。建议打开 Wireshark 亲手操作一遍,对照着本文逐包查看,效果最好。