斯坦福 CS144 计算机网络播客:互联网核协议NAT, HTTP, BitTorrent/P2P, DNS, DHCP

本文将依次探讨五个核心主题:

网络地址转换 (NAT) :它是如何让亿万设备共享有限的 IPv4 地址,又给 P2P 应用带来了哪些挑战?超文本传输协议 (HTTP) :作为 Web 的语言,它经历了怎样的演变?BitTorrent :P2P 文件共享背后蕴含着怎样的智慧与博弈论?域名系统 (DNS) :这个庞大的分布式“电话簿”是如何工作的?动态主机配置协议 (DHCP) :我们是如何实现“即插即用”上网的?

希望这篇文章能带你一窥网络世界的精妙设计。

网络地址转换 (NAT):内网“保护伞”与 P2P 的“拦路虎”

你是否想过,为什么家里有多台设备(手机、电脑、智能电视),却通常只有一个公共 IP 地址?这背后的功臣就是 网络地址与端口转换 (Network Address Port Translation, NAPT) ,通常被简称为 NAT。它的核心使命是解决 IPv4 地址枯竭的问题,让多个位于内网的设备能共享同一个公共 IP 地址访问互联网。

NAPT 的运作机制

想象一下,你的路由器就是一个公司的“前台”。所有内部员工(内网设备)要寄信(发起网络连接)时,都把信交给前台,由前台统一用公司的地址(公共 IP)寄出。前台会记录下是谁(内网 IP 和端口)、在什么时间、要寄给谁(目标 IP 和端口)。当收到回信时,前台再根据记录把信准确地分发给对应的员工。

这个“前台”就是 NAPT 设备。当内网一台 IP 地址为 10.0.0.7 的电脑向 Stack Overflow (151.101.1.69) 发起 TCP 连接时,数据包的源地址最初是 10.0.0.7:8000 (假设源端口是 8000)。

数据包离开内网 : 当数据包到达 NAPT 设备(路由器)时,设备会重写数据包的源信息。建立映射规则 : NAPT 会将源 IP 地址替换为自己的公共 IP 地址(例如 120.10.20.30),并将源端口替换为一个临时的、未被占用的端口(例如 54321)。同时,它会在内部的映射表中创建一条规则。

这张映射表看起来像这样:

复制
+------------------------------------+-------------------------------------+ | Internal Endpoint | External Endpoint | +------------------------------------+-------------------------------------+ | (src) 10.0.0.7 : 8000 | (src) 120.10.20.30 : 54321 | | (dst) 151.101.1.69 : 443 (https) | (dst) 151.101.1.69 : 443 (https) | +------------------------------------+-------------------------------------+1.2.3.4.5.6.
外部服务器响应 : Stack Overflow 的服务器看到的是一个来自 120.10.20.30:54321 的请求。它的响应包将以此为目标地址。数据包返回内网 : 当响应包回到 NAPT 设备时,设备会查询映射表,找到 120.10.20.30:54321 对应的内部地址是 10.0.0.7:8000。于是,它再次重写数据包的目标地址和端口,并将其转发给内网的电脑。

这个映射规则是动态创建的,当 TCP 连接关闭或长时间不活跃后,它会被自动 垃圾回收 (garbage collected) 。

NAPT 的双重角色:节约与防护

NAPT 的主要优势有两个:

IP 地址共享 :这是它的初衷,极大地延缓了 IPv4 地址的耗尽。基础防火墙 :由于 NAPT 只会转发那些“有记录可查”(即由内部设备主动发起)的返回数据包,对于外部网络主动发起的连接请求,NAPT 会因为找不到匹配的映射规则而直接丢弃。这无形中为内网设备提供了一层基础的安全保护。NAPT 的四种类型

根据 RFC 3489 的定义,NAPT 根据其映射和过滤行为的严格程度可以分为四种类型:

全锥型 (Full Cone) :最宽松的类型。一旦一个内部地址 (IP_in:Port_in) 映射到了一个外部地址 (IP_out:Port_out),任何外部主机都可以通过向 IP_out:Port_out 发送数据来访问 IP_in:Port_in。受限锥型 (Restricted Cone) :比全锥型严格一些。它要求只有之前该内部主机发送过数据的那个外部 IP 地址,才能向映射的外部端口发送数据。端口受限锥型 (Port Restricted Cone) :最严格的“锥型”。它不仅要求源 IP 地址匹配,还要求源端口也必须匹配。对称型 (Symmetric) :这是最严格且最麻烦的一种。对于同一个内部 IP 和端口,当它请求不同的外部目标时,NAPT 会为它分配不同的外部端口映射。例如,访问服务器 A 可能映射为端口 50001,访问服务器 B 则映射为端口 50002。这对 P2P 应用是毁灭性的,因为两个客户端很难预测对方的下一个映射端口。一个例子深入理解 NAPT 的四种类型

为了更清晰地理解这四种 NAPT 类型,我们来设定一个场景并贯穿始终:

你的电脑 (Alice) :内网地址为 192.168.1.10:12345。你的路由器 (NAT) :公网 IP 地址为 120.10.20.30。游戏服务器 (Bob) :IP 地址为 203.0.113.10:80。语音服务器 (Charlie) :IP 地址为 198.51.100.20:90。路人玩家 (Dave) :IP 地址为 200.20.30.40,他想直接连接你。

现在,你的电脑 (Alice) 首先向游戏服务器 (Bob) 发送了一个数据包,启动了游戏。你的路由器 (NAT) 为此创建了一条映射规则。让我们看看不同类型的 NAT 在此之后会如何表现。

全锥型 (Full Cone)

这是最“开放”的 NAT。当 Alice 连接 Bob 时,NAT 会在公网 120.10.20.30 上打开一个端口(比如 54321)并映射到 Alice 的 192.168.1.10:12345。

映射规则 :120.10.20.30:54321 <-> 192.168.1.10:12345行为特点 :这个 54321 端口现在对整个互联网开放。 任何人 (无论是 Bob、Charlie 还是路人 Dave)只要向 120.10.20.30:54321 发送数据,NAT 都会将其转发给 Alice。生活比喻 :你点了一份外卖(连接 Bob),外卖员知道了你的公寓地址和房间号。在全锥型 NAT 下,你家的门(54321 端口)就此敞开,不仅外卖员能进来,你的朋友、邻居、甚至任何知道你地址的路人都能直接推门进来。这对 P2P 应用(如语音聊天)非常友好。受限锥型 (Restricted Cone)

这种 NAT 增加了一层“IP 地址”限制。

映射规则 :同上,120.10.20.30:54321 <-> 192.168.1.10:12345。行为特点 :只有 曾经接收过 Alice 数据的 IP 地址 (在这个例子里是 Bob 的 203.0.113.10)才能向 120.10.20.30:54321 发送数据并被转发。来自 Charlie 或 Dave 的数据包会被 NAT 丢弃。生活比喻 :你点了一份外卖。现在,你家的楼栋有了门禁,只有外卖员(Bob)刷他的门禁卡(IP 地址 203.0.113.10)才能进入楼栋给你送东西。你的朋友(Charlie)和路人(Dave)因为没有门禁卡,所以被拦在楼下。端口受限锥型 (Port Restricted Cone)

这是最严格的“锥型” NAT,在 IP 地址的基础上,又增加了“端口”限制。

映射规则 :同上。行为特点 :只有 严格匹配 Alice 曾经通信过的 IP 和端口 (即 Bob 的 203.0.113.10:80)的数据包,才能被 NAT 转发。如果 Bob 换了一个端口(比如从 203.0.113.10:8888)给你发消息,也会被拒绝。生活比喻 :门禁系统升级了。它不仅认识外卖员(Bob 的 IP),还只认他用来联系你的那部特定手机(端口 80)。如果外卖员换个手机给你打电话,门禁系统就不认了。对称型 (Symmetric)

这是对 P2P 应用最不友好的类型,因为它的映射是“动态且有针对性的”。

行为特点 :映射关系与 目标地址 有关。

当 Alice 连接游戏服务器 Bob (203.0.113.10:80) 时,NAT 创建映射:120.10.20.30:54321 <-> 192.168.1.10:12345。只有 Bob 能通过这个映射联系 Alice。

如果此时,Alice 又从 同一个 内网端口 192.168.1.10:12345 去连接语音服务器 Charlie (198.51.100.20:90),对称型 NAT 会 创建一个全新的、不同的 公网端口映射,例如:120.10.20.30:61111 <-> 192.168.1.10:12345。只有 Charlie 能通过这个新映射联系 Alice。

生活比喻 :你是一个特工,对外有多个一次性电话。联系 Bob 时,你用电话 A (54321);联系 Charlie 时,你用电话 B (61111)。Bob 无法通过电话 B 找到你,反之亦然。如果你想让 Bob 和 Charlie 直接通话,他们互相告知的电话号码都是错的,因为那个号码只对你有效。这就是为什么对称型 NAT 会严重阻碍 P2P 通信。应对 NAPT 的挑战

NAPT 的防火墙特性也给需要 传入连接 (incoming connections) 的应用(如 P2P 下载、在线游戏、视频会议)带来了巨大挑战。为了穿透这层“保护”,开发者们想出了多种策略:

中继 (Relay) :通过一个公共服务器(如 TURN 服务器)来转发所有数据。这是最可靠但成本最高的方式。连接反转 (Connection Reversal) :两个客户端 A 和 B 都连接到一个公共的 会合服务器 (Rendezvous Server) 。当 A 想连接 B 时,它通过服务器告诉 B。然后,B 主动向 A 发起连接。**端口转发 (Port Forwarding)**:在路由器上手动配置一条静态规则,将某个外部端口的所有流量都转发到指定的内部 IP 和端口。NAT 穿透 (NAT Hole Punching) :一种非常巧妙的技术。客户端 A 和 B 首先都通过一个公共服务器(如 STUN 服务器 )获知自己经过 NAT 转换后的公网 IP 和端口。然后,它们尝试 同时 (Simultaneous Open) 向对方的公网地址发送数据。这个动作会“欺骗”双方的 NAPT 设备,让它们各自创建一条“允许对方进入”的映射规则,从而打通一条直接的 P2P 通道。

为了让网络应用更好地工作,后续的 RFC 规范(如 RFC 4787)建议 NAPT 设备应实现更友好的行为,例如 端点独立映射 (Endpoint-Independent Mapping) (即不要做对称型 NAT)和支持 发卡弯 (Hair-pinning) (允许内网设备通过路由器的公网 IP 访问另一个内网设备)。

NAT 穿透策略详解

面对 NAT 的阻碍,开发者们发明了多种“穿透”技术,其核心思想都是借助一台在公网上的、地址固定的服务器来协调两个位于 NAT 后的客户端建立连接。

中继 (Relay) - TURN 协议

这是最可靠但也最“笨”的方法,它的代表是 TURN (Traversal Using Relays around NAT) 协议。

工作原理 :当中继服务器介入时,它不试图建立直接连接,而是充当一个数据“中转站”。

客户端 A 和 B 都无法直接通信。

于是,A 和 B 都与公网上的 TURN 服务器建立连接。

A 想发送数据给 B 时,它先把数据包封装一层,目标地址写成 TURN 服务器。

TURN 服务器收到后,拆开封装,看到里面真正的目标是 B,于是将数据转发给 B。

比喻 :你和你的朋友住在两个管理严格、禁止访客的小区。为了交流,你们都把信寄给一个共同的、地址公开的“邮政信箱”(TURN 服务器),再由这个信箱的管理员把信分发给对方。优缺点 :优点是它几乎能兼容所有类型的 NAT(包括对称型),是最后的保底方案。缺点是所有流量都要经过服务器中转,这会增加延迟,并且极大地消耗服务器的带宽和计算资源,成本高昂。NAT 穿透 (Hole Punching) - STUN 协议

这是一种更高效、更主流的技术,其核心在于“打洞”。它通常需要 STUN (Session Traversal Utilities for NAT) 协议的辅助。

第一步:自我认知 (STUN 的角色)

STUN 服务器非常简单,像一面“镜子”。

客户端 A 向公网的 STUN 服务器发送一个请求。数据包穿过 A 的 NAT 时,源地址被改写为公网地址,例如 A_public_IP:A_public_port。STUN 服务器收到包后,它并不关心包的内容,而是读取包的源地址 A_public_IP:A_public_port,并把这个地址作为响应数据发回给 A。这样,客户端 A 就从“镜子”里看到了自己在外网的样子。

第二步:打洞 (Hole Punching 的过程)

现在,假设 A 和 B 都通过 STUN 服务器知道了自己的公网地址,并通过一个信令服务器(Rendezvous Server)交换了这些信息。

约定时间 :A 和 B 约定好,在某个时刻 同时 向对方的公网地址发送一个 UDP 数据包。A 的动作 :A 从其内网端口向 B 的公网地址 B_public_IP:B_public_port 发送数据包。这个动作会迫使 A 的 NAT 创建一条映射规则,并打开一个“洞”,该规则允许来自 B_public_IP:B_public_port 的数据进入。B 的动作 :与此同时,B 也从其内网端口向 A 的公网地址 A_public_IP:A_public_port 发送数据包。同样,B 的 NAT 也创建了一条规则,打开了一个允许 A_public_IP:A_public_port 进入的“洞”。连接建立 :当 A 的数据包到达 B 的 NAT 时,B 的 NAT 查表发现“哦,我正等着这个地址来的包呢”,于是放行。反之亦然。这样,一条直接的 P2P 通道就奇迹般地建立起来了。比喻 :你和朋友被关在相邻的两个房间,墙壁很薄。你们事先约好,数到三就同时对着墙上同一个位置用力一推。由于你们同时向外用力,墙壁(NAT 规则)就被推开了一个洞,你们可以直接对话了。局限性 :这个方法对锥型 NAT 非常有效,但常常在对称型 NAT 上失败。因为对称型 NAT 会为不同的目标地址创建不同的公网端口,A 在 STUN 服务器上看到的端口,与 A 连接 B 时 NAT 创建的端口很可能不是同一个。

超文本传输协议 (HTTP):从简单文档到现代 Web 的基石

超文本传输协议 (Hypertext Transfer Protocol, HTTP) 无疑是现代互联网的通用语言。它最初被设计用来传输 HTML 文档,如今已发展为承载网页、视频流、API 调用等各种网络应用的基础。

HTTP 的基本模型

HTTP 是一个运行在 TCP 之上的客户端-服务器协议。其交互流程非常直观:

客户端(如浏览器)与服务器建立一个 TCP 连接。客户端向服务器发送一个 请求 (Request) 。服务器处理请求,并返回一个 响应 (Response) 。

请求和响应都采用易于阅读的 ASCII 文本格式。它们都由三部分组成:

起始行 (Start Line) :对于请求,是 方法 URL 版本(如 GET /index.html HTTP/1.1);对于响应,是 版本 状态码 描述(如 HTTP/1.1 200 OK)。头部 (Headers) :零个或多个键值对,提供关于请求/响应的元数据。消息体 (Body) :可选的、实际传输的数据。

一个简单的 HTTP GET 请求如下:

复制
GET /wiki/Main_Page HTTP/1.1 Host: en.wikipedia.org User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Accept: text/html,*/*1.2.3.4.
HTTP 的演进之路

HTTP/1.0 的短板 :早期的 HTTP/1.0 有一个显著的性能问题: 每个请求都需要建立一个新的 TCP 连接 。加载一个包含许多图片和脚本的网页,意味着需要进行数十次 TCP 的“三次握手”,这不仅耗时,也使得 TCP 的拥塞控制窗口难以有效增长,传输效率低下。

HTTP/1.1 的改进:持久连接 :HTTP/1.1 引入了 Connection: Keep-Alive 头部(后来成为默认行为),允许在 同一个 TCP 连接上发送多个请求和接收多个响应 。这极大地减少了连接建立的开销,是 Web 性能的一次飞跃。HTTP/2 的未来:多路复用 :尽管 HTTP/1.1 解决了连接复用的问题,但队头阻塞 (Head-of-Line Blocking) 问题依然存在。HTTP/2(其前身是 Google 的 SPDY 协议)通过引入 多路复用 (Multiplexing) ,允许在一个 TCP 连接上同时发送多个请求和响应,并且响应可以不按请求的顺序返回。它还支持头部压缩,进一步提升了效率。如今,HTTP/2 已经成为主流,而 HTTP/3 正在兴起。HTTP/2:解决队头阻塞的“多车道”高速公路

HTTP/1.1 的持久连接虽然减少了 TCP 握手的次数,但引入了新的问题: 队头阻塞 (Head-of-Line Blocking) 。在一个 TCP 连接上,所有请求是按顺序发送的,响应也必须按顺序回来。如果第一个请求(比如一个复杂的 API 调用)被服务器卡住了,那么后面的请求(比如加载一张小图片)即使服务器已经处理完了,也必须排队等着,导致整个页面加载变慢。

HTTP/2 带来了革命性的解决方案: 多路复用 (Multiplexing) 。

核心概念:流 (Stream) 与帧 (Frame) :HTTP/2 将每个“请求-响应”视为一个独立的、带编号的“ 流 ”。然后,它把每个流的数据分割成更小的二进制“ 帧 ”。这些来自不同流的帧可以在同一个 TCP 连接上混合交错地发送,到达对端后再根据编号重新组装成完整的流。比喻

HTTP/1.1 就像一条 单车道 。前面有一辆慢悠悠的拖拉机(慢请求),后面的法拉利(快请求)也只能跟着堵车。

HTTP/2 则是 多车道高速公路 。拖拉机在慢车道走,法拉利在快车道飞驰,它们共享一条路(一个 TCP 连接),但互不干扰。

除了多路复用,HTTP/2 还带来了两个重要特性:

头部压缩 (HPACK) :HTTP 请求和响应的头部信息有很多是重复的(如 User-Agent, Accept 等)。HPACK 算法可以智能地压缩这些头部,只发送变化的部分,大大减少了传输的数据量。服务器推送 (Server Push) :服务器可以在浏览器请求一个 HTML 文件的同时,就“预见”到浏览器接下来肯定会请求相关的 CSS 和 JS 文件,于是主动将这些资源“推送”过去,减少了请求的往返次数。HTTP/3:彻底告别 TCP,拥抱 QUIC

尽管 HTTP/2 解决了应用层的队头阻塞,但它依然建立在 TCP 之上,而 TCP 本身也存在队头阻塞问题。TCP 要求数据按序到达。如果一个 TCP 数据包在网络中丢失,那么操作系统必须等待这个包被重传并成功接收后,才会将后续的、已经到达的数据包交给上层应用。在这个等待期间,TCP 连接上的所有 HTTP/2 流都会被卡住。

为了根除这个问题,HTTP/3 做出了一个大胆的决定: 放弃 TCP,改用一个建立在 UDP 之上的新协议——QUIC (Quick UDP Internet Connections) 。

QUIC 的核心优势

内置多路复用 :QUIC 自己实现了流的概念。一个 QUIC 连接可以包含多个流,每个流之间的数据是独立传输和确认的。如果一个流的数据包丢失,只会阻塞那一个流,其他流完全不受影响。这彻底解决了传输层的队头阻塞。更快的连接建立 :QUIC 将 TCP 的三次握手和 TLS(用于加密)的握手过程合并了。建立一个安全的 QUIC 连接通常只需要 1 个往返时延 (RTT),甚至对于已经访问过的站点可以实现 0-RTT,大大降低了延迟。更好的拥塞控制和连接迁移 :QUIC 的拥塞控制算法更灵活,并且连接不依赖于 IP 地址和端口四元组,而是依赖于一个唯一的连接 ID。这意味着当你的手机从 Wi-Fi 切换到 4G 网络时,你的 IP 地址变了,但 QUIC 连接可以无缝迁移,下载或视频通话不会中断。

比喻

HTTP/2 over TCP 像是用一架大货机(TCP 连接)运输来自不同客户(HTTP 流)的集装箱。虽然集装箱可以混装,但只要货机本身(TCP)延误或出故障,所有客户的货都得等着。HTTP/3 over QUIC 则像是为每个客户都派了一架专属的敏捷无人机(QUIC 流)。一架无人机迷路了,完全不影响其他无人机准时送达。

BitTorrent:P2P 文件共享的智慧与博弈

与传统的客户端-服务器下载模式不同, BitTorrent 是一种高效分发大文件的 点对点 (Peer-to-Peer, P2P) 协议。它的核心思想是“人人为我,我为人人”,让每个下载者同时也成为上传者,从而将下载压力分散到整个网络中。

核心概念文件分块 (Pieces) :一个大文件会被切分成许多固定大小的“ 块 (pieces) ”(通常为 256KB 或更大)。种子文件 (Torrent File) :这是一个小文件,包含了文件的元数据(如大小、分块信息、每个块的 SHA-1 哈希值)以及如何联系 追踪器 (Tracker) 或其他对等节点的信息。群 (Swarm) :所有正在下载或分享同一个文件的客户端(或称 对等节点 (peers) )组成的集合。追踪器 (Tracker) :一个服务器,负责记录群中有哪些活跃的对等节点。现代 BitTorrent 客户端越来越多地使用 分布式哈希表 (Distributed Hash Table, DHT) 技术,实现了无需追踪器的去中心化网络。关键算法:效率与公平的博弈

BitTorrent 的高效运作离不开几个精妙的算法:

稀有块优先 (Rarest First Policy) :客户端会优先请求那些在它的邻居节点中最稀有的文件块。这个策略有两个好处:

保证块的多样性 :确保所有块都能在网络中均匀分布,避免某些块因只有少数节点拥有而成为下载瓶颈。

延长文件生命周期 :即使最初的发布者下线,只要所有块都至少在网络中存在一份,整个文件就能被完整地恢复出来。

一报还一报 (Tit-for-Tat / Choking Algorithm) :这是 BitTorrent 的激励机制,鼓励分享。每个客户端会周期性地评估哪些对等节点为自己提供了最快的上传速度。它会优先向这些“贡献最大”的节点上传数据(这个过程称为 解除限制 (unchoking) ),同时暂时停止向其他节点上传(称为 限制 (choking) )。乐观的解除限制 (Optimistic Unchoking) :为了发现新的、可能更优质的上传伙伴,客户端会每隔一段时间,随机地为一个被限制的节点“解除限制”。这给了新加入的节点一个获取初始文件块的机会,也让系统有机会摆脱局部最优,找到更好的数据源。

最后,通过校验 Torrent 文件中提供的哈希值,BitTorrent 保证了下载数据的 完整性 ,防止数据在传输过程中被损坏或篡改。

域名系统 (DNS):互联网的“电话簿”

当你在浏览器输入 www.stanford.edu 时,计算机是如何知道要去哪个 IP 地址获取信息的?答案就是 域名系统 (Domain Name System, DNS) ,这个系统扮演着互联网“电话簿”的角色,负责将人类易于记忆的域名翻译成机器能够理解的 IP 地址。

DNS 的设计原则

在 DNS 出现之前,域名和 IP 的映射关系存储在一个名为 hosts.txt 的本地文件中。随着互联网的扩张,这种集中式管理方式很快暴露了可伸缩性、管理和健壮性问题。DNS 的设计正是为了解决这些问题,其核心原则包括:

分布式管理 :不同域名的管理权可以委托给不同的组织。层次化命名空间 :域名结构呈树状,易于扩展和管理。健壮性 :通过冗余和缓存机制,确保系统的可用性。读多写少,宽松一致性 :DNS 查询远多于更新,系统允许数据更新有一定的延迟。域名解析之旅

DNS 的查询过程是一个精妙的协作过程,主要分为两种查询类型:

递归查询 (Recursive Query) :客户端向其本地 DNS 解析器(通常由 ISP 提供)发起请求,并期望解析器返回最终的 IP 地址。迭代查询 (Non-recursive/Iterative Query) :解析器在收到递归查询后,会向其他 DNS 服务器发起一系列的迭代查询。

让我们以查询 www.cs.stanford.edu 为例,看看一次典型的解析过程:

你的电脑向本地 DNS 解析器发起递归查询:“www.cs.stanford.edu 的 IP 是什么?”本地解析器(如果缓存中没有答案)会向 根服务器 (Root Server) 发起迭代查询。根服务器回答:“我不知道,但 .edu 的 顶级域 (Top-Level Domain, TLD) 服务器知道。它们的地址是...”本地解析器转向 .edu TLD 服务器查询。.edu TLD 服务器回答:“我不知道,但 stanford.edu 的 权威名称服务器 (Authoritative Name Server) 知道。它们的地址是...”本地解析器再转向 stanford.edu 的权威名称服务器查询。stanford.edu 的服务器可能最终会找到 www.cs.stanford.edu 的 IP 地址,或者指向 cs.stanford.edu 这个子域的名称服务器,再进行一次查询。最终,本地解析器获得 IP 地址,返回给你的电脑,并将其缓存起来以备后用。资源记录 (RR) 与胶水记录

DNS 中存储的信息单元被称为 资源记录 (Resource Records, RRs) 。常见的记录类型有:

A 记录:将域名映射到 IPv4 地址。AAAA 记录:将域名映射到 IPv6 地址。NS 记录:指定管理某个域的权威名称服务器。MX 记录:指定接收该域邮件的邮件服务器。CNAME 记录:为一个域名创建别名。

这里有一个有趣的概念叫 胶水记录 (Glue Records) 。想象一下,如果 example.com 的 NS 记录是 ns1.example.com。当你向 .com TLD 服务器查询 example.com 的 NS 记录时,它返回 ns1.example.com。但为了找到 ns1.example.com,你又需要查询 example.com 的 DNS... 这就陷入了一个“鸡生蛋,蛋生鸡”的死循环。

为了解决这个问题,.com TLD 服务器在返回 NS 记录 ns1.example.com 的同时,会附带上 ns1.example.com 的 IP 地址。这个附带的 A 记录就是“胶水”,它将查询过程“粘合”起来,使其能够继续进行。

动态主机配置协议 (DHCP):“即插即用”的网络魔法

当你带着笔记本电脑到一个新的咖啡馆,连上 Wi-Fi 后几乎瞬间就能上网,这个“即插即用”的体验背后,是 动态主机配置协议 (Dynamic Host Configuration Protocol, DHCP) 在默默工作。它使得设备无需手动配置就能自动获取上网所需的网络参数。

DHCP 的使命

DHCP 主要解决三大问题:

自动配置 :自动为设备分配 IP 地址、子网掩码、默认网关和 DNS 服务器地址。简化管理 :对于移动设备,当它从一个网络移动到另一个网络时,能自动获取新的配置。高效利用 IP :通过 租约 (Lease) 机制,可以将不再使用的 IP 地址回收,分配给新的设备。DHCP 是一个应用层 (Application Layer) 协议

这可能会让人感到有些困惑,但我们可以从以下几点来理解:

封装结构 :这是最根本的判断依据。协议栈的封装遵循自上而下的原则。DHCP 协议的消息(如 DHCP DISCOVER)是作为 用户数据报协议 (User Datagram Protocol, UDP) 的载荷来传输的。而 UDP 是一个 传输层 (Transport Layer) 协议。在 TCP/IP 模型中,任何运行在传输层之上的协议都被归类为应用层协议。所以,其封装顺序是:DHCP 消息 -> UDP 头部 -> IP 头部 -> 以太网帧头部。工作模式 :DHCP 采用 客户端-服务器 (Client-Server) 的通信模型。网络中存在 DHCP 客户端(你的电脑)和 DHCP 服务器(通常是路由器)。这种 C/S 模型是应用层协议的一个典型特征,与 HTTP、FTP、DNS 等协议类似。功能与位置的分离 :我们需要区分一个协议的“功能”和它在协议栈中的“位置”。DHCP 的 功能 是为网络层进行配置,但它实现这个功能所 依赖的服务 来自传输层(UDP)。可以把它理解成一个“网络管理应用”,这个应用本身运行在应用层,它通过标准的网络通信(UDP/IP)来完成它的管理任务,即为客户端动态地配置 IP 地址等网络参数。

所以,尽管 DHCP 的工作与网络层紧密相关,但根据其在协议栈中的位置和工作方式,它确确实实是一个应用层协议。

DORA 四步握手

DHCP 的核心是一个四步交互过程,常被缩写为 DORA :

Discover (发现)一个刚接入网络的客户端(此时它还没有 IP 地址)需要找到网络中的 DHCP 服务器。它会广播一个 DHCP DISCOVER 消息。这个消息的源 IP 是 0.0.0.0,目标 IP 是 255.255.255.255(本地网络广播地址)。Offer (提供)网络上所有收到发现消息的 DHCP 服务器都会响应一个 DHCP OFFER 消息,其中包含了一个可供分配的 IP 地址和其他网络配置信息。Request (请求)客户端可能会收到多个 OFFER。它会选择其中一个(通常是第一个收到的),然后再次广播一个 DHCP REQUEST 消息,向网络宣告它希望使用这个 IP 地址。之所以要再次广播,是为了通知所有提供了 OFFER 的服务器,它已经做出了选择。其他未被选中的服务器就可以收回它们提供的 IP 地址了。Acknowledge (确认)被选中的 DHCP 服务器会发送一个 DHCP ACK 消息,正式确认将该 IP 地址和配置信息分配给客户端,并设定一个租约期限。至此,客户端配置完成,可以正常上网了。客户端会在租约到期前自动向服务器请求续约。

从输入 URL 到网页呈现的全过程

我们已经分别探讨了 DHCP、DNS、NAT 和 HTTP。现在,让我们通过一个最常见的场景——“在浏览器地址栏输入一个网址后按下回车”——将这些知识点串联起来,看看它们是如何协同工作的。

场景设定 :你刚刚启动你的 Linux 桌面电脑,并通过网线连接到了家里的路由器。此时,你的电脑还没有 IP 地址,浏览器也处于关闭状态。我们的目标是访问 http://www.example.com。

第 1 步:上线准备 - DHCP 动态获取网络配置

在你打开浏览器之前,你的操作系统需要先“上线”。

发现自我 :由于电脑没有 IP 地址,它无法进行正常的 IP 通信。于是,操作系统会构建一个 DHCP DISCOVER 广播包,源 IP 地址为 0.0.0.0(代表“我是谁”),目标 IP 地址为 255.255.255.255(代表“网络上的所有人”)。获得 offer :你家里的路由器收到了这个广播包。作为网络中的 DHCP 服务器,它会从自己的地址池中挑选一个可用的 IP 地址(比如 192.168.1.100),连同子网掩码、网关地址(192.168.1.1,也就是路由器自己)以及 DNS 服务器地址等信息,通过 DHCP OFFER 包回复给你的电脑。确认请求与最终确认 :你的电脑收到 offer 后,会广播一个 DHCP REQUEST 包,表示“我接受这个配置”。路由器最终回复一个 DHCP ACK 包,确认租约。

至此,你的电脑获得了三样宝贵的上网工具:自己的身份(IP 地址)、出网的门(默认网关)和问路用的地图(DNS 服务器)。

第 2 步:寻址问路 - DNS 的层级解析

现在,你打开浏览器,输入 http://www.example.com 并按下回车。浏览器需要知道 www.example.com 的 IP 地址才能建立连接。

本地缓存优先(The Fast Paths) :为了效率,系统会进行多级缓存检查,顺序如下:浏览器缓存 :浏览器会先看自己内部有没有缓存过这个域名的 IP。操作系统缓存 :如果浏览器没有,操作系统会检查自己的 DNS 缓存(在 Linux 中可能由 systemd-resolved 或 nscd 等服务管理)。hosts 文件 :最后,操作系统会查看 /etc/hosts 文件,这是一个静态的、由用户自己配置的域名-IP 映射表。发起递归查询 :假设以上缓存全部未命中。你的电脑会向 DHCP 分配给它的 DNS 服务器(即 192.168.1.1)发起一个 递归查询 :“请告诉我 www.example.com 的 IP 地址。”迭代查询之旅 :你的路由器(作为 DNS 解析器)会开启一趟 迭代查询 之旅:它首先询问 根服务器 ,根服务器说:“我不认识 www.example.com,但我知道谁管 .com。”接着它询问 .com TLD 服务器 ,TLD 服务器说:“我不管 www.example.com,但我知道 example.com 的权威名称服务器在哪。”最后它询问 example.com 的权威名称服务器 ,这个服务器最终在自己的记录中找到了 www.example.com 对应的 A 记录,并返回其 IP 地址(例如 93.184.216.34)。结果返回与缓存 :路由器将这个 IP 地址返回给你的电脑。你的电脑和路由器都会将这个结果缓存起来(遵循 TTL,即 Time-To-Live),以便下次查询时能直接使用。

至此,浏览器终于知道了它的目的地:IP 地址为 93.184.216.34 的服务器。

第 3 步:建立连接 - 穿越 NAT 的 TCP 握手

知道了 IP 地址,浏览器就可以开始建立连接了。由于是 http,目标端口是 80。

发起 TCP 握手 :浏览器向操作系统申请建立一个 TCP 连接。操作系统会发出第一个握手包(SYN 包)。这个包的初始信息是:

源地址:192.168.1.100 : 50000 (一个随机的高位端口)

目标地址:93.184.216.34 : 80

NAT 的“偷天换日” :这个 SYN 包到达你的路由器(网关)。路由器的 NAT 功能开始工作:它将包的 源地址 从你的内网 IP 和端口(192.168.1.100:50000)改写成路由器的公网 IP 和一个新的随机端口(例如 120.10.20.30:62000)。同时,它在自己的映射表中记下一笔:120.10.20.30:62000 对应 192.168.1.100:50000。然后,它把这个修改后的包发送到互联网。透明的返回路径 :Web 服务器收到 SYN 包后,回复一个 SYN-ACK 包,目标是 120.10.20.30:62000。当这个包回到你的路由器时,路由器查询映射表,找到对应的内网地址,再次改写目标地址为 192.168.1.100:50000,并转发给你的电脑。握手完成 :你的电脑回复 ACK 包,这个过程同样经过 NAT 的翻译。至此,一条 TCP 连接在你的浏览器和 Web 服务器之间成功建立,而 NAT 在其中扮演了一个透明的“中间人”。第 4 步:数据交换 - HTTP 请求与响应

连接已建立,浏览器可以发送 HTTP 请求了。

发送请求 :浏览器通过建立好的 TCP 连接发送一个 HTTP GET 请求报文:

复制
GET / HTTP/1.1 Host: www.example.com User-Agent: Mozilla/5.0 (X11; Linux x86_64; ...) ...1.2.3.4.

接收响应 :Web 服务器接收到请求后,处理并返回一个 HTTP 响应报文,其中包含了状态码 200 OK 和网页的 HTML 内容。

NAT 的持续工作 :这一来一回的所有数据包,都会被你的路由器上的 NAT 进行地址和端口的翻译。

至此,浏览器获得了网页的 HTML 源代码。 浏览器会解析 HTML,如果发现其中还引用了其他资源(如 CSS 文件、图片、JavaScript 文件),它会重复上述的第 3 步和第 4 步(如果域名相同,会重用已建立的 TCP 连接),去获取这些资源,最终将完整的网页渲染出来,呈现给你。

THE END