从打开一个商品页开始:网址怎样变成网络请求,再变成页面

跟着商品 42 的一次访问,从网址、DNS、TCP、TLS 和 HTTP,走到 IP 路由、链路传输、服务端处理与浏览器渲染,再用小实验定位慢在哪里。

假设你开了一家很小的网店,服务器上只有一个商品页面。顾客点开 https://shop.example.com/products/42,看到“马克杯,29 元”。没有微服务,没有复杂架构,只有浏览器和一台服务器。

顾客没有输入服务器的 IP,没有指定走哪条网线,也没有亲手把收到的数据排成页面。这些工作分别由浏览器、操作系统、网络设备和服务端程序完成。我们沿着这次访问走一圈,看看它们各自接过什么、做了什么,再交给谁。

先约定主线:首次访问,没有可直接使用的页面缓存和连接,采用 IPv4、HTTPS、TCP 和 HTTP/1.1。后面再加入缓存、代理与新协议。example.com 是示例域名,文中的公网地址也只用于说明,不是可访问的网店。协议解释依据 RFC 和浏览器文档;末尾实验使用本机 HTTP,刻意将公网 DNS、路由和 TLS 留在实验范围之外。

一、先让最小网店真的返回一个页面

设想服务器上已经有一个程序在等待请求。它约定:收到对 /products/42 的读取请求,就返回一段 HTML;收到 /api/products/42,就返回同一个商品的 JSON 数据。

浏览器和服务器之间需要共同理解的请求与响应规则,这就是 HTTP,中文叫超文本传输协议。先把它看作双方交换消息的格式和语义:请求写明要做什么、访问什么资源,响应说明处理情况以及返回的数据。

暂时跳过底下怎样通信,一次 HTTP/1.1 交换可以读成这样。以下是解密后的应用消息示意,实际行结束使用 CRLF,HTTPS 抓包时不会直接看到这些明文:

GET /products/42 HTTP/1.1
Host: shop.example.com
Accept: text/html

GET 表示读取,路径指向商品页面,Host 说明要访问的站点。服务器可以回答:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 19

<h1>Product 42</h1>

这里正文恰好是 19 个 ASCII 字节,不计展示代码块时额外添加的末尾换行。真实页面通常更长,但这几行已经说明一件事:服务器返回的是带有类型和长度等信息的数据,浏览器再理解它。200 表示请求处理成功,Content-Type 帮助接收方选择解释方式。读取 JSON 时,响应类型会改为 application/json,正文也换成字段与值。HTTP 语义:RFC 9110

浏览器地址栏中的 https://shop.example.com/products/42 是统一资源定位符,简称 URL。拆开看:https 指定安全访问方式,shop.example.com 是主机名,没显式写端口时 HTTPS 通常使用 443,后面是资源路径。如果加上 ?color=white,查询参数也会送往服务端;如果加上 #reviews,片段标识由浏览器使用,不会作为这次 HTTP 请求的目标片段发给服务器。

这时已经有了应用层的意图:“读取这个站点的这个页面”。但主机名还不是可用于网络投递的地址。接下来先解决去哪里。

二、知道店名以后,怎样找到服务器地址

DNS 回答的是名字对应什么,不负责搬运页面

浏览器需要将 shop.example.com 转成可连接的地址。域名系统 DNS 保存名称与记录的关系,例如 A 记录给出 IPv4 地址,AAAA 记录给出 IPv6 地址。假设这次最终得到 203.0.113.10,浏览器才有了主线中的目标 IP。

可以把过程想成查通讯录,但通讯录本身也分层。通常本机把查询交给一个递归解析器;解析器的任务是替客户端寻找最终答案。它若没有可用缓存,就沿域名委派关系,向根、顶级域和相应权威服务器寻找负责方,再取得记录。浏览器不必自己逐台询问世界上所有 DNS 服务器。

本机缓存、递归解析器缓存和权威记录之间,可能已经有答案;因此不是每次点链接都走完整查询。记录带有允许缓存的时间,称为 TTL,接收方可以在有效期内复用结果。名称还可能先指向另一个名称,再解析到地址。DNS 名称、解析器与缓存:RFC 1034

“DNS 返回 IP”与“服务器返回 HTML”是两次不同的交互。DNS 通常不会替浏览器接收和转发商品页请求。解析成功只说明拿到了名称记录,不能证明 443 端口可连接、证书正确或商品存在。后面增加 CDN 时,这个地址甚至可能属于边缘节点,而非商品数据库所在的机器。

改了地址,为什么还有人访问旧机器

假设旧地址是 A,准备把网店搬到 B。管理员在权威 DNS 修改了记录,但某个递归解析器仍缓存着 A,浏览器就可能继续拿到 A。即使记录更新已被看到,浏览器已经建立的连接也不必因为 DNS 变化立即断开。

所以迁移需要给旧入口留出过渡时间,观察访问,再逐步退出。TTL 控制缓存记录的使用期限,不是“到点关闭所有旧连接”的命令。缩短 TTL 也不能瞬间清除此前已经缓存的长 TTL 答案。

如果查不到域名,问题还停在寻址前;如果能解析但访问不了,继续调查后续阶段。把两者都描述成“网站挂了”,排查时就失去了方向。

三、为什么一个地址还不够,还需要端口和连接

拿到 203.0.113.10 后,浏览器要联系这个地址上的 443 端口。IP 在网络中标识目标,端口帮助目标主机区分传输层的服务入口。一个服务器上可以同时运行网站、数据库和管理程序,它们不应收到同一份请求。

操作系统为通信提供 socket,即套接字接口。服务端程序创建监听 socket,绑定本地地址和端口,等待连接。浏览器通过客户端 socket 发起连接,操作系统通常分配一个临时源端口,例如 53120。通信中于是出现一组定位信息:源 IP、源端口、目的 IP、目的端口和协议。

注意监听 socket 和已经建立的连接。服务器可以一直监听 443,并在这个监听入口下处理许多客户端连接;各连接由端点信息区分,不需要为每个顾客再开一个新的服务端监听端口。操作系统处理底层连接事件,应用通过 accept 等接口取得可读写的连接。使用事件循环的服务器也不一定每条连接创建一个线程。

TCP 建连时,双方到底在确认什么

传输控制协议 TCP 提供可靠、有序的字节流。双方初始还没有本次连接的同步状态,需要确认各自使用的初始序号。普通三次握手可以简化为:

浏览器所在主机 -> 服务端:SYN,初始序号 x
服务端 -> 浏览器所在主机:SYN + ACK,初始序号 y,确认 x + 1
浏览器所在主机 -> 服务端:ACK,确认 y + 1

这不是在检查商品是否存在,而是在为本次字节传输建立状态。后续数据按序号标识位置,接收方确认已收到的范围,发送方根据情况重传丢失的数据。丢包可能增加等待;持续故障则让连接最终失败。TCP 不会保证网络永远可达,也不能保证远端业务一定执行。TCP 建连与字节流:RFC 9293

浏览器把一段请求写入 socket,内核可以将它拆成多个 TCP 段;接收程序一次读取也可能只拿到一部分,或连续拿到多条消息的数据。HTTP 必须按自己的消息规则识别请求结束,不能认为一次 write 必然对应一次 read。RPC 的消息长度和分帧机制也在解决同类问题。

先记下一个确认边界:TCP 对数据的确认,至多说明对端 TCP 接收到了相应字节。应用可能还没读取,数据库更可能还没提交。网络“送到了”和业务“做完了”之间,还有多步。

四、TCP 段怎样跨过路由器,抵达另一台机器

前面说到“发 SYN”,实际上这个 SYN 也得经过网络。为了把逻辑讲顺,我们先认识连接,再回头拆开每一个 TCP 段怎样上路。并不是先在完全不用 IP 的地方建好 TCP,之后才开始使用 IP。

应用交字节,操作系统逐层封装

HTTPS 的 HTTP 数据先经过 TLS 加密,再由 TCP 传输。TCP 段外面加上 IP 头,形成 IP 数据报;数据报再装入当前链路可传输的帧。帧最后通过无线或有线介质变成信号发出去。SYN 等握手控制信息也会被装入 IP 数据报和链路帧,只是里面尚未装着页面请求。

这里的分层是为了拆开职责。HTTP 不需要知道网卡怎样发信号,TCP 不需要理解“马克杯”这几个字,IP 路由器也不需要读取商品路径。

浏览器产生消息,系统封装数据并选择下一跳;邻居缓存未命中时先解析网关地址,再逐跳转发

图里向下拆的是本机如何准备数据,向右走的是数据交给网关。两种方向接起来,才能理解一份请求既经过本机协议栈,又经过多台转发设备。

层次 本例交给下一步的东西 它主要解决的问题
应用层 HTTP 请求与响应;DNS 也属于应用协议 消息含义和交互规则
安全处理 TLS 记录承载的加密数据 身份验证、机密性与完整性
传输层 TCP 段 端口、连接和可靠有序的字节传输
网络层 IP 数据报 跨网络的寻址与逐跳转发
链路层 当前链路的帧 向这一跳的接收方交付数据
物理传输 电、光或无线信号 在介质上传输比特

TLS 在这里单独列出来帮助理解 HTTPS,不是另外宣布一个统一的网络层编号。常见 TCP/IP 分层与七层参考模型的划分也不完全相同,学习时先把工作分清,再对照名称。

电脑先找下一跳,不是直接找远方服务器的网卡

假设顾客电脑位于家庭网络,地址是 192.168.1.20,服务器在公网。操作系统查路由表,发现目标不在本地直连网络,应交给默认网关 192.168.1.1。要在以太网上发给这个网关,还需要知道网关接口的链路地址,也就是 MAC 地址。

本机可从邻居缓存获取;没有时,IPv4 以太网使用地址解析协议 ARP,在本地链路询问谁拥有这个网关 IP,再记录回答。它查的是这一跳网关的地址,通常不是跨互联网寻找远端服务器的 MAC。IPv6 使用邻居发现等机制,这里不把 ARP 套到 IPv6 上。ARP 的地址解析范围:RFC 826

网关收到帧,取出里面的 IP 数据报,查自己的路由表,再选择下一跳和出口。到下一段链路时,会重新按那段链路封装;所以帧中的源、目的链路地址会随跳变化。IP 数据报中的目标地址用于跨网寻址;普通路由转发也会调整 TTL 等信息,并非整份数据报完全不变。IPv4 转发与生存时间:RFC 791

家庭网关通常还做网络地址转换 NAT,把内网源地址和端口映射到公网出口,记住映射,让响应能回来。NAT 不等于所有路由器的必备动作,也不意味着任意外部主机可以主动连进家庭电脑。这里用它解释家中多台设备为什么能共享一个公网出口。

因此,到达公网服务器的流量可能已经换过源地址;排查“客户端 IP”时必须说明从哪个观察点看。网关知道映射,服务端只看见它收到的端点,HTTP 代理还会另外引入一层观察差异。

五、连上 443 后,怎样确认真的是这家店

TCP 建连只建立传输通道。一个错误地址也可能监听 443,所以浏览器还要确认对方是否有资格代表 shop.example.com,并协商加密通信。

传输层安全协议 TLS 承担这项工作。以 TLS 1.3 的完整握手为例,客户端发送支持的参数和密钥交换材料,服务端选择参数并提供自己的交换材料;使用证书认证时,服务端提供证书和签名证明。客户端校验信任链、主机名、有效期等,再验证握手完整性,双方派生用于通信的密钥。

证书包含的公钥不等于后续所有页面内容都直接用这个公钥加密。证书认证与密钥协商共同建立可信的会话,后续应用数据使用协商出的流量密钥保护。这里先理解参与者的动作,不必把密码学公式全部展开。TLS 1.3 握手:RFC 8446

如果证书过期或名称不匹配,浏览器可以在发出正常页面请求前终止访问。连接可达、TCP 成功,都不能替代身份验证。另一方面,TLS 验证通过只说明通信对象通过了相应的站点身份校验,不证明商品价格正确,也不证明站点没有应用漏洞。

浏览器与服务端还可以协商使用哪个 HTTP 版本。公网入口若是 CDN 或 Nginx,TLS 可能终止在这个入口上;它解密请求,再通过另一条连接访问应用。后面那条连接是否继续使用 TLS,需要单独配置。浏览器到入口安全,不自动等于入口到数据库的所有路径都已加密。

六、服务器收到了字节,怎样变成一次业务处理

网络数据到达服务端网卡以后,操作系统按协议处理,把属于连接的 TCP 字节按序提供给相应 socket。若 TLS 在当前服务终止,TLS 实现验证并解密记录,再交给 HTTP 解析器;解析器识别方法、路径、头部和正文,应用才开始理解“查询商品 42”。

最简单的程序直接根据路径查询商品数据,填充 HTML 并返回。更常见的站点先进入反向代理:浏览器把它当成网站入口,它替后端服务收请求。入口可以依据主机名、路径选择处理方式,静态文件直接返回,业务请求则转发到应用实例。

这里要分清两次接收。入口读取到完整请求,不代表应用已经处理;应用拿到参数,也不代表查询数据库已经完成。每一步可能排队、超时或被拒绝。对于 HTTPS,路由器通常看不到被加密的路径;需要读取路径的七层代理应在某处得到解密后的 HTTP 消息。

假设 Nginx 把请求交给 127.0.0.1:18081 上的商品程序。Nginx 与应用之间是另一条连接,程序按自己看到的请求获取商品,返回 HTML,Nginx 再将响应送给浏览器。两边的连接可以分别复用,没必要一一永久绑定。入口对客户端暂时停止发送,也不代表应用端已经自动取消正在进行的工作。

如果页面里还要展示实时库存,商品程序可能调用库存服务。此时外部这一次页面访问又引出了内部的一次远程调用。内部请求也有寻址、连接、编码、执行和返回,只是客户端从浏览器换成了商品程序。RPC 原理:怎样像调用本地方法一样调用远程服务继续拆开了这个过程。

首次访问先解析地址,再建连验证身份,收到响应后继续加载资源;缓存和连接可走复用分支

图里 DNS 是提供地址的查询分支,业务请求从浏览器直接走向解析所得入口。进入“拿到响应”以后还会产生资源请求,并不是一个网页永远只对应一次网络交换。

七、拿到 HTML,为什么页面还没有立刻显示完整

浏览器读取 HTML 并构建文档对象模型 DOM,也就是供浏览器和脚本使用的页面结构;读取 CSS 后形成样式规则。结合结构和样式,浏览器计算元素的位置、尺寸,绘制文字与图像,必要时由合成阶段组合图层,才形成屏幕上看到的页面。

这个过程可以随数据到达逐步进行,不必一律等全部 HTML 下载完才开始。发现样式、脚本、图片等外部资源后,浏览器继续请求它们;脚本的加载与执行方式又会影响解析和后续工作。于是“HTML 返回得快”和“用户很快看见商品”可能相差很远。浏览器解析与渲染过程

商品页还有另一种常见形态:先返回页面框架,再由 JavaScript 请求 /api/products/42。API 返回 {"id":42,"name":"马克杯","price":29},脚本读取数据,创建或更新页面元素,再触发布局与绘制。JSON 是给程序读取的数据,不能因为网络返回了它,就断言完整商品界面已经显示。

直接在地址栏打开 JSON 地址,浏览器可能展示原始内容或格式化视图;在页面中 fetch 同一个地址,则由 JavaScript 消费响应。这两个入口使用相近的网络机制,后续呈现者却不同。若前后端跨了域,还会涉及浏览器跨源访问规则;传输成功与脚本获准读取结果,同样要分开判断。

假设开发者工具显示 API 已返回 200,页面却一直转圈。需要检查响应是否能被解析、脚本是否报错、页面状态是否更新,不能直接要求网络设备重启。如果 HTML 已显示但图片还没出来,就沿那张图片自己的请求继续查。

八、再次打开时,哪些步骤可以不做

页面缓存、地址缓存和连接复用各省了什么

浏览器若持有仍可直接使用的资源缓存,可以省掉该资源的网络请求。缓存需要验证时,可以携带之前记录的验证信息;服务器确认内容未变,返回 304,浏览器使用原有正文。这次仍然发生了网络交互,只是不用再次下载相同正文。

DNS 缓存省的是重新查询名称;连接复用省的是重复建连与相应安全握手。三者互不替代:DNS 命中时仍可能需要新建连接,连接能复用时仍可能需要重新下载内容。缓存策略还必须符合资源性质,用户私有页面不能只因想提速就放进任意共享缓存。HTTP 缓存与验证

CDN,即内容分发网络,可以让边缘节点替源站提供可缓存资源。浏览器可能先从边缘拿到商品图片,动态价格仍回到源站查询。缓存未命中时,边缘发起回源请求;回源失败也有自己的错误和回退策略。这已经是多段通信,每段都应分别看连接、身份和响应。

因此“点链接就 DNS、三次握手、TLS、HTTP”是第一次访问的一条教学主线。真实瀑布图中缺少某个阶段,可能恰好说明已有缓存或连接可用,不应据此认定浏览器漏做了步骤。

HTTP/2 和 HTTP/3 改变了哪些传输安排

HTTP/2 可以在一条连接上组织多个流,让多个请求与响应交错传输;但采用 TCP 时,连接上的丢失字节仍可能使后到字节等待恢复。多路复用不代表带宽无限,也不代表所有等待都消失。HTTP/2 流与帧:RFC 9113

HTTP/3 将 HTTP 映射到 QUIC。QUIC 通常运行在 UDP 之上,并提供可靠流、拥塞控制及集成的安全握手;不能因为底下用了 UDP,就推断它不保证所需的可靠传输。它也没有普通的 TCP 三次握手这一步。网络是否支持、端点如何发现和协议如何协商,都影响实际选择。HTTP/3:RFC 9114

换协议后,“先确定对象、再交换请求、由应用解释响应”的主干还在。需要改变的是对连接、丢包影响和握手过程的具体分析,而不是把 TCP 的每一个动作原封不动套上去。

九、在本机开一家小店,亲眼看见额外请求

仓库附有一个不依赖第三方包的小程序:scripts/experiments/shop-network-demo.mjs。它在 /products/42 返回页面,页面引用一份 CSS 和一份 JavaScript;脚本随后查询商品 JSON,将价格显示出来。它不连接真实数据库,商品数据只是内存中的演示值。

在项目根目录打开终端运行:

node scripts/experiments/shop-network-demo.mjs --port 18081 --name product-a

浏览器打开 http://127.0.0.1:18081/products/42,在开发者工具的 Network 面板保留请求记录并重新加载。预期能找到页面、/assets/shop.css、/assets/shop.js 和 /api/products/42;浏览器还可能自行请求图标等资源,不必要求记录恰好只有四条。响应头 X-Demo-Backend 与终端日志都能确认是哪一个演示进程提供内容。

再直接打开 http://127.0.0.1:18081/api/products/42,对比页面与 JSON 的区别。关闭演示进程后重新访问,观察连接失败;使用不存在的 /api/products/99,则会收到明确的 HTTP 404。这两种失败停在不同阶段:前者没拿到有效 HTTP 响应,后者是应用已经读懂请求并回答资源不存在。

命令行也可以查看请求和响应头。Windows 上使用 curl.exe,避免与其他命令别名混淆:

curl.exe -v http://127.0.0.1:18081/api/products/42

本机地址不会经过家用网关,直接写 IP 也没有展示公网 DNS;明文 HTTP 不进行 TLS 握手。这个实验验证的是“应用消息、端口、资源请求、页面更新”的衔接。若观察自己的公网 HTTPS 站点,可在浏览器中进一步查看 Protocol、Remote Address、Timing 和 Security;连接复用或缓存存在时,不保证每次都能看到新的 DNS 和握手耗时。

十、网页打不开时,沿哪条证据往下查

先不要把所有等待合并成“接口慢”。以下各行描述的是排查方向,不是看见一个错误就能直接确定唯一根因。

观察到的现象 下一步查什么 这项证据还不能证明什么
域名解析失败 名称拼写、解析器、记录和缓存 后端应用一定宕机
建连超时或被拒绝 地址端口、监听、网络策略、路径与容量 请求已经进入业务处理
TLS 校验失败 证书名称、信任链、有效期和入口配置 改用明文就是正确修复
长时间等首字节 入口排队、应用、下游依赖及回传路径 一定是数据库慢
收到 502 或 504 哪一层生成响应、上游连接和处理日志 上游业务一定未执行
响应已返回,界面没更新 内容、脚本异常、主线程、布局与资源 网络还没有收到数据

比如入口日志显示请求已转发,商品服务日志却没有对应记录。可能是应用还没读取、日志采集缺失,也可能请求根本没到该实例。需要结合目标实例、连接阶段和请求编号继续查,不能把“没有日志”当成“没有执行”的充分证据。写操作还要根据业务记录确认效果,网络异常不能替代结果查询。

浏览器 Timing 中的等待时间也有观察边界。从请求发送到首字节返回的时间,可以同时包含网络、代理排队和后端执行;只有结合入口与应用侧耗时,才能继续拆分。跨机器绝对时间可能有偏差,优先关联请求编号和各段自身测得的耗时。

现在再看顾客的那一次点击:URL 给出访问意图,DNS 提供名称记录,传输和安全机制建立可用通道,IP 与链路设备逐跳运送,服务端按 HTTP 处理,浏览器把响应组织成页面。每一步都有自己的完成条件,只有把这些条件接起来,才解释得了“网络通了,为什么页面还不出来”。

下一步让这家网店从一台服务器扩成多台:DNS、NLB 与 Nginx:从一家小店理解多级负载均衡。那里继续追问同一份请求应该选哪一个区域、哪一个入口和哪一个应用实例。