输入网址,再按回车后面发生了什么?

"Web"

Posted by 許敲敲 on November 21, 2019

我想做个好Web developer……

前言

如题,如果你是一名web developer,那么你一定要知道这个到底是什么过程?从浏览器判别输入是否为搜索关键字还是URL一直到页面render完整呈现,这里面涉及到网络,操作系统,Web的一些知识点。这里面的知识点比较零散。如果能系统而又全面的回答出这个问题,那么他一定是个好Web developer。看了极客时间的《浏览器原理专栏》以及Udacity的browser rendering optimization,打算试着整理一下思路。

正文

键入URL 到页面展示的过程

首先可以简单梳理一下这个过程,如果从浏览器视角的话,该过程的主要流程可以如下图所示: 从输入URL 到页面展示流程示意图 从上图就可以了解从输入URL到页面解析的整个过程。

首先需要从操作系统的角度来知道,该过程需要有浏览器进程、渲染进程和网络进程之间的相互配合。就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 进程。
  • 网络进程。主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
  • 插件进程。主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。

所以该过程的主要流程可以参考如下:

  • 用户在浏览器进程里输入Query
  • 网络进程发起URL请求
  • 服务器响应URL请求之后,浏览器开始准备渲染进程
  • 渲染进程ready,再通知浏览器进程‘可以展示页面’,该阶段也称为“提交文档”。
  • 浏览器进程接收到“提交文档”后,就开始移除之前新的文档,并通知渲染进程开始“解析页面”。

1.输入Query

当user 在地址栏输入一个查询关键字的时候,地址栏会判断,输入的是需要搜索的关键字,还是请求的URL。如果是关键字,地址栏会结合浏览器所设置的搜索引擎来生成一个带输入关键字的URL,然后开始发起URL请求。如果浏览器判断输入的是合法的URL,那么浏览器也会进行相应的修正,比如为baidu.com 加上协议,合成完整的URL:https://www.baidu.com 然后开始请求。

当用户输入关键字并键入回车之后,这意味着当前页面即将要被替换成新的页面,不过在这个流程继续之前,浏览器还给了当前页面一次执行 beforeunload 事件的机会,beforeunload 事件允许页面在退出之前执行一些数据清理操作,还可以询问用户是否要离开当前页面,比如当前页面可能有未提交完成的表单等情况,因此用户可以通过 beforeunload 事件来取消导航,让浏览器不再执行任何后续工作。

从浏览器搜索栏键入 baidu.com 开始,就可以看到浏览器会有之前的搜索记录匹配。 输入URL的浏览器的缓存搜索记录匹配

2. URL请求过程

此时,开始进入页面资源请求过程。浏览器会进行进程间通信(IPC) 把URL请求发送到网络进程。当网络进程接收到URL请求时,首先网络进程会查找本地是否具有缓存。如果具备缓存资源,那么直接返回浏览器该缓存资源,否则直接进入网络请求流程。 请求的第一步会进入DNS解析,来获取域名的IP地址。域名解析需要访问一系列的域名解析服务器,来把该域名翻译成为TCP/IP 协议的IP地址。为提高performance,域名解析的过程中会设置多级的缓存,浏览器首先看一下自己的缓存里有没有,如果没有就向操作系统的缓存要,还没有就检查本机域名解析文件 hosts。如果本地都找不到缓存,就会去顶级域名服务器“com”去查找,并且将该结果缓存。 如果请求协议是HTTPS还需要建立TLS连接。 TLS 握手协议过程示意图: TLS TLS handshake protocol

当Client利用IP地址和服务器建立TCP连接后,浏览器会构建请求行、请求头等信息,并将该域名相关的Cookie等数据附加到请求头中,向服务器发送请求信息。 服务器接收到请求信息后,会根据请求信息生成响应数据(包括响应行、响应头和响应体等信息),并发给网络进程。等网络进程接收了响应行和响应头之后,就开始解析响应的内容了。

2.1重定向

在接收到服务器返回的响应头后,网络进程开始解析响应头,如果发现返回的状态码是 301 或者 302,那么说明服务器需要浏览器重定向到其他 URL。这时网络进程会从响应头的 Location 字段里面读取重定向的地址,然后再发起新的 HTTP 或者 HTTPS 请求,重头开始。如果响应行是 200,那么表示浏览器可以继续处理该请求。

2.2响应数据类型处理

通常URL请求的数据类型会包含HTML页面,或者下载数据类型。响应头中的Content-type会告知浏览器返回的数据类型。如Content-type:text/html 表示返回数据 类型为HTML格式,Content-Type:application/octet-stream表示返回数据类型为字节流类型,浏览器按照下载类型来处理。不同的Content-Type字段,浏览器后续处理流程会有所不同。如果是下载类型,那么浏览器会将请求任务提交给下载管理器,同时关闭导航流程。如果是HTML浏览器的导航流程还会继续,并通知渲染进程开始进行渲染页面。

3.准备渲染进程

默认情况下,Chrome 会为每个页面分配一个渲染进程,也就是说,每打开一个新页面就会配套创建一个新的渲染进程。但是,也有一些例外,在某些情况下,浏览器会让多个页面直接运行在同一个渲染进程中。比如从极客时间的首页里面打开了另外一个页面——算法训练营,观察下图的 Chrome 的任务管理器截图: 多个页面运行在一个渲染进程中 从图中可以看出,打开的这三个页面都是运行在同一个渲染进程中,进程 ID 是 23601。 当从新的页面打开另外一个页面,并且这些页面属于同一个站点(same-site)的话,那么新页面会复用父页面的渲染进程(process-per-site-instance)。

同一站点”定义为根域名(例如,geekbang.org)加上协议(例如,https:// 或者 http://),还包含了该根域名下的所有子域名和不同的端口,比如下面这三个:

https://time.geekbang.org
https://www.geekbang.org
https://www.geekbang.org:8080

4.提交文档

渲染进程准备好之后,它就会通知浏览器进程,可以替换当前旧的文档了,具体地讲,需要经过下列几个步骤:

  • 首先“提交文档”的消息是由渲染进程发出给浏览器进程的,这是告诉浏览器进程,它已经准备好了,可以执行解析渲染等后续操作了。
  • 浏览器进程接收到当前渲染进程的“提交文档”消息后,便开始清理当前的旧文档,然后会发出“确认提交”的消息给渲染进程。同时,浏览器进程会更新浏览器界面状态,包括了安全状态、地址栏的 URL、前进后退的历史状态,并更新 Web 页面。
  • 当渲染进程接收到“确认提交”的消息后,便开始执行解析数据、下载子资源等后续流程,并实时向浏览器进程更新最新的渲染状态。 导航完成状态

5.渲染阶段

文档被提交,渲染进程便开始页面解析和子资源加载了。该过程主要涉及 HTML,CSS,JavaScript 如何生成页面。从解析 *.html文件,生成DOM, CSSOM, Render Tree,再到计算布局(layout),绘制(paint),合成页面(composite layers),每个过程都涉及到优化问题。开发者应该注重用户体验,防止页面卡顿,使用JavaScript优化动画流程,优化样式表等来防止强制同步布局等。具体的主要过程如下。

  • 生成Render Tree

    首先解析请求返回的HTML代码,根据相关的Token规则,我们得到相应的DOM Node,并构建DOM Tree,如下图所示。 构建DOMt tree1 构建DOMt tree2 然后根据CSS的样式规则,我们依次得到各Node与对应的CSS样式结合,再生成Render Tree。其中Render Tree与DOM tree非常类似,不同的是Render tree 没有head也不含任何脚本。如果CSS样式中设置了{display:none} 属性的话,该node也会从Render tree中移掉,对于一些伪元素,例如添加了h1:after{content:"this will be visible"}属性,Render tree会添加该node,但DOM并不会包含这个node。需要注意的是,只有实际显示在网页上的元素才会进入Render tree.

    构建DOM tree3

  • 计算布局

    当浏览器知道哪个规则适用于哪个元素后,就会开始进入布局计算阶段。其中具体样式适用元素的规则需要根据CSS 的继承规则和层叠规则计算。我们可以通过盒模型,排版等计算每个节点坐标,大小,得到页面的布局。这一过程比较复杂,该计算过程也称为回流(reflow)。 页面布局得到的边框示意图: layout

  • 光栅化

    得到计算的布局之后,便可以矢量光栅化到图形(vector 2 raster).比如我们上一步得到的只是一系列方框,只是形状,这一步便会填充逐个像素。如下图所示: vector2raster 矢量的栅格化到图形 render tree 通过计算边框、绘制文本、绘制阴影、白线、绘制位图最终形成的展示内容: paint

    绘制位图:浏览器会将.jpg,.png,*.gif等内容解码到内存,再适用到web设计。

  • 合成页面

    之前我们所介绍的页面,只是在一个图层(layer)内完成的,实际的网页页面要复杂的多,往往需要包含多个图层。浏览器会根据需要创建多个图层,单独绘制这些图层并合成层。 合成层

    页面图层示意图

    创建图层的条件: 1.拥有层叠上下文属性的元素会被提升为单独的一层 2.需要剪裁(clip)的地方也会被创建为图层

    对于绘制和合成页面的中间过程,还会涉及到跨进程操作。通常,绘制过程中,都是在一系列的网格图块(tiles)中实现的。图层会将其本身以及图块上传到GPU中进行栅格化加速生成。使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。GPU 操作是运行在 GPU 进程中,如果栅格化操作使用了 GPU,那么最终生成位图的操作是在 GPU 中完成的。具体可以参考下图流程。 gpu

    最终GPU 按照指示,将图片显示到屏幕上,以上就是我们从单个请求到像素填充到屏幕上的简单流程。

总结:

最后简单的总结一下过程要点:

HTTP 请求过程:

  • 浏览器从地址栏的输入中获得服务器的 IP 地址和端口号;
  • 浏览器用 TCP 的三次握手与服务器建立连接;
  • 浏览器向服务器发送拼好的报文;
  • 服务器收到报文后处理请求,
  • 同样拼好报文再发给浏览器;浏览器解析报文,渲染输出页面。

页面渲染过程:

  • 根据HTML构建DOM 树
  • 根据CSS 样式表,计算 DOM 树所有节点样式并构建Render tree
  • 计算Layout 布局
  • 根据Layout进行分层
  • 各图层分成图块,并在GPU中转换图块成位图
  • 生成页面,并显示到显示器。