前言:那个老生常谈的面试题
"从你在浏览器输入一个 URL,到页面显示出来,中间发生了什么?"
这道题我被问过不下十次,也背过不下十个版本的答案。但说实话,每次答完我都隐约感觉自己是在背八股,而不是真的理解了什么。
直到最近拿起这本户根勤写的《网络是怎样连接的》,第一章就把我拉到一个"哦,原来如此"的清醒状态。这本书的切入角度很妙——它不是枯燥地讲协议定义,而是追着一个数据包,从浏览器出发,一路旅行到服务器,让你亲眼看清楚每一个环节到底发生了什么。
第一章就聚焦在这趟旅程的起点:浏览器。
一、URL:一个地址,藏着很多信息
万事的起点是那一串我们天天打的网址,学名叫 URL(Uniform Resource Locator,统一资源定位符) 。
我们平时觉得网址不就是个字符串嘛,但书里一拆解,发现它其实是一份"指令书":
↑ ↑ ↑
协议类型 服务器名称 资源路径
协议类型告诉浏览器:"用什么方式去取东西"——http:// 就是用 HTTP 协议去 Web 服务器拿,ftp:// 就是用 FTP 协议去文件服务器拿,file:// 则是直接读本地文件。
服务器名称是目标地址(后面 DNS 会把它翻译成 IP 地址)。
资源路径是你要哪个文件。如果路径末尾是 / 或者没有写文件名,Web 服务器通常会默认返回 index.html。
一个小细节:URL 里省略了端口号,是因为 HTTP 有默认端口 80、HTTPS 是 443,浏览器会自动补上。这就好比你打电话给公司总机,不需要说分机号,总机会转给默认部门。
二、HTTP 请求:浏览器在"说"什么
解析完 URL,浏览器知道了要去哪、取什么,接下来它得把这个需求"说出口",这就是生成 HTTP 请求报文的过程。
HTTP 请求报文的结构非常规整:
GET /dir/page.html HTTP/1.1 ← 请求行(方法 + 路径 + 版本)
Host: www.example.com ← 消息头(Header)
User-Agent: Mozilla/5.0 ...
Accept-Language: zh-CN,zh;q=0.9
(空行) ← 头部结束标志
(消息体,GET 请求通常为空)
-
方法:最常见的是 GET(我要拿数据)和 POST(我要提交数据)。
-
URI:服务器上资源的路径。
-
消息头:附带很多"背景信息",告诉服务器浏览器类型、语言偏好、缓存设置等等。
服务器收到后会返回响应报文,格式类似:
HTTP/1.1 200 OK ← 状态行
Content-Type: text/html; charset=UTF-8
(空行)
... ← 响应体(页面内容)状态码是个很有意思的设计:200 OK 是成功,301/302 是重定向,404 Not Found 是找不到,500 Internal Server Error 是服务器炸了……这套数字语言开发者都太熟悉了。
有意思的地方:HTTP 的"无状态"设计意味着每次请求都是独立的,服务器不记得你上次来过。这也是为什么我们需要 Cookie/Session 这类机制——本质上是在 HTTP 头里塞一个"认证贴纸",让服务器认出你。
三、DNS 查询:域名到 IP 的翻译官
有了 HTTP 请求,浏览器还差一步——它知道要去 *www.example.com*,但网络世界里真正能用来寻址的是 IP 地址(比如 93.184.216.34),不认识"www.example.com"这种对人友好的名字。
这就需要 DNS(Domain Name System) 出场了。
浏览器会调用操作系统的解析器(Resolver) ,向 DNS 服务器发出查询请求,过程大概是这样的:
浏览器
↓ 调用 Socket 库的解析器
本地 DNS 服务器(运营商或公司内网提供)
↓ 如果本地没有缓存
根域名服务器(Root DNS)
↓ 返回负责 .com 的服务器地址
顶级域名服务器(.com DNS)
↓ 返回负责 example.com 的服务器地址
权威域名服务器
↓ 返回 www.example.com 的 IP 地址
书里对这个"分级查询"的设计解释得很透彻:为什么不把全球所有域名都存在一台服务器上?因为那台服务器撑不住,而且全球只有一台 DNS 的话,宕机了整个互联网就瞎了。分级的设计让每层服务器只管自己那一块,既高效又可靠。
关于 IP 地址:书里提到 IP 地址其实分两部分——网络号(你在哪个网段)和主机号(这个网段里的第几台机器)。这就好比一个地址"北京市朝阳区XX小区101号"——北京市朝阳区 XX 小区是网络号,101 号是主机号,路由器靠这个找到目的地所在的网络,再交给那个网络里的具体机器。
四、Socket:建立连接的那根"管道"
IP 地址到手,HTTP 报文准备好,终于到了真正发送数据的环节。
这里要出场的主角是 Socket(套接字) 。
Socket 是操作系统提供给应用程序的网络通信接口,你可以把它想象成一个插孔——浏览器这边有一个,服务器那边有一个,中间拉一根管道,数据就可以互相流通了。
建立 Socket 连接的流程:
-
socket() → 创建一个套接字(得到一个"描述符")
-
connect() → 拿着 IP + 端口,向服务器发起连接请求
-
write() → 把 HTTP 请求数据塞进管道
-
read() → 从管道里读取服务器返回的响应
-
close() → 通信完毕,关闭连接
端口号是这里的关键概念之一。服务器同时跑着很多服务(Web、FTP、邮件……),端口号就是"部门门牌",HTTP 默认是 80 端口,HTTPS 是 443 端口。浏览器发起连接时,目标 IP + 目标端口缺一不可。
一个小细节:HTTP/1.0 时代,每次请求完服务器就会主动关闭连接,下次再请求要重新三次握手;HTTP/1.1 引入了长连接(Keep-Alive) ,一次 TCP 连接可以发多个 HTTP 请求,省去了大量重复建连的开销——这对于一个动辄需要加载几十个资源文件的网页来说,性能提升相当可观。
五、把整条链路串起来看
读完第一章,整个流程在脑子里终于清晰地串起来了:
你在浏览器输入 www.example.com/index.html
↓
浏览器解析 URL(协议=https, 服务器=www.example.com, 路径=/index.html)
↓
生成 HTTP 请求报文(GET /index.html HTTP/1.1)
↓
DNS 查询:www.example.com → 93.184.216.34
↓
调用 Socket 库,向 93.184.216.34:443 发起 TCP 连接
↓
通过 write() 发送 HTTP 请求
↓
通过 read() 接收 HTTP 响应
↓
浏览器渲染页面
每一步都有明确的职责,每一层都做好自己分内的事——这正是分层设计的精妙之处。
六、对我的触动:从"会用"到"懂了"
这本书给我最大的冲击,不是某个具体的知识点,而是视角的转换。
以前看待 HTTP 请求,我的视角是"我调用了一个 fetch,然后数据回来了"。读完这一章,我的视角变成了"我的一份数据,经历了 URL 解析、HTTP 封装、DNS 查询、Socket 建立,最终才以电信号的形式离开我的网卡"。
这种感觉很像是——原来你只知道插上电插座灯就亮了,现在你终于知道发电站、输电线、变压器是怎么协作的。
对开发者的一点实际意义:
-
当线上接口偶发超时,你会想到"是不是 DNS 解析变慢了?是不是连接没有复用导致频繁三次握手?"
-
当排查跨域问题,你知道那不是什么玄学,就是 HTTP 请求头和响应头之间的一次"协商"。
-
当优化首屏加载速度,你会想到 DNS 预解析、HTTP/2 多路复用、连接预热这些真实有效的手段。
底层原理,是性能优化和问题排查的底气。
结语
《网络是怎样连接的》第一章只是一个开始。后续的章节还会深入到协议栈的内部、网卡的工作方式、路由器的转发逻辑……这趟数据包的旅程还很长。
但第一章已经给了我足够的惊喜——它把我从"知道有 DNS 这个东西"带到了"理解 DNS 为什么这么设计、它的分级查询解决了什么问题"。这就是好书的价值:不只给答案,还给你思考框架。
强烈推荐给每一个想把计算机网络从八股文变成真理解的开发者。
如果这篇文章对你有帮助,欢迎点赞收藏~后续我会持续更新这本书的读书笔记 **🚀