第 10 章 · 一次请求的完整旅程

写这一章的时候,我的 Wireshark 和终端一直开着,因为本章的每个数字都来自一次真实的抓包:在我自己的机器上访问 https://example.com/,从敲下回车到页面到手。前面九章是分镜头,这一章把镜头接回去:DNS、TCP、TLS、HTTP、路由、ARP,所有的东西在一次请求里各就各位。读法建议:对照第 1 章那棵协议树,每一层都能在这里找到自己的戏份。

10.1 先算一笔时间账

用 curl 把各阶段耗时拆出来(-w 支持直接输出这些计时器):

$ curl -s -o /dev/null -w 'DNS: %{time_namelookup}s\nTCP: %{time_connect}s\nTLS: %{time_appconnect}s\n首字节: %{time_starttransfer}s\n总计: %{time_total}s\n' https://example.com/
DNS: 0.005780s
TCP: 0.219637s
TLS: 0.456015s
首字节: 0.696126s
总计: 0.696170s

这是计时器是累计值,相减得到各阶段真实开销:DNS 查询 6 ms(本机缓存命中,几乎白送),TCP 握手 214 ms,TLS 握手 236 ms,服务器吐出首字节 240 ms。

重点看中间两个数:第 5 章我 ping 同一批地址,RTT 是 186 ms 上下,而 TCP 握手恰好需要一个 RTT(发出 SYN,等回 SYN+ACK),214 减去一点开销,对上了。TLS 1.3 只需要 1-RTT,236 ms 也在一个 RTT 的量级。一个 700 ms 的「秒开」请求里,物理距离吃掉了六成,这就是为什么 CDN 要把机房搬到你旁边,也是第 7 章那个「光速的三分之二」的现实版本。

10.2 完整时间线

从回车到页面,全程九步(括号是详解所在章节):

1. DNS 解析        域名 → IP,缓存/递归/迭代              (第 2 章)
2. TCP 三次握手    随机端口(如 33906)→ 443              (第 3、4、9 章)
3. TLS 握手        验证书,协商会话密钥                    (第 8 章)
4. 发送请求        GET / HTTP/2                           (第 2 章)
5. 服务器处理      路由、查库、渲染模板……                  (框架的事)
6. 返回响应        200 + HTML,分段编号滑出               (第 4 章)
7. 沿途转发        每一跳:路由表 → ARP → 帧出网卡         (第 5、6 章)
8. 浏览器渲染      解析 HTML/CSS/JS,可能再触发 N 个请求    (浏览器的事)
9. 连接收尾        HTTP/2 复用连接;空闲后四次挥手         (第 4 章)

第 4 到 7 步值得强调:每往下一层,数据就被再包一层(TCP 头、IP 头、以太网帧头尾,第 1 章的套娃),每过一个路由器,MAC 头撕掉重写而 IP 头不动(第 6 章),每过一段光纤就变成光脉冲(第 7 章)。服务器回程时同理反着来一遍。

10.3 在报文里,它们长这样

curl -v 把整场戏的台词都打印出来(节选,本机实跑):

* Host example.com:443 was resolved.
* IPv6: 2606:4700:10::6814:179a, 2606:4700:10::ac42:93f3
* IPv4: 172.66.147.243, 104.20.23.154
*   Trying [2606:4700:10::6814:179a]:443...
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519MLKEM768
* Server certificate:
*   subject: CN=example.com
*   issuer: C=US; O=SSL Corporation; CN=Cloudflare TLS Issuing ECC CA 3
* Established connection to example.com (2606:4700:10::6814:179a port 443)
* using HTTP/2
> GET / HTTP/2
> Host: example.com
> User-Agent: curl/8.23.0
> Accept: */*

逐段对应时间线:第一块是 DNS 结果(注意域名解析出一批 IPv6 和 IPv4,curl 挨个试,Happy Eyeballs);握手三个 TCP 包 curl 不屑于打印(太 routine),直接跳到 TLS 的 ClientHello;证书在 Certificate (11) 那一步到达并被本机 OpenSSL 验证;using HTTP/2 是 TLS 握手里 ALPN 扩展顺带谈成的——连用什么 HTTP 版本都塞进了 TLS 握手;最后 > 开头的就是发出去的请求。

想看更底层的,抓包就是了:

sudo tcpdump -i any port 443 -c 10      # 只看 443,收十个包收手

Wireshark 里同样的事用过滤器:dns 看 10.2 的第 1 步,tcp.port == 443 看握手,icmp 看第 5 章的体检报文。第 4 章画的时序图和真实报文一一对应,看过一次就再也忘不掉。

10.4 浏览器的视角:Timing 面板

DevTools 的 Network 面板里点开任一请求,Timing 标签把同样的账单换个名字再给你一遍:DNS Lookup、Initial connection(TCP)、SSL(TLS)、Waiting for server response(即 TTFB)、Content Download(正文下载)。两边对照过一次,你就拥有了两种武器:curl 快、适合脚本和命令行;DevTools 直观、能看每个资源的瀑布图。页面慢的时候,先看时间大头落在哪一段,再决定去查 DNS、网络还是服务器。

10.5 排障顺序:把九章装进一张清单

最后给我的排查习惯,按层从下往上,每步都有第 N 章背书:

症状线索先查什么章节武器
ping 域名不通,ping IP 通DNSgetent hosts,第 2 章
ping 同网段通,外网全断网关/默认路由ip route,第 5 章
curl 卡在 Trying <IP>:443...路由或防火墙丢包第 5 章,tracepath 看断在哪一跳
curl 立刻 Connection refused端口上没服务第 3 章 RST,ss -tlnp 看监听
TLS 告警证书链/系统时间第 8 章,读错误码
小包通、大包死MTUping -s 1472,第 5 章
请求发出去后周期性卡顿Nagle × 延迟 ACK第 4 章,40 ms 空档

这张表当然覆盖不了全部故障,但「先分层定位、再深入单层」是永远不会错的框架,毕竟网络本来就是这么造出来的。

练习

  1. 动手:对三个不同的网站各跑一遍 10.1 节的计时命令,把 DNS、TCP、TLS、首字节四段列成表,解释为什么有的站 TCP 段只有 30 ms,有的要 200 ms。
  2. 动手:DevTools 里刷新一个页面,打开 Timing 标签,和 10.1 的账单逐项对号。
  3. 思考:HTTP/2 连接复用之后,10.1 账单里的哪些项在同一次页面加载里只会出现一次?这对「首屏时间」意味着什么?
  4. 排障:朋友说「网页打不开,ping 是通的」。按 10.5 的顺序,写出你的前三步操作和每一步分别排除什么。