一、Chrome浏览器为什么设置4个进程?
1.进程 和 线程
进程:一个进程就是一个程序的运行实例。详细解释就是,启动一个程序的时候,操作系统会为该程序创建一块内存,用来存放代码、运行中的数据和一个执行任务的主线程,我们把这样的一个运行环境叫进程。
线程:程序中独立运行的代码段。一个进程 由单个或多个 线程 组成,线程是负责执行代码的。
进程和线程之间的关系有以下 4 个特点:
- 进程中的任意一线程执行出错,都会导致整个进程的崩溃。
- 线程之间共享进程中的数据。
- 当一个进程关闭之后,操作系统会回收进程所占用的内存。
- 进程之间的内容相互隔离。
2.单进程浏览器
单进程浏览器是指浏览器的所有功能模块都是运行在同一个进程里。
造成问题:
不稳定:一个插件的意外崩溃会引起整个浏览器的崩溃
不安全:通过插件可以获取到操作系统的任意资源,当你在页面运行一个插件时也就意味着这个插件能完全操作你的电脑
不流畅:同一时刻只能有一个模块可以执行
3.多进程浏览器
最新的 Chrome 浏览器包括:1 个浏览器(Browser)主进程、1 个 GPU 进程、1 个网络(NetWork)进程、多个渲染进程和多个插件进程。
- 浏览器进程。主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
- 渲染进程。核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。
- GPU 进程。其实,Chrome 刚开始发布的时候是没有 GPU 进程的。而 GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。最后,Chrome 在其多进程架构上也引入了 GPU 进程。
- 网络进程。主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
- 插件进程。主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。
虽然多进程模型提升了浏览器的稳定性、流畅性和安全性,但同样不可避免地带来了一些问题:
- 更高的资源占用。因为每个进程都会包含公共基础结构的副本(如 JavaScript 运行环境),这就意味着浏览器会消耗更多的内存资源。
- 更复杂的体系架构。浏览器各模块之间耦合性高、扩展性差等问题,会导致现在的架构已经很难适应新的需求了。
二、TCP协议
互联网中的数据是通过数据包来传输的。如果发送的数据很大,那么该数据就会被拆分为很多小数据包来传输。比如你现在听的音频数据,是拆分成一个个小的数据包来传输的,并不是一个大的文件一次传输过来的。
1. IP:把数据包送达目的主机
下面我们一起来看下一个数据包从主机 A 到主机 B 的旅程:
- 上层将含有“极客时间”的数据包交给网络层;
- 网络层再将 IP 头附加到数据包上,组成新的 IP 数据包,并交给底层;
- 底层通过物理网络将数据包传输给主机 B;
- 数据包被传输到主机 B 的网络层,在这里主机 B 拆开数据包的 IP 头信息,并将拆开来的数据部分交给上层;
- 最终,含有“极客时间”信息的数据包就到达了主机 B 的上层了。
2. UDP:把数据包送达应用程序
用户数据包协议是一个简单的面向数据报的通信协议,位于OSI的传输层
IP 通过 IP 地址信息把数据包发送给指定的电脑,而 UDP 通过端口号把数据包分发给正确的程序,和IP相比UDP 头中除了目的端口,还有源端口号等信息。
为了支持 UDP 协议,我把前面的三层结构扩充为四层结构,在网络层和上层之间增加了传输层,如下图所示:
下面我们一起来看下一个数据包从主机 A 旅行到主机 B 的路线:
- 上层将含有“极客时间”的数据包交给传输层;
- 传输层会在数据包前面附加上UDP 头,组成新的 UDP 数据包,再将新的 UDP 数据包交给网络层;
- 网络层再将 IP 头附加到数据包上,组成新的 IP 数据包,并交给底层;
- 数据包被传输到主机 B 的网络层,在这里主机 B 拆开 IP 头信息,并将拆开来的数据部分交给传输层;
- 在传输层,数据包中的 UDP 头会被拆开,并根据 UDP 中所提供的端口号,把数据部分交给上层的应用程序;
- 最终,含有“极客时间”信息的数据包就旅行到了主机 B 上层应用程序这里。
2.1:优点
UDP 不能保证数据可靠性,但是传输速度却非常快,所以 UDP 会应用在一些关注速度、但不那么严格要求数据完整性的领域,如在线视频、互动游戏等。
2.2:缺点
1、数据包在传输过程中容易丢失;
2、大文件会被拆分成很多小的数据包来传输,这些小的数据包会经过不同的路由,并在不同的时间到达接收端,而 UDP 协议并不知道如何组装这些数据包,从而把这些数据包还原成完整的文件。
3. TCP:把数据完整地送达应用程序
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议
相对于 UDP,TCP 有下面两个特点:
1、对于数据包丢失的情况,TCP 提供重传机制;
2、TCP 引入了数据包排序机制,用来保证把乱序的数据包组合成一个完整的文件。
TCP 单个数据包的传输流程和 UDP 流程差不多,不同的地方在于,通过 TCP 头的信息保证了一块大的数据传输的完整性。
一个完整的 TCP 连接的生命周期包括了“建立连接”“传输数据”和“断开连接”三个阶段。
总结:
- 互联网中的数据是通过数据包来传输的,数据包在传输过程中容易丢失或出错。
- IP 负责把数据包送达目的主机。
- UDP 负责把数据包送达具体应用。
- 而 TCP 保证了数据完整地传输,它的连接可分为三个阶段:建立连接、传输数据和断开连接。
三、HTTP请求流程
HTTP 协议,建立在 TCP 连接基础之上的。HTTP 是一种允许浏览器向服务器获取资源的协议,是 Web 的基础
带着问题去学习:
1.为什么通常在第一次访问一个站点时,打开速度很慢,当再次访问这个站点时,速度就很快了?
2.当登录过一个网站之后,下次再访问该站点,就已经处于登录状态了,这是怎么做到的呢?
在浏览器地址栏里键入网站会完成哪些动作:
1. 构建请求:
浏览器构建请求行信息,准备发起网络请求。
2. 查找缓存:
浏览器缓存是一种在本地保存资源副本,以供下次请求时直接使用的技术。
当浏览器发现请求的资源已经在浏览器缓存中存有副本,它会拦截请求,返回该资源的副本,并直接结束请求,而不会再去源服务器重新下载。这样做的好处有:
- 缓解服务器端压力,提升性能(获取资源的耗时更短了)。
- 对于网站来说,缓存是实现快速资源加载的重要组成部分。
当然,如果缓存查找失败,就会进入网络请求过程了。
3. 准备 IP 地址和端口
在 HTTP 工作开始之前,浏览器需要通过 TCP 与服务器建立连接。也就是说HTTP 的内容是通过 TCP 的传输数据阶段来实现的。
浏览器会请求 DNS 返回域名对应的 IP,当然浏览器还提供了DNS 数据缓存服务,如果某个域名已经解析过了,那么浏览器会缓存解析的结果,以供下次查询时直接使用,这样也会减少一次网络请求。如果 URL 没有特别指明端口号,那么 HTTP 协议默认是 80 端口。
4. 等待 TCP 队列
Chrome 有个机制,同一个域名同时最多只能建立 6 个 TCP 连接,如果在同一个域名下同时有 10 个请求发生,那么其中 4 个请求会进入排队等待状态,直至进行中的请求完成。
5. 建立 TCP 连接:三次握手
6. 发送 HTTP 请求
在浏览器发送请求行命令之后,还要以请求头形式发送其他一些信息,把浏览器的一些基础信息告诉服务器。比如包含了浏览器所使用的操作系统、浏览器内核等信息,以及当前请求的域名信息、浏览器端的 Cookie 信息,等等。
7. 服务器端处理 HTTP 请求流程返回响应数据
8. 断开连接
通常情况下,一旦服务器向客户端返回了请求数据,它就要关闭 TCP 连接。不过如果浏览器或者服务器在其头信息中加入了:
那么 TCP 连接在发送后将仍然保持打开状态。
保持 TCP 连接可以省去下次请求时需要建立连接的时间,提升资源加载速度
9. 重定向
如上图,响应行返回的状态码是 301,状态 301 就是告诉浏览器,我需要重定向到另外一个网址,而需要重定向的网址正是包含在响应头的 Location 字段中。
问题解答:
1.为什么很多站点第二次打开速度会很快?
- DNS 缓存:DNS 缓存比较简单,它主要就是在浏览器本地把对应的 IP 和域名关联起来。
- 页面资源缓存:
浏览器是通过响应头中的 Cache-Control 字段来设置是否缓存该资源,时长是通过 Cache-Control 中的 Max-age 参数来设置的
但如果缓存过期了,浏览器则会继续发起网络请求,并且在HTTP 请求头中带上
服务器收到请求头后,会根据 If-None-Match 的值来判断请求的资源是否有更新。
- 如果没有更新,就返回 304 状态码,相当于服务器告诉浏览器:“这个缓存可以继续使用,这次就不重复发送数据给你了。”
- 如果资源有更新,服务器就直接返回最新资源给浏览器。
简要来说,很多网站第二次访问能够秒开,是因为这些网站把很多资源都缓存在了本地,浏览器缓存直接使用本地副本来回应请求,而不会产生真实的网络请求,从而节省了时间。同时,DNS 数据也被浏览器缓存了,这又省去了 DNS 查询环节。
2. 登录状态是如何保持的?
服务器接收到浏览器提交的信息之后,查询后台,验证用户登录信息是否正确,如果正确的话,会生成一段表示用户身份的字符串,并把该字符串写到响应头的 Set-Cookie 字段里。然后把响应头发送给浏览器。
浏览器在接收到服务器的响应头后,开始解析响应头,如果遇到响应头里含有 Set-Cookie 字段的情况,浏览器就会把这个字段信息保存到本地。比如把UID=3431uad
保持到本地。
当用户再次访问时,浏览器会发起 HTTP 请求,但在发起请求之前,浏览器会读取之前保存的 Cookie 数据,并把数据写进请求头里的 Cookie 字段里(如下所示),然后浏览器再将请求头发送给服务器。
服务器在收到 HTTP 请求头数据之后,就会查找请求头里面的“Cookie”字段信息,当查找到包含UID=3431uad
的信息时,服务器查询后台,并判断该用户是已登录状态,然后生成含有该用户信息的页面数据,并把生成的数据发送给浏览器。
浏览器在接收到该含有当前用户的页面数据后,就可以正确展示用户登录的状态信息了。
简单地说,如果服务器端发送的响应头内有 Set-Cookie 的字段,那么浏览器就会将该字段的内容保持到本地。当下次客户端再往该服务器发送请求时,客户端会自动在请求头中加入 Cookie 值后再发送出去。服务器端发现客户端发送过来的 Cookie 后,会去检查究竟是从哪一个客户端发来的连接请求,然后对比服务器上的记录,最后得到该用户的状态信息。
四、输入URL到页面展示,这中间发生了什么?
1.合成完整的URL
输入内容符合 URL 规则,比如输入的是 baidu.com,那么地址栏会根据规则,把这段内容加上协议,合成为完整的 URL,如 https://www.baidu.com。
2.URL 请求过程
接下来,便进入了页面资源请求过程。这时,浏览器进程会通过进程间通信(IPC)把 URL 请求发送至网络进程,网络进程接收到 URL 请求后,会在这里发起真正的 URL 请求流程。那具体流程是怎样的呢?
1.网络进程会查找本地缓存是否缓存了该资源。如果有缓存资源,那么直接返回资源给浏览器进程;如果在缓存中没有查找到资源,那么直接进入网络请求流程。
2.DNS 解析,以获取请求域名的服务器 IP 地址。如果请求协议是 HTTPS,那么还需要建立 TLS 连接。
3.利用 IP 地址和服务器建立 TCP 连接。连接建立之后,浏览器端会构建请求行、请求头等信息,并把和该域名相关的 Cookie 等数据附加到请求头中,然后向服务器发送构建的请求信息。
4.服务器接收到请求信息后,会根据请求信息生成响应数据(包括响应行、响应头和响应体等信息),并发给网络进程。等网络进程接收了响应行和响应头之后,就开始解析响应头的内容了。
5.在接收到服务器返回的响应头后,网络进程开始解析响应头,如果发现返回的状态码是 301 或者 302,那么说明服务器需要浏览器重定向到其他 URL。这时网络进程会从响应头的 Location 字段里面读取重定向的地址,然后再发起新的 HTTP 或者 HTTPS 请求,一切又重头开始了。如果响应行是 200,那么表示浏览器可以继续处理该请求。
6.根据Content-Type来判断返回的类型处理该响应内容。
7.准备渲染进程
- 通常情况下,打开新的页面都会使用单独的渲染进程;
- 如果从 A 页面打开 B 页面,且 A 和 B 都属于同一站点的话,那么 B 页面复用 A 页面的渲染进程;如果是其他情况,浏览器进程则会为 B 创建一个新的渲染进程。
8.提交文档
- 提交文档”的消息是由浏览器进程发出的,渲染进程接收到“提交文档”的消息后,会和网络进程建立传输数据的“管道”。
- 等文档数据传输完成之后,渲染进程会返回“确认提交”的消息给浏览器进程。
- 浏览器进程在收到“确认提交”的消息后,会更新浏览器界面状态,包括了安全状态、地址栏的 URL、前进后退的历史状态,并更新 Web 页面。
这也就解释了为什么在浏览器的地址栏里面输入了一个地址后,之前的页面没有立马消失,而是要加载一会儿才会更新页面。
9.渲染更新页面内容
小结
整个流程小结
1,用户输入url并回车
2,浏览器进程检查url,组装协议,构成完整的url
3,浏览器进程通过进程间通信(IPC)把url请求发送给网络进程
4,网络进程接收到url请求后检查本地缓存是否缓存了该请求资源,如果有则将该资源返回给浏览器进程
5,如果没有,网络进程向web服务器发起http请求(网络请求),请求流程如下:
5.1 进行DNS解析,获取服务器ip地址,端口(端口是通过dns解析获取的吗?这里有个疑问)
5.2 利用ip地址和服务器建立tcp连接
5.3 构建请求头信息
5.4 发送请求头信息
5.5 服务器响应后,网络进程接收响应头和响应信息,并解析响应内容
6,网络进程解析响应流程;
6.1 检查状态码,如果是301/302,则需要重定向,从Location自动中读取地址,重新进行第4步
(301/302跳转也会读取本地缓存吗?这里有个疑问),如果是200,则继续处理请求。
6.2 200响应处理:
检查响应类型Content-Type,如果是字节流类型,则将该请求提交给下载管理器,该导航流程结束,不再进行
后续的渲染,如果是html则通知浏览器进程准备渲染进程准备进行渲染。
7.准备渲染进程
7.1 浏览器进程检查当前url是否和之前打开的渲染进程根域名是否相同,如果相同,则复用原来的进程,如果不同,则开启新的渲染进程
8.传输数据、更新状态
8.1 渲染进程准备好后,浏览器向渲染进程发起“提交文档”的消息,渲染进程接收到消息和网络进程建立传输数据的“管道”
8.2 渲染进程接收完数据后,向浏览器发送“确认提交”
8.3 浏览器进程接收到确认消息后更新浏览器界面状态:安全、地址栏url、前进后退的历史状态、更新web页面。
9.浏览器不能直接理解 HTML 数据,所以第一步需要将其转换为浏览器能够理解的 DOM 树结构;生成 DOM 树后,还需要根据 CSS 样式表,来计算出 DOM 树所有节点的样式; 最后计算 DOM 元素的布局信息,使其都保存在布局树中。
五、HTML、CSS和JavaScript,是如何变成页面的?
1.构建 DOM 树
因为浏览器无法直接理解和使用 HTML,所以需要将 HTML 转换为浏览器能够理解的结构——DOM 树。
2.样式计算
2.1 把 CSS 转换为浏览器能够理解的结构
渲染引擎接收到 CSS 文本时,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的结构——styleSheets。
2.2 转换样式表中的属性值,使其标准化
2.3 计算出 DOM 树中每个节点的具体样式
3.布局阶段
需要计算出 DOM 树中可见元素的几何位置
3.1 创建布局树
- 遍历 DOM 树中的所有可见节点,并把这些节点加到布局中;
- 而不可见的节点会被布局树忽略掉,如 head 标签下面的全部内容,再比如 body.p.span 这个元素,因为它的属性包含 dispaly:none,所以这个元素也没有被包进布局树。
3.2 布局计算
4.分层
因为页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树。
5.图层绘制
在完成图层树的构建之后,渲染引擎会对图层树中的每个图层进行绘制。
6.栅格化操作
所谓栅格化,是指将图块转换为位图。
7.合成和显示
8.渲染流程小结
- 渲染进程将 HTML 内容转换为能够读懂的DOM 树结构。
- 渲染引擎将 CSS 样式表转化为浏览器可以理解的styleSheets,计算出 DOM 节点的样式。
- 创建布局树,并计算元素的布局信息。
- 对布局树进行分层,并生成分层树。
- 为每个图层生成绘制列表,并将其提交到合成线程。
- 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
- 合成线程发送绘制图块命令DrawQuad给浏览器进程。
- 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。
9.拓展相关概念
“重排”“重绘”和“合成”
9.1 更新了元素的几何属性(重排)
从上图可以看出,如果你通过 JavaScript 或者 CSS 修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的。
9.2 更新元素的绘制属性(重绘)
从图中可以看出,如果修改了元素的背景颜色,那么布局阶段将不会被执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。
9.3 直接合成阶段
在上图中,我们使用了 CSS 的 transform 来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。
10.思考
10.1 触发重排操作有哪些?
如offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight 等属性
10.2 触发重绘操作有哪些?
color、visibility、outline、background-color
10.3 如何减少重排重绘
减少重排重绘, 方法很多:
1. 使用 class 操作样式,而不是频繁操作 style
2. 避免使用 table 布局
3. 批量dom 操作,例如 createDocumentFragment,或者使用框架,例如 React
4. Debounce window resize 事件
5. 对 dom 属性的读写要分离
6. will-change: transform 做优化