一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
输入过程
在浏览器输入网址时,浏览器会根据历史记录、书签智能匹配补全域名或 url。
解析地址
输入完成回车后,浏览器会首先判断输入的url是什么类型。
文件地址
如果是文件地址,浏览器会尝试打开该文件,如果文件不存在,会进行提示。
域名
如果用户输入的是域名,那么就需要将域名解析为 ip 地址。
解析的过程如下:
首先会查看本地的 hosts 文件,查找相关域名与 ip 地址的对应规则,若查找到的话,就直接使用 hosts 文件里面的 ip 地址。
若在本地 hosts 文件中,未找到目标域名与 ip 地址的对应关系,浏览器便会向最近的 DNS 服务器(客户端TCP/IP设置中填写的DNS服务器地址)发起一个 DNS 请求,最近的 DNS 服务器收到请求之后,便会查询其缓存记录,若查询到此记录便直接返回结果。
若最近的 DNS 服务器缓存中未查询到相关记录,便向根域 DNS 服务器进行查询,根域 DNS 服务器如果有记录具体的域名与 ip 地址的映射关系,则返回 ip 地址,否则返回对应域服务器(com|cn...)的地址。
最近的 DNS 继续向域服务器发起请求,域服务器收到请求之后,如果有记录映射关系,则返回 ip 地址,否则会返回再下一级域服务器的地址,继续进行请求,直到找到为止。
最后本地 DNS 服务器得到返回的域名与 ip 映射关系信息,将映射信息保存到其缓存中,同时也将 ip 地址返回给用户电脑。
ip 地址
如果用户输入的是 ip 地址,则不需要进行域名解析,走到下一环节。
以上都不是
作为关键字,调用默认搜索引擎进行搜索。
判断缓存
如果本地有缓存,则会走缓存,否则才会发起 http 请求。
缓存规则分为 强缓存 和 协商缓存。
强缓存
强缓存的规则是只要缓存资源没有过有效期,就会直接使用缓存资源,状态码是 200。
强缓存可以通过 Expires 和 Cache-Control 设置,Cache-Control 和 Expires 同时存在的情况下,Cache-Control 的优先级更高。
Expires
服务器可以通过设置 Exprires:(时间戳)的方式设置资源的过期时间。
缺点是服务器的时间和本地时间可能不一致,导致判断不准确。
Cache-Control
服务器可以通过设置 Cache-Control: max-age=(时间长度,毫秒为单元)的方式设置资源的过期时间。
相比 Expires 的好处是这里设置的是相对时间,避免了 Expires 的缺点。
协商缓存
协商缓存的规则是浏览器还是会向服务器发送一个请求判断资源是否发生改变,如果已经发生改变,从服务器重新获取,如果没有发生改变,则使用缓存资源,状态码是 304。
协商缓存可以通过 Last-Modified 和 Etag 设置,Etag 和 Last-Modified 同时存在的情况下,Etag 的优先级更高。
Last-Modified/If-Modified-Since
Last-Modified 是服务器响应请求时,返回该资源文件在服务器最后被修改的时间。
浏览器下次请求该资源时会通过 If-Modified-Since 携带之前返回的 Last-Modified 的值,服务器会通过对比该值与文件最后修改时间判断资源是否发生更新。如果发生更新,重新返回资源,状态码为 200,并更新 Last-Modified值,否则返回 304,浏览器使用缓存文件。
Etag/If-None-Match
Etag 是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成)。
浏览器下次请求该资源时会通过 If-None-Match 携带之前返回的 Etag 的值,服务器会通过对比该值与文件在服务器保存的 Etag 值判断资源是否发生更新。如果发生更新,重新返回资源,状态码为 200,并更新 Etag值,否则返回 304,浏览器使用缓存文件。
发起 http 请求
浏览器获取到域名对应的 ip 地址之后,便会以一个随机端口(1024< port < 65535)向服务端 web 程序 80 端口发起 TCP 连接请求。
浏览器与服务端经过 3 次 TCP 握手之后,建立 TCP 连接,然后浏览器发起一个 http 请求。这里以 GET 或者 POST 请求为例。
请求体格式包含四个部分:
- 请求行(请求方法 URI 协议/版本)
- 请求头(缓存、客户端身份信息等)
- 空行
- 消息体
3 次握手
浏览器 SYN 发送与服务器状态设置很多人已经写了,这里就不重复写了,3 次握手其实就是我们打电话最开始的过程:
- 浏览器发送一个请求确认服务器是否可以收到请求
- 服务器说我可以收到请求,你可以吗
- 浏览器说我也可以
是不是很像大家打电话开头 互相 喂~ 的那一下 😂
目的:为了防止已经失效的连接请求报文发送到服务端,服务端因而产生不必要的响应开销。
数据传出
- 浏览器组装报文并发送报文
- 服务器收到请求根据请求内容返回响应
- 浏览器解析响应报文并进行后续操作(渲染页面,下载文件,展示文件等)
4 次挥手
同理,4 次挥手就像我们通话最后结束的确认过程:
- 浏览器发送一个请求说我没事了,并关闭主动通道,开始只能被动接收
- 服务器说知道了,稍等,我把剩下的数据发送完
- 服务器说我也没事了,我也关闭通道了
- 浏览器说那挂了哈
目的:为确保收发双方数据传输的完整性。
收到 http 响应
服务端返回 http 响应的格式包含四个部分:
- 响应行(状态行)(协议/版本号 状态码 状态说明)
- 响应头(响应头是服务器传递给客户端用于说明服务器的一些信息,以及将来继续访问该资源时的策略。)
- 空行
- 消息体
网络进程会对响应报文进行解析,根据响应头中的 Content-type 来判断响应数据的类型进行相应处理。
比如:
如果是 字节流类型,就将该请求交给下载管理器去下载,
如果是 text/html 类型,就通知浏览器进程获取到的是HTML,准备渲染进程。
浏览器解析 & 渲染页面
这里我们以服务端响应消息体为 HTML 文本为例,浏览器解析 HTML 文本时,主体流程分为以下 6 个步骤:
- 解析HTML,构建DOM树
- 解析CSS,生成CSS规则树
- 合并DOM树和CSS规则,生成render树
- 布局render树(Layout/reflow),负责各元素尺寸、位置的计算
- 绘制render树(paint),绘制页面像素信息
- 浏览器会将各层的信息发送给GPU,GPU会将各层合成(composite),显示在屏幕上
其中,如果在 head 中遇到 style 标签则开始解析css,如果解析到link标签则先异步下载,完成后解析css。
如果在 head 中遇到 script 标签,判断是行内写法则直接解析执行,如果是 src 引入且没有设置 defer 或 async,则同步下载脚本文件,下载完成后立即执行,这里下载过程和执行是 阻塞 的,其他流程都会等下载且执行完成后执行。
在解析过程中,如果遇到请求外部资源时,如图片、外链的 css 、 js 等,请求过程是异步的,并不会影响整个 HTML 文档的加载。
如有任何问题或建议,欢迎留言讨论!