阅读 146
浏览器工作原理(上)

浏览器工作原理(上)

前言

此文非原创,多为资料整理
正在持续更新中...

浏览器基础知识

chrome架构

  • 目前的浏览器的进程架构

如上图: 目前的浏览器的进程架构,对应:浏览器(Browser)主进程、GPU 进程、网络(NetWork)进程、渲染进程、插件进程。

浏览器进程:负责界面的展示、用户的交互、子进程的管理、还有存储等功能。

渲染进程:将html、css、js转换为用户眼睛所见的页面。排版引擎Blink和V8都在该进程中运行。一般情况下,浏览器都会为每个tab页创建一个渲染进程。

GPU进程:GPU负责图像运算工作,具有高并行能力,通过计算将图像显示在屏幕像素中。工作原理简单来说就是将 “3D坐标” 转换成 “2D坐标” ,再将 “2D坐标” 转换为 “实际有颜色的像素”。

网络进程:网络资源加载。

插件进程:负责插件的运行,但是插件容易崩溃可能会对浏览器和页面造成负面影响,所以通过插件进程来隔离。

  • 所以打开一个页面,至少会产生4种进程

如上图:
对应 浏览器主进程、网络进程、GPU进程、render进程。

但是进程一多就会面临几个问题:
(1) 占用资源变高:每个进程都会包含公共基础结构的副本(如 JavaScript 运行环境),这就意味着浏览器会消耗更多的内存资源。
(2) 架构体系复杂:浏览器各模块之间耦合性高、扩展性差。

  • 关于单个页面卡死最终崩溃导致所有页面崩溃的情况

(1) 一般情况下,一个页面使用一个进程。但当处于同一站点的时候,chrome会执行默认策略。即:从一个页面打开了新页面,新页面和当前页面属于同一站点,那么新页面会复用父页面的渲染进程。所以说当一个页面崩溃了,会导致同一站点的页面同时崩溃,因为他们使用了同一个渲染进程。

(2) 什么是同一站点?
aaa.kisure.org
bbb.kisure.org
ccc.kisure.org:8080
如上3个url地址,协议(https)相同,域名地址(kisure.org)相同,那么就称为同一站点。

(3) 为什么要让他们跑在一个进程里面呢?
因为是同一站点,js执行环境共享。

进程和线程区别

多线程可以执行多个任务。而进程是用来管理的。

TCP协议

  • FP

FP也叫首屏绘制时长,它是衡量web性能的重要指标。影响FP的其中一个因素是:网络加载速度。

  • 数据的传输过程

(1) 将数据发送到目标主机上(借助 IP地址)
计算机的地址就是IP地址,访问网站,其实就是从你的计算机访问另一台计算机的信息。
所以如果想向主机A发送信息,就需要在数据附加上主机A的IP地址信息等,这样才能准确的送达。当然,附加的信息是存放在IP头的数据结构中,IP头包含:IP 版本、源 IP 地址、目标 IP 地址、生存时间等信息。

(2) 将数据送达到指定的应用程序(借助 UDP)
IP只是将数据送达到对方主机,但是不能确定对应的应用程序。所以需要借助UDP将数据发送给正确的应用程序,UDP也叫“用户数据包协议”,它有个重要的信息叫端口号,端口号是一个数字分别对应相应的网络程序。
当然UDP是不能保证数据的完整性,但是传输速度非常快。所以比较适合应用于:直播、互动游戏这一类场景。这些场景对数据的完整性要求不高。

(3) 将数据完整送达指定应用程序(借助 TCP)
TCP 也就是传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。它有2个特点:

  • TCP 提供重传机制,如果数据包丢失,TCP 会重传数据;
  • TCP 引入了数据包排序机制,保证把乱序的数据包组合成一个完整的文件。

如上图是一个TCP连接过程:
第一步:建立连接,通过“三次握手”来建立客户端和服务器之间的连接。

第二步:传输数据,接收端在接收到数据包之后,需要发送确认数据包给发送端,发送端发送了一个数据包之后,在规定时间内没有接收到接收端反馈的确认消息,则判断为数据包丢失,并触发发送端的重发机制。同样,一个大的文件在传输过程中会被拆分成很多小的数据包,这些数据包到达接收端后,接收端会按照 TCP 头中的序号为其排序,从而保证组成完整的数据。

第三步:断开连接,数据传输完毕之后,通过四次挥手,终止连接。

HTTP请求流程

  • 浏览器发起请求

输入一个URL,例如:www.baidu.com/,会发生如下事情:

第一步:浏览器构建请求行信息,然后准备发起请求。

第二步:查询缓存,在发起请求前会先在浏览器的缓存中查询是否存在请求的文件,如果存在,会直接返回缓存的文件,并结束请求(这种机制,是为了缓解服务端压力,提升性能)。如果缓存查找失败,就会发起网络请求。

第三步:如上图 http网络请求。建立和服务器连接的TCP,而建立TCP所需要的IP和端口信息来自于URL网址。所以,浏览器的第一步会请求DNS返回域名对应的IP信息,借着就是获取端口号。通常情况下,如果url没有指定端口号,那么http默认就是80端口,https默认端口443。

  • 什么是DNS

DNS也叫域名系统,用于映射IP的。因为IP地址比较难记,像百度的IP为 202.108.22.5,比较难记的,所以我们用域名 www.baidu.com 这样会好记很多。为了能将域名转化成IP,于是就需要DNS去做映射。

  • DNS缓存

浏览器会对之前访问过得域名解析出来数据进行缓存,下一次访问相同的域名时,就会直接使用缓存数据。这样浏览器就能减少了一次网络请求。

  • TCP队列

chrome存在一个机制,同一个域名同时最多建立 6 个 TCP 连接,如果有 10 个请求,那么其中 4 个请求会进入排队等待状态,直至进行中的请求完成。如果前请求少于 6个,则直接建立 TCP 连接。

  • http请求

浏览器向服务器发送请求行,请求行包含了:请求的方法、请求的url、http协议版本。发送请求行是为了告诉服务器客户端浏览器需要什么资源。
在浏览器发送请求行命令之后,还要以请求头形式发送其他一些信息,把浏览器的一些基础信息告诉服务器。比如包含了浏览器所使用的操作系统、浏览器内核等信息,以及当前请求的域名信息、浏览器端的 Cookie 信息,等等。

  • 服务端处理HTTP请求

服务器会返回响应行,包括协议版本和状态码。其中状态码用来告诉浏览器处理的结果。例如 200 代表处理成功。

服务器也会像浏览器随时发送请求行一样,服务器也会随时响应向浏览器发送响应头。响应头包含了服务器自身一些信息:服务器生成返回数据的时间、返回的数据类型(JSON、HTML、流媒体等类型),以及服务器要在客户端保存的 Cookie 等信息。

发送完响应头后,服务器就可以继续发送响应体的数据,通常,响应体就包含了 HTML 的实际内容。

  • 头信息 Connection:Keep-Alive

一般情况下,服务端向客户端返回数据以后,就应该关闭TCP连接了。但是如果浏览器或者服务器在头信息中加入了 Connection:Keep-Alive 那么TCP任然保持连接,于是浏览器就可以继续通过同一个 TCP 连接发送请求。这样做能是为了省去下次重新建立连接的时间,提高资源加载速度。

  • 为什么有些网站二次打开速度会很快?

因为首次加载过程中,缓存了相关的数据。主要包含了:DNS缓存、页面资源缓存。
(1) DNS缓存:浏览器本地把对应的 IP 和域名关联起来。
(2) 页面资源缓存:
服务器返回 HTTP 响应头给浏览器时,浏览器通过响应头中的 Cache-Control 字段来设置是否缓存该资源。Cache-Control:Max-age=2000 代表的是缓存过期时间是 2000 秒。也就是说,如果时间没有过期,再次请求该资源,会直接返回缓存中的资源给浏览器。如果时间过期,浏览器会再次发起请求。并且会将 If-None-Match 字段带上。服务端收到请求以后,会根据 If-None-Match 来判断请求的资源是否有更新。如果资源没有更新,则返回304,如果更新,则将新的资源返回给浏览器。

  • 登录状态的保持

服务端接收到客户端发来的登录信息,会验证用户登录信息是否正确。如果正确,则生成一段表示用户身份的字符串。并把该字符串写到响应头的 Set-Cookie 字段里,然后把响应头发送给浏览器。
浏览器在接收到响应头后进行解析,如果含有 Set-Cookie 字段,则会将字段信息保存到本地。
当用户再次访问时候,浏览器会在发起 HTTP 请求前,读取之前保存的cookie,并把数据写进请求头里的 Cookie 字段。然后发送给服务端。例如:Cookie: UserID=36437634348734。
服务端在接收到请求头以后,会查找cookie字段信息,找到对应的值信息(UserID=36437634348734),然后查询后台,是否处于登录状态,然后在生成对应用户登录状态的信息数据返回给客户端。

总结

输入url到生成页面,中间发生了什么

如上图是:
这是从输入 URL 到页面展示完整流程示意图

  • 用户输入

(1) 用户在地址栏中输入查询关键字,地址栏会判断输入的关键字是搜索内容,还是请求的 URL。

  • 如果是搜索内容,地址栏会使用浏览器默认的搜索引擎,来合成新的带搜索关键字的 URL。
  • 如果是符合 URL 规则,则地址栏会根据规则,把这段内容加上协议,合成为完整的 URL。

(2) 当输入内容并且回车以后,在当前窗口生成新页面之前,浏览器会给当前页执行 beforeunload 的事件。beforeunload 的作用就是允许当前页在退出之前执行一些数据清理,也可以询问用户是否离开当前页。例如:你填写了表单数据,没有提交,误操作要离开当前页时,通过 beforeunload 来取消离开,这样浏览器就不会再执行任何后续操作。

  • url请求

此时处于页面资源请求过程。浏览器进程会通过进程间通信(IPC)把 URL 请求发送至网络进程,网络进程接收到 URL 请求后,会在这里发起真正的 URL 请求流程。

具体流程如下:
第一步:网络进程查找本地缓存是否缓存该资源,若有,则直接返回资源给浏览器进程。若无,则进入网络请求流程。

第二步:在请求前,会DNS解析,获取请求域名服务器的IP地址。如果协议为 HTTPS 则需要建立TLS连接。

第三步:利用IP地址和服务器建立 TCP 连接。连接成功以后,浏览器端构建请求行、请求头等信息,并把和该域名相关的 Cookie 等数据附加到请求头中,然后向服务器发送构建的请求信息。

第四步:服务器接收到请求信息,生成对应的响应数据(包括响应行、响应头和响应体等信息)。再发给网络进程。等网络进程接收了响应行和响应头之后,就开始解析响应头的内容了。

第五步:网络进程接收到响应信息后,进行解析。如果状态码对应 301、302 就说服务器想让浏览器重定向到其他url。此时网络进程向响应头 Location 字段读取重定向地址,再发新的 HTTP/HTTPS 请求。
如果返回的是200,则浏览器继续处理请求。

第六步:对响应数据进行处理
问:url请求的数据类型可能是下载类型、HTML页面,浏览器是如何区分的?
答:响应头 Content-Type 字段,能告诉浏览器响应体数据类型。
注意,如果服务器返回的 Content-Type 不正确,那么浏览器则会曲解文件内容。如将 text/html 类型配置成 application/octet-stream 类型,本来是用来展示的页面,变成了一个下载文件。

  • 准备渲染进程

Chrome 默认策略:每个标签对应一个渲染进程。但若从一个页面打开了另一个新页面,新页面和当前页面属同一站点,则新页面会复用父页面的渲染进程。
渲染进程准备好之后,还不能立即进入文档解析状态。此时文档数据还在网络进程中,未提交给渲染进程,所以下一步就进入了提交文档阶段。

  • 提交文档

提交文档:指浏览器进程将网络进程接收到的 HTML 数据提交给渲染进程。

具体步骤如下:
第一步:向渲染进程发起"提交文档"的消息。

第二步:渲染进程接收消息,并和网络进程建立传输数据的管道。

第三步:数据传输完成,渲染进程向浏览器进程发送"确认提交"的信号

第四步:浏览器进程在接收到"确认提交"信号后,会更新浏览器界面状态,包括了安全状态、地址栏的 URL、前进后退的历史状态,并更新 Web 页面。
这也解释了为什么在浏览器的地址栏中输入一个地址后,之前的页面没有立马消失,而是要加载一会儿才会更新页面。

  • 渲染页面

一旦文档被提交,渲染进程便开始页面解析和子资源加载了。页面生成完成,渲染进程会发送消息给浏览器进程,浏览器接收到消息后,会停止标签图标上的加载动画。而具体渲染下一节会讲到。

浏览器渲染

渲染步骤:构建 DOM 树、样式计算、布局、图层、绘制、栅格化和合成与显示。

  • 构建 DOM 树

问:为什么要构建 DOM 树呢?
答:浏览器无法直接理解HTML,要将 HTML 转换为浏览器能理解的结构——DOM 树。

如上图,是构建过程。




如上图,我们在控制台输入 document ,发现DOM 和 HTML 内容差不多,区别在于:DOM 是保存在内存中树状结构,可以通过 js 来查询或修改其内容。

  • 样式计算

这一步,目的是为了计算出 DOM 节点中每个元素的具体样式。下面分三阶段讲述:

第一阶段:将css转化为浏览器能理解的结构,即 styleSheets。

如上图,我们在控制台输入 document.styleSheets,这个就是styleSheets结构。

第二阶段:转换样式表中的属性值,使其标准化。所谓标准即:

/* 加载进来的样式文件 */
div {
  font-size: 2em;
  color: red;  
}

/* 转换为渲染引擎容易理解的、标准化的计算值 */ 
div {
  font-size: 32px;
  color: rgb(255,0,0)
}
复制代码

第三阶段:计算DOM 树中每个节点的具体样式
此阶段涉及CSS 的继承规则和层叠规则。关于CSS继承即,每个DOM节点都包含有父节点的样式。 关于层叠规则即,合并来自多个来源的属性值

  • 样式计算 -> 第三阶段 -> CSS继承
body { font-size: 20px }
p {color:blue;}
span  {display: none}
div {font-weight: bold;color:red}
div  p {color:green;}
复制代码

如上图和代码:
body节点字体为20px,那么子节点font-size值都为20px,这就是CSS继承。

如上图:红框处"inherited from"代表的就是继承样式来源。

  • 样式计算 个人总结

我们以前了解的CSSOM,是个很老旧的概念。目前浏览器是没有CSSOM概念的,或者你可以把CSSOM理解成我上文提到的 styleSheets。同时渲染树也是16年之前的东西,现代的浏览器因为内部实现重构了,所以可以把LayoutTree看出渲染树。但是这个渲染树和16年前的渲染树存在差别。

  • 布局

有时候我们发现一些不可见的元素,如设置了display:none属性,浏览器在展示前,会额外的构建一棵只包含可见元素的布局树。
构建布局树,浏览器会做此操作:遍历 DOM 树中的所有可见节点,并把这些节点加到布局树中,并且将不可见的节点忽略掉。

  • 如果下载 CSS 文件阻塞了,会阻塞 DOM 树的合成吗?会阻塞页面的显示吗?

答:JS和CSS都有可能会阻塞DOM解析,有如下3种情况

第一种:服务端接收到HTML页面的第一批数据时,DOM解析器就开始工作了,在解析过程中,如果遇到JS脚本,那么DOM解析器会先执行JS脚本,执行完成以后,再继续往下解析

<!-- 针对于第一种情况 -->
<html>
    <body>
        极客时间
        <script>
        document.write("--foo")
        </script>
    </body>
</html>
复制代码

第二种:内联的脚本替换成js外部文件,当解析到js时候,会暂停DOM解析,并且下载js文件,下载完成之后执行该段JS文件,然后再继续往下解析DOM。这就是JavaScript文件为什么会阻塞DOM渲染。

<!-- 针对于第二种情况 -->
<html>
    <body>
        <script type="text/javascript" src="nicefish.js"></script>
    </body>
</html>
复制代码

第三种:在js中访问某个元素的样式,这时候就需要等待这个样式被下载完成才能继续往下执行,所以在这种情况下,CSS也会阻塞DOM的解析。

<!-- 针对于第三种情况 -->
<html>
    <head>
        <style type="text/css" src = "nicefish.css" />
    </head>
    <body>
        <p>nicefish</p>
        <script>
            let e = document.getElementsByTagName('p')[0]
            e.style.color = 'blue'
        </script>
    </body>
</html>
复制代码
  • 分层

页面中存在复杂的效果,如:3D变换、页面滚动、使用了z-indexing做Z轴排序等。所以渲染引擎还需要为特定的节点生成专用的图层,同时生成一颗对应的图层树。
并不是布局树所有的节点都有对应的图层,如果一个节点没有对应图层,那么这个节点就属于父节点图层。

生成图层需要的条件(满足任意一个即可):
第一点:含有属性 position:fixed/absolute(明确定位属性)、z-index、opacity(透明属性)、filter(滤镜元素)。这些都具有图层
第二点:需要剪裁的地方,都会创建图层。

// 如上第二点,div写死宽高为200px,当内容较多时,渲染引擎就会产生剪裁,将内容的一部分显示出来
<style>
      div {
            width: 200px;
            height: 200px;
            overflow:auto;
            background: gray;
        } 
</style>
复制代码
  • 图层绘制

完成图层树的构建以后,渲染引擎会对图层树中的每个图层进行绘制。
渲染引擎会把图层绘制拆分成多个小的绘制指令,再将这些指令按照顺序组成待绘制的列表。

当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程。

  • 栅格化

绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的。

如上图所示的,为渲染主线程和合成线程之间的关系。当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程。

所谓的视口就是:用户肉眼能看到的页面就是视口。有些页面很长,如果要绘制所有的图层,开销会很大,所以合成线程会将图层划分为图块,然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。

所谓栅格化,是指将图块转换为位图。

而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的。


栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。

  • 合成与显示

所有图块都被光栅化,合成线程就会生成一个绘制图块的命令。再将命令提交给浏览器进程,然后浏览器进程更具命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上

  • 渲染流程总结

渲染流程分为如下阶段:DOM 生成、样式计算、布局、图层、绘制、栅格化、合成与显示。 渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。
渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。
创建布局树,并计算元素的布局信息。对布局树进行分层,并生成分层树。
为每个图层生成绘制列表,并将其提交到合成线程。
合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
合成线程发送绘制图块命令 DrawQuad 给浏览器进程。
浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。

文章分类
前端
文章标签