# 1.宏观视角下的浏览器

## 01.Chrome架构：仅仅打开一个页面，为什么有四个进程

> 在谷歌浏览器随便打开一个页面，点击“选项”菜单，选择“更多工具”，点击“任务管理器”，这将打开Chrome的任务管理器的窗口，你会发现一个页面上有四个以上的进程。为什么会有四个进程呢？ 本章通过分析浏览器的进化史而展开探讨这个问题。\
> 开始之前，我们必须得了解一下这个`进程`与`线程`的概念。

### 线程 VS 进程

> `线程这个东西它是不能单独存在的，它是由进程来启动和管理的。`\
> `一个进程就是一个程序的运行实例`。
>
> `线程是依附于进程的，而进程中使用多线程并行处理能提升运算效率`。
>
> 总结，线程与进程之间的关系有以下四个特点： 1. 进程中任一线程执行出错，都会导致整个进程崩溃。 2. 线程之间共享进程的数据。\
> 3\. 当一个进程关闭之后，操作系统会回收进程所占用的内存。\
> 4\. 进程之间的内容都互相隔离。(如果进程之间需要进行数据的通信，这时候需要使用用于进程间通讯(IPC)机制了)。

### 单进程浏览器时代

> 早在07年之前，市面上的浏览器都是单进程的。单进程，顾名思义是指：浏览器的所有功能模块都运行在同一个进程里。\
> 如此多的功能模块运行在一个进程里，导致单进程浏览器`不稳定`、`不流畅`、`不安全`。\
> 具体的表现就不说了，总之体验非常差，于是进入了“多线程浏览器”时代。

### 多进程浏览器时代

> * 多进程浏览器由于进程相互隔离，所以当一个页面或者插件崩溃的时候，影响的仅仅是当前的页面进程或者插件过程。这就完美解决了页面或者插件崩溃而导致的整个浏览器崩溃问题。JS的渲染若出现问题，影响的同样是当前的渲染页面，没有响应的仅对当前页面。而且在多进程浏览器时代，当关闭一个页面的时候，整个渲染进程会被关闭，该进程占用的内存都会被系统回收，这样也就轻松解决了浏览器页面的内存泄露问题。   &#x20;
> * 此外有关安全方面的问题：使用多线程架构可以使用`安全沙箱`。 &#x20;
>
> 可以把沙箱看成是操作系统给进程上了一把锁，沙箱里面的程序可以运行，但是不能在硬盘上写入任何数据，也不能在敏感位置读取任何数据(例如文档和桌面)，Chrome把插件进程和渲染进程锁在沙箱里面，这样即使在渲染进程或者插件进程里面执行了恶意程序，恶意程序也无法突破沙箱去获取系统权限。

### 目前多进程架构

> 最新的Chrome进程架构：Chrome浏览器包括：`1个浏览器(Brower)主进程，1个GPU进程、一个网络(NetWork)进程、多个渲染进程和多个插件进程`。\
> 虽然多进程模型提升了浏览器的稳定性、流畅性、安全性，但是同样也会不可避免带来一些问题：
>
> * `更高的资源占用` ：因为每个进程都会包含公共基础结构的副本（如JavaScript运行环境），这意味着浏览器会消耗更多的资源。 &#x20;
> * `更复杂的体系架构`：浏览器各模块之间耦合性高、扩展性差等问题，会导致现在的架构已经很难适应新的需求。

### 未来面向服务的架构(SOP)

> 为了解决这些问题，2016年，Chrome团队使用“面向服务的架构“（SOP）思想设计了新的Chrome架构，这也是现阶段Chrome团队的一个主要任务。

## 02｜TCP协议：如何保证页面文件能被完整的送达浏览器

> 在衡量Web页面性能的时候有一个重要的指标叫"FP(First Paint)",是指从页面加载到首次开始绘制的时长。其中影响FP的一个重要因素就是`网络加载速度`。
>
> 要优化网络加载速度，需要对网络有充分的了解，这一节重点介绍`在Web世界中的TCP/IP是如何工作的`。
>
> 在网络中，一个文件通常会被拆分为很多数据包来进行传输，而数据包在传输过程中有很大概览丢失或者出错，那么如何保证页面文件能被完整地送达浏览器？
>
> ### 一个数据包的“旅程”
>
> 1. IP:把数据包送达目的主机\
>    计算机的地址称为IP地址，访问任何网站实际上只是你的计算机向另外一台计算机请求信息。\
>    当从主机A向主机B发送数据(即发送数据包)，传输前，数据包会被附加上主机A和主机B的IP地址信息，这些信息会被封到一个叫做IP头的数据结构里，在这个IP头中包含IP数据包开头的信息(IP版本、源IP地址、目标地址、生存时间等信息)，于是数据包从主机A发送到主机B。
> 2. UDP:把数据包送达应用程序 IP通过IP地址信息把数据包发送给指定的电脑，而UDP通过端口号把数据包发给正确的程序。 UDP发送数据，有各种因素会导致数据包出错，虽然UDP可以校验数据是否正确，但是`UDP不提供重发机制`，只是丢弃当前的包，且UDP在发送之后无法知道能否到达目的地。\
>    `UDP不能保证数据可靠性，但是传输速度却非常快`，因此UDP应用在一些关注速度但不那么严格要求数据完整性的领域，例如：在线视频、互动游戏等。
> 3. TCP:把数据完整的送达应用程序 TCP(Transmission Control Protocol,传输控制协议)：他是一种面向连接的、可靠的、基于字节流的传输层通信协议。相对于UDP而言：
> 4. 对于数据包丢失情况，提供重传机制。 &#x20;
> 5. TCP引入数据包排序机制，用来保证把乱序的数据包组合成一个完整的文件。&#x20;
>
>    **完整的TCP连接过程**
>
>    我们现在已经知道TCP单个数据包的传输流程和UDP流程差不多，不同在于，通过TCP头信息可以保证一块大的数据传输的完整性。&#x20;
>
>    一个完整的TCP连接过程，其生命周期包括了“建立连接“、”数据传输“、”断开连接“三个阶段。
> 6. 建立连接阶段：这个阶段通过“三次握手”来建立客户端和服务器之间的连接。 &#x20;
> 7. 数据传输阶段：在该阶段，接收端需要对每个数据包进行确认操作。接收端应该在接收数据后要发送确认数据包给发送端，若发送端没有接收到这个确认，则判断数据包丢失，并触发发送端的重发机制。 &#x20;
> 8. 断开连接阶段：数据传输完毕，终止连接，通过最后一个阶段“四次挥手”来保证双方都能断开连接。

## 03｜HTTP请求流程：为什么很多站点第二次打开速度会很快？

> 首先的首先我们知道：HTTP协议建立在TCP连接基础之上的。`HTTP是一种允许浏览器向服务器获取资源的协议，是Web的基础。HTTP是浏览器使用最广的协议`。
>
> 简单说说HTTP和TCP的关系：浏览器使用HTTP协议作为`应用层协议`，用来封装请求的文本信息，并使用TCP/IP作为`传输层协议`将它发到网路上,所以HTTP工作前，需要通过TCP与服务器建立连接，也就是说：`HTTP的内容是通过TCP的传输数据阶段来实现的`。

### 浏览器端发起HTTP请求流程

> 如果在浏览器地址栏里输入：<https://www.liugezhou.online> 这个网址后,浏览器这个庞然大物，它的背后都做些什么呢？

* 构建请求

  > 首先浏览器构建请求行信息，构建好之后，浏览器准备发起网路请求。
* 查找缓存

  > 在准备发起网路请求阶段，浏览器偷偷的在它的缓存中查询是否有要请求的资源。\
  > 若有：拦截请求，返回资源副本，直接结束请求。\
  > 若缓存查找失败：继续下一步。
* 准备IP地址和端口号

  > 这个IP地址和端口号的获取，肯定是通过域名与其映射，即“域名系统”，也就是我们熟知的DNS。 于是，浏览器第一步会请求DNS返回域名对应的IP，如果没有特别指明端口号，则默认为80。 (浏览器提供了DNS数据缓存服务，若缓存过也就不会去请求，直接解析。从而减少一次网络请求)
* 等待TCP队列

  > 拿到IP地址与端口号后，还需要在TCP队列中排队才能建立TCP连接。\
  > 这是因为：Chrome有个机制，同一个域名同时最多只能建立6个TCP连接，若此刻同时有10个请求发生。则四个会进入TCP队列进行排队。 当然，若当前请求数量少于6个，则会直接进入下一步。
* 建立TCP连接&#x20;

  > 建立TCP连接，上一节我们已经知道，一个完整的TCP连接过程包括“建立连接”、“数据传输”、“断开连接“三个阶段。
* 发送HTTP请求

  > HTTP请求是在TCP连接的数据传输阶段工作的，这个时候浏览器向服务器发送请求行，它包括请求方法、请求URI、HTTP版本协议。，HTTP中的数据在这个通信过程中传输。

### 服务器端处理HTTP请求流程

> 这里可以在命令行中输入`curl -i https://www.google.com`来查看返回请求数据。 (-i 返回响应行、响应头和响应体信息。 -I 不返回响应体。)\
> 返回网站的HTTP协议、Connection、Location、Cache-Control等信息。
>
> 通常情况，一旦服务器向客户端返回了请求数据，它就要关闭TCP连接，但是如果浏览器或者服务器设置了`Connection:keep-alive`，那么TCP连接在发送后将仍保持打开状态。`保持TCP连接可以省去下次请求时需要建立连接的时间，提升资源加载速度`。

### 问题解答

1. 为什么很多站点第二次打开速度会很快？

   > 主要原因肯定是第一次加载页面的过程中，缓存了一些数据(从上面的过程分析，我们知道`DNS缓存`和`页面资源缓存`这两块数据是会被浏览器缓存起来的).\
   > 网站把很多资源都缓存到了本地，浏览器缓存直接使用本地副本来回应请求，而不会产生真实的网络请求，从而节省了时间。
2. 登录状态是如何保持的&#x20;

   > 简单地说，如果服务器端发送的响应头内有 Set-Cookie 的字段，那么浏览器就会将该字段的内容保持到本地。当下次客户端再往该服务器发送请求时，客户端会自动在请求头中加入 Cookie 值后再发送出去。服务器端发现客户端发送过来的 Cookie 后，会去检查究竟是从哪一个客户端发来的连接请求，然后对比服务器上的记录，最后得到该用户的状态信息。

## 04｜导航流程：从输入URl到页面展示，这中间发生了什么

> 流程开始前，回顾下浏览器进程、网络进程、渲染进程的各自主要职责：
>
> * 浏览器进程：主要负责`用户交互`、`子进程管理`和`文件储存`等功能。 &#x20;
> * 网络进程：面向渲染进程或浏览器进程提供`网络资源下载`。 &#x20;
> * 渲染进程：将HTML、CSS、JS、图片等资源解析为可以显示和交互的页面。

### 过程大致描述

> * 首先，用户从浏览器进程中输入请求信息。 &#x20;
> * 然后，网络进程发起URL请求。&#x20;
> * 服务器响应URL请求后，浏览器进程开始准备渲染进程。&#x20;
> * 渲染进程准备好以后，需要先向渲染进程提交页面数据，这称之为文档提交阶段。 &#x20;
> * 渲染进程接收到文档信息之后，便开始解析页面和加载子资源，完成页面的渲染。
>
>   这其中，用户发出URL请求到页面开始解析的过程，就叫做导航。

### 从输入URL到页面展示--过程细节

> * 响应数据类型处理：根据服务端返回的`Content-Type`字段来决定如何显示响应体的内容。
> * 同一站点下的多个页面会运行在一个渲染进程中。

### “从输入 URL 到页面展示，这中间发生了什么？”(留言总结)

> 1. 用户输入url并回车·
>    1. 浏览器进程检查url，组装协议，构成完整的url
>    2. 浏览器进程通过进程间通信（IPC）把url请求发送给网络进程
>    3. 网络进程接收到url请求后检查本地缓存是否缓存了该请求资源，如果有则将该资源返回给浏览器进程
>    4. 如果没有，网络进程向web服务器发起http请求（网络请求），请求流程如下：
>
>       5.1 进行DNS解析，获取服务器ip地址，端口
>
>       5.2 利用ip地址和服务器建立tcp连接
>
>       5.3 构建请求头信息
>
>       5.4 发送请求头信息
>
>       5.5 服务器响应后，网络进程接收响应头和响应信息，并解析响应内容
>    5. 网络进程解析响应流程；
>
>       6.1 检查状态码，如果是301/302，则需要重定向，从Location自动中读取地址，重新进行第4步 &#x20;
>
>       6.2 200响应处理：&#x20;
>
>       &#x20; 检查响应类型Content-Type，如果是字节流类型，则将该请求提交给下载管理器，该导航流程结束，不再进行后续的渲染，如果是html则通知浏览器进程准备渲染进程准备进行渲染。
>    6. 准备渲染进程
>
>       7.1 浏览器进程检查当前url是否和之前打开的渲染进程根域名是否相同，如果相同，则复用原来的进程，如果不同，则开启新的渲染进程
>    7. 传输数据、更新状态
>
>       8.1 渲染进程准备好后，浏览器向渲染进程发起“提交文档”的消息，渲染进程接收到消息和网络进程建立传输数据的“管道”
>
>       8.2 渲染进程接收完数据后，向浏览器发送“确认提交”
>
>       8.3 浏览器进程接收到确认消息后更新浏览器界面状态：安全、地址栏url、前进后退的历史状态、更新web页面。

## 05｜渲染流程(上)：HTML、CSS和JavaScript，是如何变成页面的

> 按照渲染的时间顺序，渲染流水线可分为以下几个构建阶段：\
> `构建DOM树`、`样式计算`、`布局阶段`、`分层`、`绘制`、`光栅化`、`合成`。 本节主要讨论前三个阶段。

### 构建DOM树

> 因为浏览器无法直接理解和使用 HTML，所以需要将 HTML 转换为浏览器能够理解的结构——DOM 树。 DOM树和HTML内容几乎一样，但和HTML不同的是：DOM是保存在内存中的树结构。

### 样式计算

> 1. 把CSS转换为浏览器能够理解的结构。
> 2. 转换样式表中的属性值，使其标准化。  &#x20;
>
>    「例如rem -> px, red -> rgb(255,0,0),bold -> 700」 &#x20;
> 3. 计算出DOM树中每个节点的具体样式。 &#x20;
>
>    「CSS继承：每个DOM节点都包含有父节点的样式」 &#x20;
>
>    「CSS层叠：它在 CSS 处于核心地位，定义了如何合并来自多个源的属性值的算法」 &#x20;
>
>    「样式来源：如果一个元素不提供任何样式，默认使用的是UserAgent样式---浏览器提供的一组默认样式」。

### 布局阶段

> 内容： 布局阶段是根据DOM树和样式计算出元素的几何位置。 1. 创建布局树「构建一颗只包含可见元素的布局树」\
> 2\. 布局计算
>
> 「在执行布局操作的时候，会把布局运算的结果重新写回布局树中，所以布局树既是输入内容也是输出内容，这是布局阶段一个不合理的地方，因为在布局阶段并没有清晰地将输入内容和输出内容区分开来。针对这个问题，Chrome 团队正在重构布局代码，下一代布局系统叫 `LayoutNG`，试图更清晰地分离输入和输出，从而让新设计的布局算法更加简单。」

## 06|渲染流程(下)：HTML、CSS和JavaScript，是如何变成页面的

### 分层

> 为了生成一些复杂效果(3D变换、页面滚动、z轴排序等)，渲染引擎还需要为特定的节点生成专用的图层，生成一颗对应的图层树.
>
> * 并不是布局树的每个节点都包含一个图层，如果一个节点没有对应的层，那么这个节点就从属于父节点的图层.  &#x20;
> * 素有了层叠上下文的属性或者需要被剪裁，满足这任意一点，就会被提升成为单独一层。

### 绘制

> 一个图层的绘制拆分成很多小的绘制指令，然后再把这些指令按照顺序组成一个`待绘制列表`。「可在浏览器开发者工具的Layers中查看。」

### 栅格化操作

> 所谓栅格化，是指将图块转换为位图。\
> 栅格化过程都会使用 GPU 来加速生成，使用 GPU 生成位图的过程叫快速栅格化，或者 GPU 栅格化，生成的位图被保存在 GPU 内存中.

### 合成和显示

> 一旦所有图块都被光栅化，合成线程就会生成一个绘制图块的命令——“DrawQuad”，然后将该命令提交给浏览器进程。经过浏览器就会显示出页面。
>
> 一个完整的渲染流程大致可总结为如下：
>
> * 渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。&#x20;
> * 渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets，计算出 DOM 节点的样式。 &#x20;
> * 创建布局树，并计算元素的布局信息。对布局树进行分层，并生成分层树。 &#x20;
> * 为每个图层生成绘制列表，并将其提交到合成线程。 &#x20;
> * 合成线程将图层分成图块，并在光栅化线程池中将图块转换成位图。 &#x20;
> * 合成线程发送绘制图块命令 DrawQuad 给浏览器进程。浏览器进程根据 DrawQuad 消息生成页面，并显示到显示器上。

### 相关概念

#### 重排(更新了元素的几何属性)

> 使用CSS或者JS使元素的几何位置发生了改变，例如改变元素的宽度、高度等，这会使得浏览器触发重新布局、解析之后的一系列子阶段，这个过程就是重排。无疑，重排需要更新完整的渲染流水线，所以开销是最大的。

#### 重绘(更新元素的绘制属性)

> 比如改变了元素的背景色，这会触发浏览器进行重绘之后的操作。相较于重排操作，重绘省去了布局和分层阶段，所以执行效率会比重排操作要高一些。
>
> #### 直接合成阶段
>
> 那如果你更改一个既不要布局也不要绘制的属性，会发生什么变化呢？渲染引擎将跳过布局和绘制，只执行后续的合成操作，我们把这个过程叫做合成. 例如CSS的transform实现的动画效果，可以避开重排和重绘阶段，相对于重绘和重排，合成能大大提升绘制效率。
