1.宏观视角下的浏览器

本篇是这个专栏的第一章:《宏观视角下的浏览器》。本章分为六讲。

01.Chrome架构:仅仅打开一个页面,为什么有四个进程

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

线程 VS 进程

线程这个东西它是不能单独存在的,它是由进程来启动和管理的。 一个进程就是一个程序的运行实例

线程是依附于进程的,而进程中使用多线程并行处理能提升运算效率

总结,线程与进程之间的关系有以下四个特点: 1. 进程中任一线程执行出错,都会导致整个进程崩溃。 2. 线程之间共享进程的数据。 3. 当一个进程关闭之后,操作系统会回收进程所占用的内存。 4. 进程之间的内容都互相隔离。(如果进程之间需要进行数据的通信,这时候需要使用用于进程间通讯(IPC)机制了)。

单进程浏览器时代

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

多进程浏览器时代

  • 多进程浏览器由于进程相互隔离,所以当一个页面或者插件崩溃的时候,影响的仅仅是当前的页面进程或者插件过程。这就完美解决了页面或者插件崩溃而导致的整个浏览器崩溃问题。JS的渲染若出现问题,影响的同样是当前的渲染页面,没有响应的仅对当前页面。而且在多进程浏览器时代,当关闭一个页面的时候,整个渲染进程会被关闭,该进程占用的内存都会被系统回收,这样也就轻松解决了浏览器页面的内存泄露问题。

  • 此外有关安全方面的问题:使用多线程架构可以使用安全沙箱

可以把沙箱看成是操作系统给进程上了一把锁,沙箱里面的程序可以运行,但是不能在硬盘上写入任何数据,也不能在敏感位置读取任何数据(例如文档和桌面),Chrome把插件进程和渲染进程锁在沙箱里面,这样即使在渲染进程或者插件进程里面执行了恶意程序,恶意程序也无法突破沙箱去获取系统权限。

目前多进程架构

最新的Chrome进程架构:Chrome浏览器包括:1个浏览器(Brower)主进程,1个GPU进程、一个网络(NetWork)进程、多个渲染进程和多个插件进程。 虽然多进程模型提升了浏览器的稳定性、流畅性、安全性,但是同样也会不可避免带来一些问题:

  • 更高的资源占用 :因为每个进程都会包含公共基础结构的副本(如JavaScript运行环境),这意味着浏览器会消耗更多的资源。

  • 更复杂的体系架构:浏览器各模块之间耦合性高、扩展性差等问题,会导致现在的架构已经很难适应新的需求。

未来面向服务的架构(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. 对于数据包丢失情况,提供重传机制。

  5. TCP引入数据包排序机制,用来保证把乱序的数据包组合成一个完整的文件。

    完整的TCP连接过程

    我们现在已经知道TCP单个数据包的传输流程和UDP流程差不多,不同在于,通过TCP头信息可以保证一块大的数据传输的完整性。

    一个完整的TCP连接过程,其生命周期包括了“建立连接“、”数据传输“、”断开连接“三个阶段。

  6. 建立连接阶段:这个阶段通过“三次握手”来建立客户端和服务器之间的连接。

  7. 数据传输阶段:在该阶段,接收端需要对每个数据包进行确认操作。接收端应该在接收数据后要发送确认数据包给发送端,若发送端没有接收到这个确认,则判断数据包丢失,并触发发送端的重发机制。

  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连接

    建立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. 登录状态是如何保持的

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

04|导航流程:从输入URl到页面展示,这中间发生了什么

流程开始前,回顾下浏览器进程、网络进程、渲染进程的各自主要职责:

  • 浏览器进程:主要负责用户交互子进程管理文件储存等功能。

  • 网络进程:面向渲染进程或浏览器进程提供网络资源下载

  • 渲染进程:将HTML、CSS、JS、图片等资源解析为可以显示和交互的页面。

过程大致描述

  • 首先,用户从浏览器进程中输入请求信息。

  • 然后,网络进程发起URL请求。

  • 服务器响应URL请求后,浏览器进程开始准备渲染进程。

  • 渲染进程准备好以后,需要先向渲染进程提交页面数据,这称之为文档提交阶段。

  • 渲染进程接收到文档信息之后,便开始解析页面和加载子资源,完成页面的渲染。

    这其中,用户发出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步

      6.2 200响应处理:

      检查响应类型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. 转换样式表中的属性值,使其标准化。

    「例如rem -> px, red -> rgb(255,0,0),bold -> 700」

  3. 计算出DOM树中每个节点的具体样式。

    「CSS继承:每个DOM节点都包含有父节点的样式」

    「CSS层叠:它在 CSS 处于核心地位,定义了如何合并来自多个源的属性值的算法」

    「样式来源:如果一个元素不提供任何样式,默认使用的是UserAgent样式---浏览器提供的一组默认样式」。

布局阶段

内容: 布局阶段是根据DOM树和样式计算出元素的几何位置。 1. 创建布局树「构建一颗只包含可见元素的布局树」 2. 布局计算

「在执行布局操作的时候,会把布局运算的结果重新写回布局树中,所以布局树既是输入内容也是输出内容,这是布局阶段一个不合理的地方,因为在布局阶段并没有清晰地将输入内容和输出内容区分开来。针对这个问题,Chrome 团队正在重构布局代码,下一代布局系统叫 LayoutNG,试图更清晰地分离输入和输出,从而让新设计的布局算法更加简单。」

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

分层

为了生成一些复杂效果(3D变换、页面滚动、z轴排序等),渲染引擎还需要为特定的节点生成专用的图层,生成一颗对应的图层树.

  • 并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层.

  • 素有了层叠上下文的属性或者需要被剪裁,满足这任意一点,就会被提升成为单独一层。

绘制

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

栅格化操作

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

合成和显示

一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。经过浏览器就会显示出页面。

一个完整的渲染流程大致可总结为如下:

  • 渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。

  • 渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。

  • 创建布局树,并计算元素的布局信息。对布局树进行分层,并生成分层树。

  • 为每个图层生成绘制列表,并将其提交到合成线程。

  • 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。

  • 合成线程发送绘制图块命令 DrawQuad 给浏览器进程。浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。

相关概念

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

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

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

比如改变了元素的背景色,这会触发浏览器进行重绘之后的操作。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。

直接合成阶段

那如果你更改一个既不要布局也不要绘制的属性,会发生什么变化呢?渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做合成. 例如CSS的transform实现的动画效果,可以避开重排和重绘阶段,相对于重绘和重排,合成能大大提升绘制效率。

Last updated