📘 第一章 了解Web及网络基础
📄 一句话概括

DNS 把名字翻成地址。它自己不产生任何内容,但整条链路的第一个往返总是它——DNS 慢,后面全慢。

📝 本节和前面几节的关系

上一节讲 TCP 怎么保证不丢。可要建连接,得先知道对方的 IP。这一节补上「问路」这一段。

一、为什么不直接用 IP

IP 会变:换机房、扩容、走 CDN,地址就不一样。名字不会变。 所以名字是稳定的引用,地址只是当下的位置。 更实际的原因是一个名字要对应多个地址:一个站点可能有多台服务器、多个地域节点, 甚至同时有 IPv4 和 IPv6。用名字才能在它们之间调度。

二、查询报文长什么样

Demo 手工拼了一个 29 字节的查询报文:12 字节固定头 + 13 字节域名编码 + 4 字节类型与类。 固定头里最关键的是 2 字节事务 ID(0x1234)——回来的响应必须带同一个 ID,否则不认。 这是 DNS 在 UDP 上做「请求响应配对」的唯一手段。 域名编码是「每段前面一个长度字节、末尾一个 0」: example.com 变成 07 e x a m p l e 03 c o m 00,13 字节。 这个格式让名字可以任意长,也让解析器必须逐段读。

三、递归与缓存:为什么平时感觉不到它

客户端一般只问本地递归服务器(运营商或 8.8.8.8),由它去问根、问顶级、问权威,一路问到答案。 这叫递归查询。 答案会被缓存,缓存时长由记录的 TTL 决定。 所以「改了解析为什么不生效」——通常是本地或中间缓存还没过期。 实测里 解析 www.example.com 只用了不到 4 毫秒,说明命中了本机或近端缓存; 冷启动时要几十到几百毫秒。这个差异比 HTTP 本身大得多,所以线上优化第一步常常是 DNS 预解析。

四、动手 Demo:解析一个名字,并手工拼一份 DNS 查询报文

先用标准库解析一次并计时,再手工拼一个 29 字节的查询报文,看它到底长什么样——固定头 12 字节, 域名编码 13 字节,类型与类 4 字节。

q = bytearray()
q += b'\x12\x34'      # 事务 ID
q += b'\x01\x00'      # 标志:标准查询,要递归
q += b'\x00\x01'      # 问题数 1
q += b'\x00\x00' * 3  # 回答/授权/附加数 各 0
for label in u'example.com'.split(u'.'):
    q += bytes([len(label)]) + label.encode()
q += b'\x00'            # 名字结束
q += b'\x00\x01'      # 类型 A
q += b'\x00\x01'      # 类 IN

实跑输出:

--------------------------------------------------------------------
DNS:把名字翻成地址的那一次往返
--------------------------------------------------------------------
  解析 www.example.com       3.91 毫秒
  拿到                       4 条候选地址
  首条                       2606:4700:10::ac42:93f3

  手工 DNS 查询报文          29 字节
  文本 \x124\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\x00\x00\x01\x00\x01
  十六 12 34 01 00 00 01 00 00 00 00 00 00 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 01 00 01
  事务 ID                    0x1234(同一个 ID 回来才算自己的回包)
  域名编码                   13 字节(每段前面一个长度字节,末尾一个 0)
  实测结论                   系统解析器把整个递归过程藏起来了,这里只看得见一次函数调用
  29 个字节里,真正想说的只有 13 个字节的域名;其余都是定长字段。
  这一节的 Demo 只到「报文长什么样」;要把响应解析回地址,得自己发 UDP 并逐段解,那是 1.5 节之外的事。

文本那行里 \x124 看着别扭,是因为 0x12 是不可打印字符,后面紧跟的 4 是 0x34 的 ASCII。 十六进制那行才是真相:12 34 01 00 00 01 …。 29 个字节里,真正想说的只有 13 个字节的域名——其余都是定长字段。 DNS 的固定开销占比在这个尺度上非常高。 顺带看到 拿到 4 条候选地址、首条 2606:4700:…:example.com 现在同时有 IPv6 和 IPv4, 而且不止一个地址。

30 秒实验

  1. 把事务 ID 改成 0x0000,想想服务器会不会拒答(不会,但客户端可能误配)。
  2. 把类型从 A(00 01)改成 AAAA(00 1c),猜猜会问什么。
  3. 连续解析同一个域名十次,看第一次和后面几次耗时差多少。

常见误区

  1. 「DNS 是 HTTP 的一部分」——它比 HTTP 独立得多,邮件、SSH、游戏全用它。
  2. 「改了 DNS 立刻生效」——TTL 决定缓存多久,没到期谁都不去问。
  3. 「DNS 一定走 UDP」——超过 512 字节或做区域传送时用 TCP;现在还有 DNS over HTTPS/TLS。
自测三问(点开对答案) 1. DNS 查询报文里靠什么把请求和响应对上? —— 2 字节事务 ID。请求里带什么,响应里必须原样带回。 2. 域名为什么用「长度前缀」编码? —— 这样名字可以任意长、任意层数,解析器逐段读就知道边界在哪。 3. 为什么改了域名解析很久不生效? —— 各级缓存按 TTL 保存旧记录,TTL 没到期就不会重新查询。

IP、TCP、DNS 三个配角齐了。下一节把它们摆到一张图上,看谁先谁后。

相关笔记