【面试】经典问题之从输入URL到页面展示

看阿里的面经又看到了这个,之前虽然复习过,但是还是有些模糊,这次再复习一下。

简略版

1. 输入URL

  1. 解析URL
  2. 检查HSTS列表
  3. DNS解析
  4. TCP三次握手
  5. 发送HTTP请求

2. 服务器处理请求

  1. 服务器处理请求
  2. 服务器返回响应

3. 浏览器解析渲染

  1. 解析HTML生成DOM
    - 标记化
    - 建树
  2. 解析CSS生成CSSOM
    - 格式化样式表
    - 标准化样式属性
    - 计算每个节点的具体样式:继承和层叠
  3. DOM树和CSSOM树结合生成渲染树
  4. 根据渲染树计算每个节点的信息
  5. 浏览器根据计算好的信息渲染页面
    - 建立图层树
    - 生成绘制列表
    - 生成图块并栅格化
    - 显示器显示内容

详细版

1. 解析URL并发起请求

浏览器在解析URL后会构建请求行: 请求行包括请求方法、请求地址和 HTTP 协议版本。

1
2
// 请求方法是GET,路径为根路径,HTTP协议版本为1.1
GET / HTTP/1.1

1.1 浏览器检查自带的“预加载 HSTS(HTTP严格传输安全)”列表

  • 这个列表里包含了只使用HTTPS进行连接的域名。如果网站在这个列表里,浏览器会使用 HTTPS 而不是 HTTP 协议,否则,最初的请求会使用HTTP协议发送
  • 网站不在 HSTS 列表里,也可以要求浏览器使用 HSTS 政策进行访问。浏览器向网站发出第一个 HTTP 请求之后,网站会返回浏览器一个响应,再次请求浏览器时只使用 HTTPS 发送请求。这里的第一个 HTTP 请求,可能会使用户受到 downgrade attack 的威胁,所以现代浏览器都预置了 HSTS 列表。

1.2 DNS 查询

  • 浏览器检查域名是否在缓存当中(要查看 Chrome 当中的缓存, 打开 chrome://net-internals/#dns)。
  • 如果缓存中没有,就去调用 gethostbyname 库函数(操作系统不同函数也不同)进行查询。
  • gethostbyname 函数在进行DNS解析之前,先检查域名是否在本地 Hosts
  • 如果 gethostbyname 没有这个域名的缓存记录,也没有在 hosts 里找到,它将会向 DNS 服务器发送一条 DNS 查询请求。
  • DNS 服务器是由网络通信栈提供的,通常是本地路由器或者 ISP 的缓存 DNS 服务器。
  • 查询本地 DNS 服务器时,如果 DNS 服务器主机在同一子网内,系统会对 DNS 服务器进行 ARP查询;如果在不同的子网,系统会按照下面的 ARP 过程对默认网关进行查询
  • 从客户端到本地DNS服务器是属于递归查询,而本地DNS服务器与根域名服务器,顶级域名服务器,权威服务器之间是迭代查询

1.3 TCP三次握手

  • Chrome 在同一个域名下要求同时最多只能有 6TCP 连接,超过 6 个的话剩下的请求就得等待。
  • 客户端选择一个初始序列号(ISN),将设置了 SYN 位的封包发送给服务器端,表明自己要建立连接并设置了初始序列号
  • 服务器端接收到 SYN 包,如果它可以建立连接:
    • 服务器端选择它自己的初始序列号
    • 服务器端设置 SYN 位,表明自己选择了一个初始序列号
    • 服务器端把 (客户端ISN + 1) 复制到 ACK 域,并且设置 ACK 位,表明自己接收到了客户端的第一个封包
  • 客户端通过发送下面一个封包来确认这次连接:
    • 自己的序列号+1
    • 接收端 ACK+1
    • 设置 ACK
  • 数据通过下面的方式传输:
    • 当一方发送了NBytes 的数据之后,将自己的 SEQ 序列号也增加N
    • 另一方确认接收到这个数据包(或者一系列数据包)之后,它发送一个 ACK 包,ACK 的值设置为接收到的数据包的最后一个序列号
  • 关闭连接时:
    • 要关闭连接的一方发送一个 FIN
    • 另一方确认这个 FIN 包,并且发送自己的 FIN
    • 要关闭的一方使用 ACK 包来确认接收到了 FIN

1.4 发送HTTP请求

  • 浏览器发 HTTP 请求要携带三样东西:请求行、请求头和请求体
  • 请求头,比如Cache-ControlIf-Modified-SinceIf-None-Match都由可能被放入请求头中作为缓存的标识信息。
  • 请求体只有在POST方法下存在,常见的场景是表单提交。

2. 服务器端处理

服务端收到请求后经过处理后,返回响应。跟请求部分类似,网络响应具有三个部分:响应行、响应头和响应体。

  • 响应行由HTTP协议版本、状态码和状态描述组成。
    HTTP/1.1 200 OK
  • 响应头包含了服务器及其返回数据的一些信息, 服务器生成数据的时间、返回的数据类型以及对即将写入的Cookie信息。
  • 响应后是否断开,要判断Connection字段, 如果请求头或响应头中包含Connection: Keep-Alive,表示建立了持久连接,这样TCP连接会一直保持,之后请求统一站点的资源会复用这个连接。否则断开TCP连接, 请求-响应流程结束。

3. 浏览器解析和渲染

完成了网络请求和响应,如果响应头中Content-Type的值是text/html,那么接下来就是浏览器的解析和渲染工作了。

WebKit内核工作流程

Gecko内核工作流程

由于Webkit占据了大量的市场份额,所以这里以Webkit为例,介绍浏览器的解析和渲染过程。

3.1 解析

  • 构建 DOM
    • 解析树是以 DOM 元素以及属性为节点的树。
    • 整个 DOMHTML 文档几乎是一对一的关系。
    • DOM 树的构建过程是一个深度遍历过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。
    • 解析算法在 HTML5 标准规范中有详细介绍,算法主要包含了两个阶段:标记化(tokenization)和树的构建。也就是就是词法分析和语法分析。
    • 标记化:输入为HTML文本,输出为HTML标记,也成为标记生成器。其中运用有限自动状态机来完成。即在当当前状态下,接收一个或多个字符,就会更新到下一个状态。
    • 建树:解析器首先会创建一个document对象。标记生成器会把每个标记的信息发送给建树器。建树器接收到相应的标记时,会创建对应的 DOM 对象。先将DOM对象加入 DOM 树中,再将对应标记压入存放开放(与闭合标签意思对应)元素的栈中。
  • 样式计算
    • 根据 CSS词法和句法分析三种来源:CSS文件; <style> 标签中的样式; style 属性的值
    • CSS解析器可能是自顶向下的,也可能是使用解析器生成器生成的自底向上的解析器
    • 格式化样式表:每个CSS文件都被解析成一个样式表对象StyleSheet object,这个对象包含带有选择器的CSS规则,和对应CSS语法的对象
    • 标准化样式属性:有一些 CSS 样式的数值并不容易被渲染引擎所理解,因此需要在计算样式之前将它们标准化
    • 计算每个节点的具体样式:继承和层叠。每个子节点都会默认继承父节点的样式属性,如果父节点中没有找到,就会采用浏览器默认样式,也叫UserAgent样式。这就是继承规则。然后是层叠规则,CSS 最大的特点在于它的层叠性,也就是最终的样式取决于各个属性共同作用的效果,甚至有很多诡异的层叠现象,看过《CSS世界》的同学应该对此深有体会,具体的层叠规则属于深入 CSS 语言的范畴,这里就不过多介绍了。
  • 生成渲染树
    • 通过浏览器的布局系统确定元素的位置
    • 遍历生成的 DOM 树节点,并把他们添加到渲染树中。
    • 计算渲染树节点的坐标位置。

3.2 渲染

  • 建立图层树
    • 浏览器在构建完渲染树之后,还会对特定的节点进行分层,构建一棵图层树
    • 节点的图层会默认属于父亲节点的图层(这些图层也称为合成层)。
    • 什么时候会提升为一个单独的合成层:显式合成;隐式合成
    • 显示合成:拥有层叠上下文的节点;需要剪裁的地方
    • 隐式合成:层叠等级低的节点被提升为单独的图层之后,那么所有层叠等级比它高的节点都会成为一个单独的图层。
  • 生成绘制列表
    • 渲染引擎会将图层的绘制拆分成一个个绘制指令,比如先画背景、再描绘边框……然后将这些指令按顺序组合成一个待绘制列表,相当于给后面的绘制操作进行计划。
  • 生成图块并栅格化
    • 在渲染进程中绘制操作是由专门的合成线程来完成。
    • 绘制列表准备好了之后,渲染进程的主线程会给合成线程发送commit消息,把绘制列表提交给合成线程。
    • 合成线程要做的第一件事情就是将图层分块。
    • 渲染进程中专门维护了一个栅格化线程池,专门负责把图块转换为位图数据。然后合成线程会选择视口附近的图块,把它交给栅格化线程池生成位图。
    • 生成位图的过程实际上都会使用 GPU 进行加速,生成的位图最后发送给合成线程。
  • 显示器显示内容
    • 栅格化操作完成后,合成线程会生成一个绘制命令,即"DrawQuad",并发送给浏览器进程。
    • 浏览器进程中的viz组件接收到这个命令,根据这个命令,把页面内容绘制到内存,也就是生成了页面,然后把这部分内存发送给显卡。
    • 显示器显示图像的原理:无论是 PC 显示器还是手机屏幕,都有一个固定的刷新频率,一般是 60 HZ,即 60 帧,也就是一秒更新 60 张图片,一张图片停留的时间约为 16.7 ms。而每次更新的图片都来自显卡的前缓冲区。而显卡接收到浏览器进程传来的页面后,会合成相应的图像,并将图像保存到后缓冲区,然后系统自动将前缓冲区和后缓冲区对换位置,如此循环更新。

参考