
1. 为什么全书的起点落在“地址栏里的那一串字符串”上读《网络是怎样连接的》这本书的时候我有个很明显的感受大多数人翻完第一章会觉得“浏览器解析 URL”这段太基础了扫一眼就过去了。但你仔细想一下整本书的逻辑链是怎么串起来的用户输入 URL浏览器解析然后 DNS 查询然后对 IP 地址建立 TCP 连接然后发送 HTTP 请求服务器响应浏览器渲染页面……这套流程里URL 解析是绝对的导火索。没有这一步后面所有的网络通信行为都没有发起的目标和依据。书名写得很直接——网络是怎么连接的。这里说的“连接”不是网线插上就完事而是从你有一个想访问的资源的想法开始到数据真正在链路上跑起来为止的完整过程。而浏览器作为你每天接触最多的客户端软件它的第一个任务不是“上网”而是先搞清楚你到底想访问什么。这个“搞清楚”的过程就是解析 URL。很多人会把“解析 URL”和“解析 HTML 文档”搞混。浏览器打开一个网页后确实要做大量的文档解析工作把 HTML 变成 DOM 树那是渲染引擎的事。但这里说的 URL 解析发生在你按下回车的那一瞬间是网络模块的起点。它在本地完成不发包不连网纯粹是对你输入的字符串做语义拆分。拆分的结果直接决定了浏览器接下来要采用什么协议、连接哪台服务器、发送什么内容。所以这一节虽然短却是整本书的“钥匙”。你把这个过程理解了后面讲 DNS、讲 TCP、讲 HTTP 请求报文的时候就能把每个环节跟“解析结果里的某个字段”对起来。学网络最忌讳的就是学成一盘散沙每个协议都会背但串不成一条线。而 URL 解析恰恰就是这根线的起点。2. URL 的逐段拆解每一段都对应一个网络决策2.1 一个完整 URL 的解剖图URL 的标准定义在 RFC 3986 里全称是 Uniform Resource Locator统一资源定位符。虽然你平时就在地址栏里输网址但要让你说出它由哪几部分组成很多人一下子还说不全。拿一个比较完整的例子来拆http://user:passwww.example.com:8080/dir/page.html?query1sortasc#intro这串东西看着长拆开其实就七个部分组成部分本例中的值含义协议http告诉浏览器用什么协议与服务器通信登录信息user:pass可选的认证信息现在已经很少写在 URL 里服务器地址www.example.com告诉浏览器去哪台服务器端口号8080告诉浏览器连接服务器的哪个端口路径/dir/page.html告诉服务器你要哪个资源查询参数?query1sortasc附加的请求条件常用来传搜索词、分页信息片段#intro页面内的锚点位置不会发给服务器《网络是怎样连接的》里那个例子是http://www.lab.glasscom.com/dir/index.htm属于比较简化的形式。它省略了登录信息、端口号和查询参数因为这几项在大多数场景下都不需要。但你要明白简化只是因为你没写不代表这些字段不存在浏览器在内部解析的时候会把缺省值补上。2.2 协议名决定“用什么语言”跟对方沟通协议名是 URL 里冒号左边的那部分比如http:、https:、ftp:。为什么它必须排在最前面因为浏览器解析 URL 的第一个动作就是看协议名来判断后面该走哪套流程。这很像寄包裹时先看你是走陆运还是空运——运输方式决定了后续所有操作。浏览器对常见协议都有内置的处理逻辑。http:和https:会走完整的网络请求流程mailto:会唤起邮件客户端file:会直接访问本地文件系统javascript:会在当前页面执行一段脚本。你可能会觉得这些都是浏览器内部实现但站在网络原理的角度协议名决定了“这个请求该由哪个模块处理”这是解析 URL 最核心的决策点。书里这一段讲得比较含蓄但我建议你记住一句话协议名决定了通信方式而通信方式决定了网络栈的选择。2.3 服务器名、端口号与路径目标与资源的三元组服务器名Host告诉浏览器去哪台机器上找资源。它可以是域名也可以是 IP 地址。如果是域名后面会触发 DNS 解析如果是 IP 地址DNS 这步就可以跳过。这里有一个细节值得注意域名是不区分大小写的WWW.EXAMPLE.COM和www.example.com是同一个地址因为域名系统本身就做了大小写归一化。但路径是区分大小写的/Page.html和/page.html在绝大多数服务器上是两个不同的资源。端口号是很多人容易忽略的部分。默认情况下HTTP 走 80 端口HTTPS 走 443 端口。如果你输入http://www.example.com浏览器会默认连接 80 端口如果你输入https://www.example.com默认就是 443。只有当你明确写了:8080这样的端口号时浏览器才会去连接非默认端口。路径部分对应服务器上的资源位置。但要注意的是这个“位置”是服务器自己解释的不一定就是文件系统里的真实路径。现在的 Web 服务器基本都有路由机制/user/profile可能对应的是一个处理函数而不是一个真实的目录。所以在解析 URL 时浏览器只是把路径当作一个字符串原样发送给服务器具体怎么解释是服务器的事。这个边界搞清楚后面理解 RESTful API 设计会轻松很多。3. 浏览器如何补全你省略的信息——你输的从来不是完整的 URL3.1 默认端口那个看不见的冒号大多数人一辈子都不会在地址栏里输入端口号但这不代表浏览器就省去了端口解析。相反浏览器会在内部把默认端口补全然后拿着完整的五元组协议、服务器、端口、路径、参数去发起网络连接。我之前帮人排查过一个经典问题自己在服务器上用 Nginx 起了个服务监听在 8080 端口然后浏览器里输入http://服务器IP打不开非要加:8080才行。其实这不是网络问题而是默认端口机制的体现——你输http://ip浏览器就默认走 80 端口你在服务器上只监听 8080 的话自然没有人应答。这跟 URL 解析直接相关解析结果里的端口是 80不是 8080。补全默认端口的逻辑不复杂但有一个容易混淆的点http:和https:的默认端口不一样而且如果你用了非默认端口写进 URL浏览器在解析后发起 TCP 连接时会用这个端口但 HTTP 请求头的 Host 字段也会带上这个端口。比如你连接http://www.example.com:8080请求头里的 Host 是www.example.com:8080服务器才能根据 Host 端口区分出你是来访问 8080 上那个站点的。3.2 默认文件名当路径以“/”结尾时服务器做了什么书里讲了一个很实用的知识如果你在 URL 里只写了目录路径比如http://www.example.com/dir/服务器通常会返回该目录下的默认文件。这个默认文件名没有写进标准里完全是服务器软件的配置决定。Nginx 里是index指令Apache 里是DirectoryIndex指令最常见的默认值就是index.html或index.htm。这里有一个值得反复琢磨的点浏览器解析 URL 后路径字段是/dir/它不会自己脑补出/dir/index.html再发给服务器。真正发生的事情是请求到达服务器时服务器发现路径末尾是/于是根据自己的配置查找默认文件找到后返回其内容。也就是说补全默认文件名这个动作发生在服务器端而不是浏览器端。这个区分很重要因为有些人的直觉是浏览器帮忙补全了其实没有。你可以在开发者工具的 Network 面板里验证这一点。输入http://example.com/回车看请求的 Path 列显示的就是/而不是/index.html。响应回来的是 HTML 内容那是服务器处理的结果。这个现象我还专门跟同事讨论过网上很多文章把“浏览器自动补全 index.html”说成是一种标准行为其实是错把服务器端行为安到了浏览器头上。3.3 地址栏输入的“模糊状态”浏览器怎么判断你是要访问还是搜索现代浏览器还有一个非常隐蔽的补全逻辑——地址栏自动识别“这是一个 URL”还是“这是一段搜索关键字”。你输入example.com浏览器会帮你补全成http://example.com/你输入网络是怎样连接的 豆瓣浏览器不会试图解析它而是直接丢给默认搜索引擎。这个判断过程也发生在 URL 解析之前属于用户输入意图分类。浏览器判断的依据很简单输入内容里有疑似域名结构包含点号、以常见顶级域结尾等就往 URL 方向解析否则就当作搜索词。这个逻辑偶尔会出错比如内网主机名myserver不带域名后缀浏览器会默认当搜索词处理。早期的浏览器有个设置项叫“DNS 预解析”后来大部分浏览器干脆都会在本地先做一次域名查询如果本地 DNS 能解析出来就按 URL 处理解析不出来再当搜索词。这种“先试后判”的策略在实际体验上更顺畅但也会引入一个副作用你输了一个不存在的内网主机名会卡在 DNS 超时后才跳转到搜索结果页。很多人在内网环境里访问机器名时遇到过这种情况以为是网络问题其实根因就在这段“输入意图分类”的逻辑上。解决的办法也很直接在地址栏里带上http://前缀浏览器就完全不会走搜索流程了。4. 解析不了的情况浏览器如何判断“这算不算 URL”4.1 非 HTTP 协议的转向地址栏里输入的并不总是http://开头。当浏览器解析出协议名不是 Web 协议的时候会走另外一套处理逻辑。mailto:打开邮件编辑器tel:唤起拨号file:直接读取本地文件data:则将后面这段内联数据当作页面内容来展示。这些都不经过网络层而是由浏览器委托给对应的本地应用或内置模块处理。这里面最有意思的是data:协议。虽然现在很少直接在地址栏里用但在一些安全研究和前端调试场景里很常见。比如你可以在地址栏输入data:text/html,h1Hello/h1浏览器会直接把 HTML 渲染出来整个过程根本不涉及网络请求。这从侧面说明了“解析协议名”是浏览器最优先的动作——决定走网络还是不走网络在拆解 URL 的第一步就已经定了。书里没有展开讲这些非 HTTP 协议因为它的主线是网络连接的过程。但你理解了这个分支判断就会明白为什么地址栏里输入ftp://时浏览器会显示“不支持”或者唤起其他程序而不是直接发送请求。协议名决定了处理模块模块决定了后续的网络行为是否存在。4.2 URL 编码与空格一个最常见的解析坑URL 不是随便什么字符都能直接出现。按照 RFC 3986URL 里允许的字符集是有限的包括大小写字母、数字以及-_.~这几个符号。其他字符尤其是一些保留字符和中文、空格、特殊符号都要通过“百分号编码”转换成十六进制形式再放进 URL。比如空格在 URL 里编码成%20中文字符按 UTF-8 编码后逐字节转成百分号形式。你在浏览器地址栏里输入中文搜索词地址栏里的 URL 会立刻变成一长串%E7%BD%91%E7%BB%9C之类的东西这就是浏览器在做 URL 编码。我踩过一个具体的坑印象很深。当时我在写一个爬虫拼接 URL 时把一个带空格的参数直接拼进去了没有做编码。结果请求发出去服务器返回 400。排查半天发现是空格的问题——浏览器里手动输入相同地址时浏览器会自动把空格编码成%20但我那个脚本是裸字符串拼接的空格原样传给服务器服务器解析 URL 失败就直接拒绝了。这里有一个容易混淆的知识点在application/x-www-form-urlencoded表单提交的场景里空格会被编码成号而不是%20。这个规则只适用于请求体或 Cookie 里的表单数据不适用于 URL 路径和查询参数。很多初学 Web 的人会把这两个场景混在一起写后端解析时就可能出现前端传后端当成空格处理但如果这个值是写在 URL 查询参数里的按标准应该用%20来表示空格。这个坑在排查问题时非常隐蔽因为你肉眼看到的是一个普通的加号。4.3 国际化域名与地址栏的“暗中转换”还有一个平时很难察觉的解析行为国际化域名转 punycode。如果你在一个中文域名网站比如“例子.中国”这种地址栏里输入或复制粘贴 URL浏览器的地址栏里可能显示的还是中文但在真正发起请求之前解析的结果已经转成了xn--开头的 punycode 字符串。这是因为 DNS 系统本身只支持 ASCII 字符集中文域名必须先编码成 ASCII 才能在 DNS 里查询。这个转换是在 URL 解析阶段完成的具体来说是在“判断服务器地址”的时候。如果你写了一个代理脚本去抓浏览器的请求你会看到 URL 里的域名部分全是xn--开头而不是中文字符。很多做网络工具的人第一次看到这个现象会以为代码 bug其实只是没意识到浏览器做了 IDN 编码转换。不会转的反而是你自己程序里拼出去的 URL——这也是为什么有些客户端程序访问中文域名总是超时因为域名没有编码就直接去查 DNS 了查出来的肯定是空结果。5. 解析完成之后浏览器真正交给网络的是一份“请求消息”5.1 从 URL 到请求行的映射URL 解析完成之后浏览器并没有立刻去建立连接它还要做一件重要的事情根据解析结果生成 HTTP 请求消息的框架。这个动作是理解“URL 解析”和“HTTP 请求”之间逻辑衔接的关键。HTTP 请求消息的起始行就是请求行格式是方法 路径 HTTP版本比如GET /dir/page.html HTTP/1.1你仔细看这个请求行里放的是什么是路径和查询参数不包含域名和端口。那域名去哪里了它放在下一行的Host字段里。这个设计不是随意的而是 HTTP/1.1 规范的要求一台物理服务器可能同时托管多个域名服务器靠Host字段来区分你要访问的是哪个虚拟主机。如果请求行里带了完整 URL现代代理服务器还支持这种绝对路径形式比如GET http://www.example.com/dir/page.html HTTP/1.1但浏览器直连源站时几乎都是用相对路径加 Host 头的组合。这个过程在浏览器开发者工具里看得一清二楚。随便打开一个网站切到 Network 面板点开某一个请求的 Headers 标签你能看到GET /headers HTTP/1.1 Host: www.example.com User-Agent: Mozilla/5.0 ... Accept: text/html,application/xhtmlxml...整个请求消息的头部字段都是浏览器在 URL 解析完成后根据解析出的主机名、路径等信息组装出来的。你可以理解为URL 解析把用户的意图拆成了结构化字段而请求消息是这些字段第一次被翻译成“网络语言”的产物。5.2 Host 头的由来虚拟主机是解析 URL 的直接受益者Host 头的存在直接导致了一个 Web 架构上的重要变化——虚拟主机。在 HTTP/1.0 时代请求行里用的是完整 URL 路径加上服务器 IP一个 IP 对应一个站点。但互联网发展太快IP 资源不够用出现了多个域名共享一台服务器的情况。HTTP/1.1 引入 Host 头之后服务器可以通过 URL 解析结果里提取出来的域名区分出对不同域名的请求从而在一台服务器上对外提供多个网站的访问。这也是为什么在浏览器里直接访问 IP 地址经常看到的是服务器默认站点而不是你预期的那个网站。因为服务器没法从Host: 192.168.1.10里判断出你要访问哪个虚拟主机只能回退到默认配置。这个问题的根源依然要回到 URL 解析用户输入的 URL 里如果没有域名只有 IP那么解析结果里的 Host 字段就是 IP虚拟主机机制就失效了。我当年第一次在 Nginx 上配置多个 server block 后就遇到过这个坑两个域名都解析到同一台服务器但访问第二个域名时一直显示第一个站点的内容。排查到最后发现是第二个 server block 的 server_name 写错了导致 Nginx 匹配不到对应的虚拟主机直接把请求丢给了默认的第一个站点。这个问题如果不理解 Host 头的机制很难想到方向。5.3 打开开发者工具亲眼看一次 URL 的“完整旅程”纸上谈兵再多不如实际操作一次。我建议你做一个最小实验打开浏览器开发者工具切换到 Network 面板勾选 Preserve log保留日志然后清空记录在地址栏输入一个不含协议和路径、只有域名的地址并回车。你会看到浏览器发出了两条请求第一条通常是访问/返回 301 跳转第二条才是跳转后的最终地址。这里就能直观地看到浏览器补全默认路径的行为以及服务器返回重定向后浏览器重新解析新 URL 的过程。如果把鼠标悬停在第一条请求上还能看到完整的响应头里有一个Location字段它的值就是一个完整的新 URL浏览器拿到后会对它再做一次完整的解析流程然后发起第二次请求。这个实验最大的价值是让你意识到URL 解析不是一个一次性的动作而是一个可能反复发生的过程。用户输入 URL解析后发出请求服务器返回一个重定向响应浏览器解析响应头里的 Location再去请求新的 URL。整个链路上每一次“跳转”背后都是一轮新的 URL 解析。我在实际接触网络调试的工作里最常用的工具就是这个 Network 面板。不只是看请求是否成功更重要的是看请求行、Host、路径、查询参数这些字段是怎么从 URL 字符串一步步变成报文的。看懂了这一步你对 HTTP 协议的运行方式基本上就有了画面感后面再去学 TCP 三次握手、DNS 递归查询都能找到对应的参照物。《网络是怎样连接的》这本书的高明之处恰恰就在这里它没有一上来就讲 TCP 报文格式而是从你每天都会做的一个动作——输入网址——开始拆解。你先把这一小节读透再往下翻你会发现书里讲的每一个概念都能在这串 URL 上找到落点。读网络的书最怕的是概念悬空而 URL 解析就是那个能让你把概念落到实处的抓手。