1-宏观上的浏览器

85 阅读12分钟

1.1Chrome架构——多进程

概念:进程与线程

线程不是单独存在的,他由进程来启动和管理。

进程就是一个程序运行的实例。

当启动一个程序的时候,操作系统就会为程序创建一块内存,用来存放代码、运行中的数据和一个执行任务的主线程,我们把这样的一个环境叫做进程。

image-20220718141803651

进程与线程的特点

  • 进程中的任意一线程执行出错,都会导致整个进程崩溃
  • 线程之间共享进程中的数据
  • 当进程关闭了之后,操作系统会回收进程所占用的内存(被泄漏的内存也会回收)
  • 进程之间的内容相互隔离(但是会有进程通信IPC机智)

单进程浏览器时代

单进程浏览器时代,浏览器所有的功能模块都在一个进程内,包括网络、插件、JavaScript运行环境、渲染引擎和页面等模块。

image-20220718142535282

但是那么多模块都在一个进程内,容易导致浏览器不稳定、不流畅、不安全。

不稳定

由于全部模块都在一个进程内,如果一个线程出现问题,那么整个进程就无法继续运行。

而早期浏览器通过插件来实现Web视频、游戏等功能,而插件也很容易出问题,所以浏览器就容易奔溃,不稳定。同时当js代码出现问题话(比如一个死循环),整个浏览器也会奔溃。

不流畅

从上面可以看到,页面线程包括了很多模块,这也就意味着,同时只能有一个模块在运行着,如果一个模块占用时间太长或者死循环,就会造成浏览器的卡顿。

页面的内存泄漏也容易造成浏览器变慢,当运行了一个比较复杂的页面再关闭页面,会存在内存不能完全回收的情况;这也就导致了使用时间越长,内存占用越高,浏览器会变得越来越慢。

不安全

从插件和脚本两个角度:

插件:插件可以通过操作系统获取到电脑的任意资源

脚本:脚本可以通过浏览器的漏洞来获取系统权限,同样会引发安全问题。

多进程浏览器时代

早期多进程架构:此时页面有多个进程,分别负责不同的内容。

image-20220718150416478

解决不稳定的问题

由于各个模块是在不同进程里运行的,所以一个进程出错的时候,影响到的仅仅是当前的页面进程或者插件进程,并不会影响到浏览器的其他页面。

解决不流畅的问题

由于js运行在渲染进程里,所以当js代码有死循环的时候,影响到的也只是当前页面,其他页面不会出现问题。

而对于内存泄露问题,由于关闭一个页面,就关闭了他的整个渲染进程,所以能完整的回收对应的内存。

解决安全问题

由于设置了安全沙箱,相当于操作系统给进程上了一把锁,进程可以运行,但是不能修改硬盘里面的任何数据,也不能读取敏感数据,这也就保障了安全问题。

目前的多进程架构

一个浏览器主进程 + 一个网络进程 + 一个GPU进程 + 多个渲染进程 + 多个插件进程

image-20220718151328542

浏览器进程:负责页面的显示,用户交互,子进程管理等

网络进程:主要负责页面网络资源的加载。

GPU进程:用于CSS、3D效果等页面的绘制。

渲染进程:核心任务是将HTML、CSS、JS转化为用户与之交互的界面。它运行在沙箱模式下,并且为每个tab标签创建一个渲染进程。

插件进程:主要负责插件的运行,因为插件容易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。

带来的一些问题

  • 更高的资源占用:因为每个进程都会包含公共基础结构的副本
  • 更复杂的体系结构:各模块之间耦合性高,扩展性差

未来面向服务的架构

原来的各个模块会被重构成独立的服务,每个服务都可以在独立的进程中运行,访问服务必须使用定义好的接口,通过IPC来通信。

image-20220718152555120

\

1.2HTTP请求流程

浏览器发起HTTP请求

1、构建请求

image-20220719143019970

2、查找缓存

发起请求之后,浏览器回去浏览器缓存中查询是否有要请求的文件。(缓存是在本地保存文件资源副本)

如果发现有请求的资源的副本,则直接拦截请求,并返回该资源的副本;然后结束本次请求。

好处:

  • 减少请求服务器的次数,减轻服务器的压力
  • 实现资源的快速加载

如果缓存内没有该资源的副本,则就会进入网络请求的环节。

3、准备ip地址和端口

在HTTP开始工作之前,浏览器需要通过TCP与服务器建立联系。

而建立联系的第一步就是要准备ip地址以及端口。

ip地址通过DNS域名系统来获取,端口没有指定的话就默认8080

4、等待TCP队列

由于同一个域名下最多只能建立6个TCP链接,多出的请求就会变成等待状态。

5、建立TCP链接

通过三次握手建立TCP链接

6、发送HTTP请求

请求包括了请求行、请求头、请求体

假如使用POST方法,用于发送一些数据给服务器,这时候这些数据就会放在请求体。

请求头还包括有cookie、浏览器内核等信息

image-20220719200103487

服务器处理HTTP请求

1、返回请求

状态码代表请求是否成功

响应头包含了一些服务器自身的信息和cookie等

响应体通常包含HTML的实际内容

image-20220719200621348

2、断开连接

通常一旦服务器向客户端返回了信息,就要关闭TCP连接。

但是假如浏览器或者服务器在其头信息中加入了Connection:Keep-Alive,那么TCP连接在发送后依旧保持打开状态,这样就可以省去下一个建立连接的时间。

3、重定向

有时候输入baidu.com最终打开的页面是www.baidu.com

这是因为第一次请求baidu.com的时候,服务器响应的状态时301,返回了重定向的地址,然后会重新对这个地址发送请求。

image-20220719201108264

很多站点第二次打开速度会很快

浏览器缓存机制:

关键属性:

  • Cache-Control(设置缓存时间)
  • If-None-Match(检查资源是否有更新)
  • Not Modified (资源没有更新,可以继续使用环迅)

image-20220719201718835

登录状态如何保持

  • 服务器会将用户身份的字符串放在响应头的Set-Cookie字段里面
  • 浏览器解析响应头的时候,会将字段信息保存到本地
  • 用户再次访问的时候,浏览器发起请求之前,会将字段写进请求头的cookie字段里面
  • 服务器收到cookie字段会进行校验用户信息
  • image-20220719202201699

总结

发起一个http请求时,浏览器首先会构建请求,然后检查本地是否有缓存资源,若有则直接读取;若没有,则发送请求。首先准备ip地址以及端口,然后等待TCP队列;轮到了之后建立TCP的连接,连接成功之后发起HTTP请求,服务器进行处理,服务器返回请求,接收到响应之后,TCP连接断开(不一定)

1.3导航流程 -从输入URL到页面展示

总过程

image-20220719210007635

  • 浏览器进程:主要负责与用户的交互、子进程管理等
  • 网络进程:面向渲染进程和浏览器进程等提供网络下载功能
  • 渲染进程:将从网络下载下来的HTML、JavaScript等资源解析成可以显示和交互的页面。同时渲染进程是在安全沙箱里的。

整个过程:

  • 用户输入请求
  • 网络进程发起请求
  • 服务器响应请求后,开始准备渲染进程
  • 准备好后,向渲染进程提交页面数据,接受到数据后进行渲染

用户输入

用户在地址栏中输入一个关键字后,会判断是搜索内容还是URL

  • URL:会将其加工成完整的URL(例如带上http)
  • 关键字:会加工成带搜索关键字的URL

URL请求

过程相当于上一节的HTTP请求流程,请求服务器并获取响应数据

返回的数据有一个Content-Type字段,用来区分是HTML页面还是下载类型等(text/html、application/octet-stream)

准备渲染过程

  • 通常情况下,打开新的页面都会使用单独的渲染进程
  • 但如果是同一站点的话,会使用相同的渲染进程(同一站点:同一协议与根域名)

此时渲染进程准备好了之后,不能立即解析文档,因为文档还在网络进程中,需要提交文档到渲染进程中。

提交文档

  • ‘提交文档’的消息是由浏览器进程发出的,渲染进程收到该消息之后,会与网络进程建立传输数据的管道
  • 数据传输完成之后,渲染进程会提交”确认提交“的消息给浏览器进程
  • 浏览器进程收到”确认提交“之后,会更新浏览器界面的状态,包括安全状态,地址栏URL,web页面(暂时空白)、前进后退的历史记录(此时还没有到渲染阶段)
  • image-20220719211851246

渲染阶段

渲染阶段具体在后面两个小节,渲染结束之后,渲染进程会发消息给浏览器进程,接收到消息之后,就会停止标签图标上的加载动画。

1.4渲染流程

大致流程

image-20220719215852714.png

  • 构建DOM树
  • 样式计算
  • 布局阶段
  • 分层
  • 绘制
  • 分块
  • 光栅化
  • 合成

构建DOM树

因为浏览器无法直接理解HTML语言,所以要构建成他能理解的DOM结构。(HTML和DOM树内容几乎一样,只是DOM是保存在内存中的树状结构)

image-20220719220158595.png

样式计算

  • 将CSS转化为stylesheets:浏览器不能识别CSS,所以要转化成为他能识别的stylesheets

image-20220719220437494.png

  • 转换样式表中的属性值,使其标准化

image-20220719220528070.png

  • 计算出每个DOM树中节点的具体样式:通过CSS的继承与层叠,计算出DOM节点的具体样式

布局阶段

有了DOM树和样式,还不足以显示页面,因为还不知道DOM节点的几何位置,所以把计算节点的几何位置叫做布局。

创建布局树

这个过程会忽略所有不会显示的节点,只加入页面会显示的节点

image-20220719220853814.png

布局计算

进行了布局计算之后,会将计算结果加入到布局树中,所以这个阶段,输入也是布局树,输出也是布局树,这也是不合理的一个地方。

分层——生成图层树

为了更方便的实现渲染效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一颗对应的图层树

image-20220720142641154.png

image-20220720142657372.png

哪些节点可以创建新的层:

  • 拥有层叠上下文属性的元素会被提升为单独一层

  • 需要裁剪的地方会创建为图层

image-20220720142856638.png

图层绘制

在完成图层树的构建之后,渲染引擎会对图层树的每个图层进行绘制。会把每个层的绘制拆分成很多个小的绘制指令,形成绘制列表。

栅格化操作

上诉操作只是生成了绘制列表,实际上绘制操作是由渲染引擎里面的合成线程来完成的,上述是在主线程完成的。

image-20220720144106161.png

  • 合成线程会将图层划分为图块

  • 然后栅格化线程池会将图块生成为位图

  • 栅格化的过程中还会使用GPU进程的GPU来加速生成(这一步跨进程了)

image-20220720145219132.png

合成和显示

所有图块被栅格化了之后,合成线程就会生成绘制图块的命令,提交给浏览器进程,然后对应的组件就会进行绘制,并且显示在屏幕上。

总结

  • 生成DOM树
  • 计算样式
  • 计算布局
  • 分层生成图层树
  • 生成绘制列表(将列表提交至合成线程)
  • 划分成图块
  • 将图块栅格化成位块(借助栅格化线程池,以及GPU进程)
  • 浏览器对应组件生成页面(浏览器进程)

image-20220720145449759.png