从输入URL到页面展示的过程详解

作为一个前端人员,面试时总会有这个问题:“在浏览器从输入URL到页面展示中经历了什么过程”?这个问题我之前也是一知半解,随着对内容了解的越多,发现这个问题涵盖的面越广,涉及的知识也很多,所以今天将它整理一下,方便自己和其他小伙伴一起学习。

由于这个问题涉及知识很多,而且我也想把这些知识都一起整理一下,所以本文采用总-分-总的的行文思路,具体内容如下:

结构部署

  1. 第一章 概述:首先展示整体流程图,介绍整个流程所需要涉及的几个方面
  2. 第二章 浏览器进程:这部分详细介绍一个浏览器含有的多种进程以及每种进程的作用
  3. 第三章 TCP/IP协议: 这部分介绍网络体系结构分层,TCP/IP的具体含义,介绍数据包如何传输到浏览器里,以及三次握手和四次挥手的过程
  4. 第四章 HTTP网络请求: 这部分介绍HTTP请求的完整流程,介绍重定向和如何保持登录状态
  5. 第五章 浏览器渲染:这部分介绍渲染的整个过程
  6. 第六章 从输入URL到页面展示的总流程:将上诉几章知识进行总结,最后串联全部流程
  7. 参考文献 为了这篇文章一共参考的文章,对这些作者从心里表达感谢,让我学习了很多内容

在这篇文章中,我所有的分析都是基于 Chrome 浏览器的,要是小伙伴觉得哪章的内容很无聊,或者我说的不是很好,可以提出意见和建议,若文中有不对的地方,欢迎评论区指正,十分感谢

好的,废话不多说,现在开始正文!!

第一章 概述

先看一个完整流程的示意图:
在这里插入图片描述

从图中看出,从输入 URL 到页面展示 ,整个流程涉及到了网络、进程、渲染等一系列的知识,主要包含以下几个方面:
1.多个进程之间的相互通信
2.浏览器解析域名并进行导航
3.浏览器发起http网络请求
4.服务器返回Http响应
5.浏览器进行渲染和展示

接下来详细介绍每一部分内容

第二章 浏览器进程

在了解浏览器进程之前,我们需要知道现在浏览器是多进程并行处理任务,如果不了解进程和线程之间的关系,不了解什么是并行处理,可以看进程和线程这篇文章,这里不过多说明。
现在我们用谷歌浏览器打开一个界面,我们可以看看当前启动了多少个进程?
在这里插入图片描述
从图中可以看到,我的界面是打开了5个进程,为什么一个页面会有这么多进程?这些进程都有什么作用?现在开始一一说明

2.1 Chrome浏览器最新架构

最新的Chrome浏览器的进程架构如图:
在这里插入图片描述
如图最新的chorme进程架构包括 1个浏览器主进程,1个网络进程,1个GPU进程,多个渲染进程和多个插件进程

2.2 每个进程作用

浏览器主进程:主要负责界面展示,用户交互和子进程管理等

网络进程: 负责网络资源的加载,如处理Http请求等

GPU进程: 最开始Chrome 是没有 GPU 进程,而 GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,最后在多进程架构上也引入了 GPU 进程

渲染进程 :核心任务将HTML,CSS和JS转换成用户可见的页面,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中。另外,一般默认情况,打开一个Tab页会开启一个渲染进程,但是如果从一个页面打开了新页面(必须从一个页面打开),而新页面和当前页面属于同一站点时,那么新页面会复用父页面的渲染进程。如下图所示:
同一站点复用一个渲染进程
其中csdn两个网页就是共用一个渲染进程

"同一站点(same-site)"定义为根域名(例如,csdn.net)加上协议(如https:// 或者http://)都相同,还包含了该根域名下的所有子域名和不同的端口,比如下面:
https://www.csdn.net
https://www.csdn.net:port
都是属于同一站点,因为它们的协议都是https,而根域名也都是csdn.net。
想要详细了解这个,推荐一篇文章Understanding “same-site” and “same-origin”

插件进程:主要负责插件运行,防止插件进程崩溃会对浏览器和页面产生影响

所以现在可以说明开头的问题,为什么一个页面会有多个进程?因为打开一个页面,会启动一个浏览器进程,一个网络进程,一个GPU进程,一个渲染进程,所以至少会有4个进程;如果还有插件,就会再加一个插件进程,为5个进程

2.3 小结

这章节主要介绍谷歌浏览器的几种进程 ,最新的chorme进程架构包括 1个浏览器主进程,1个网络进程,1个GPU进程,多个渲染进程和多个插件进程,每个进程执行不同任务。当开启一个页面时,至少需要开启四个进程。

另外,如果从一个页面打开了新页面,而新页面和当前页面属于同一站点时,那么新页面会复用父页面的渲染进程

第三章 TCP/IP协议

3.1 计算机网络体系结构分层

在这里插入图片描述
在介绍TCP/IP协议之前先看一下整个计算机网络体系结构,从图中可以看出,整个体系按照不同功能分为不同层,并且这些层并不是只有TCP协议和IP协议,而是包含了不同的协议,这些不同的协议组成了一个很大的协议集合,统称为TCP/IP协议。

3.2 TCP/IP具体含义

我们很多人会认为 TCP/IP 是指 TCP 和 IP 两种协议,实际生活当中有时也确实就是指这两种协议。然而在很多情况下,它只是利用 IP 进行通信时所必须用到的协议群的统称

具体来说,IP 或 ICMP、TCP 或 UDP、TELNET 或 FTP、以及 HTTP 等都属于 TCP/IP 协议,他们与 TCP 或 IP 的关系紧密,是互联网必不可少的组成部分。

总结来说,TCP/IP 被称为 传输控制协议/互联网协议,又称网络通讯协议(Transmission Control Protocol),它原本就是为使用互联网而开发制定的协议族。因此,互联网的协议就是 TCP/IP,TCP/IP 就是互联网的协议
在这里插入图片描述
介绍完什么是TCP/IP协议之后,就开始说明如何把一个页面文件完整的送到浏览器里。

3.3 页面文件送达到浏览器的过程

在了解过程之前我们要知道几点基础知识:

3.3.1 基础概念

1.数据包:

包、帧、数据包、段、消息都是用来表达数据的单位,主要区别如下:

  • 包:可以说是全能型术语;
  • 帧:链路层中包的的单位;
  • 数据包:网络层及网络层以上的分层的包的单位;
  • 段:TCP数据流中的信息
  • 消息:应用协议中数据的单位

在网络传输中,如果发送的数据很大,那么该数据就会被拆分为很多小数据包来传输。每个数据包会被标上描述顺序的标识,等到接收方拿到后,可以按照顺序重新组装成完成的数据。

2.首部数据:

每个分层中,都会对所发送的数据附加一个首部,在这个首部中包含了该层必要的信息,如发送的目标地址以及协议相关信息。通常,为协议提供的信息为包首部,所要发送的内容为数据。在下一层的角度看,从上一层收到的包全部都被认为是本层的数据。

因为有了首部数据,所以在网络中传输的数据包由两部分组成:一部分是协议所要用到的首部,另一部分是上一层传过来的数据。 在数据包的首部,明确标明了协议应该如何读取数据。反过来说,看到首部,也就能够了解该协议必要的信息以及所要处理的数据

3.协议:

互联网,实际上是一套理念和协议组成的体系架构。其中协议是一套众所周知的规则和标准,如果各方都同意使用,那么它们之间的通信将变得毫无障碍。这就好比买卖双方必须提前沟通好信息:价格是多少,要几斤几两,要哪种货物等。双方都达成共识了之后,交流才会是无障碍的。

3.3.2 IP:数据包送达主机流程

数据包发送之前,首先需要知道数据包要发给谁,这就需要网际协议(Internet Protocol,简称 IP)。互联网上不同的在线设备都有唯一的IP地址,这个IP地址是一个数字,就和我们的居住地址和门牌一样,必须唯一才能精确定位。

接下来看一下 数据包如何从主机 A 到主机 B :
在这里插入图片描述
在上图中,为了方便理解先把网络简单分为三层结构:
从图中可以看出,一个数据包从主机A到主机B的整个流程如下:

  1. 上层将数据包传递给网络层
  2. 网络层再将IP头附加到数据包上,组成新的 IP 数据包,并交给底层
  3. 底层通过物理网络将数据包传输给主机 B
  4. 数据包被传输到主机 B 的网络层,在这里进行反向操作,主机 B 拆开数据包的 IP 头信息,并将拆开来的数据部分交给上层
  5. 最终需要传输的数据包就到达了主机 B 的上层

IP头:
如果要想把一个数据包从主机 A 发送给主机 B,那么在传输之前,数据包上会被附加上主机 B 的 IP 地址信息,这样在传输过程中才能正确寻址。另外数据包上还会附加上主机 A 本身的 IP 地址,有了这些信息主机 B 才可以回复信息给主机 A。这些附加的信息会被装进一个叫 IP 头的数据结构里
IP 头是 IP 数据包开头的信息,包含 IP 版本、源 IP 地址、目标 IP地址、生存时间等信息

3.3.3 UDP:数据包送达应用程序

IP 是非常底层的协议,只负责把数据包传送到对方电脑,但是浏览器上一般会运行多个程序,而区分这些程序的一个标准就是端口号

端口号其实就是一个数字,每个想访问网络的程序都需要绑定一个端口号。

端口号是用户数据包协议(User Datagram Protocol,UDP)中一个最重要的信息,通过端口号 UDP 就能把指定的数据包发送给指定的程序了。

接下来看一下数据包如何到达应用程序。为了支持 UDP 协议,在网络层和上层之间增加了传输层,改为四层,如下图所示:
在这里插入图片描述
从图中可以看出,一个数据包从主机A到主机B的应用程序整个流程如下:

  1. 上层将数据包交给传输层
  2. 传输层会在数据包前面附加上UDP 头,组成新的 UDP 数据包,再将新的 UDP 数据包交给网络层
  3. 网络层再将 IP 头附加到数据包上,组成新的 IP 数据包,并交给底层
  4. 底层通过物理网络将数据包传输给主机 B
  5. 数据包被传输到主机 B 的网络层,在这里主机 B 拆开 IP 头信息,并将拆开来的数据部分交给传输层
  6. 在传输层,数据包中的 UDP 头会被拆开,并根据 UDP 中所提供的端口号,把数据部分交给上层的应用程序
  7. 最终需要传输的数据包就到了主机 B 上层应用程序这里

UDP头:
和 IP 头一样,端口号会被装进 UDP 头里面,UDP 头再和原始数据包合并组成新的 UDP 数据包。
UDP 头中除了目的端口,还有源端口号等信息。

所以总结来说,IP 通过 IP 地址信息把数据包发送给指定的电脑,而 UDP 通过端口号把数据包分发给正确的程序。

3.3.4 UDP的缺点

UDP传输数据的缺点:

  1. 容易丢包,虽然 UDP 可以校验数据是否正确,但是对于错误的数据包,不提供数据修复和重传机制,只是丢弃当前的包;
  2. 无法得知数据包是否能达到目的地。

这些缺点对于不在意一些数据丢失的程序来说是能接受的,因为UDP简洁快速。比如视频通话,偶尔有点卡卡的,是因为部分数据丢失,但不影响正常通话。但对于一些对于数据完整性要求很高的程序,UDP的数据丢失就无法接受了,比如邮件和文件传输等,需求是所有数据必须送达。

所以在一些对于数据完整性要求很高的情况下,就需要使用TCP协议了。

3.4 页面文件完整送达应用程序

TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议

TCP信息依然放在数据头上,和 UDP头一样,TCP 头除了包含了目标端口和本机端口号外,还提供了用于排序的序列号,以便接收端通过序号来重排数据包。

相对于 UDP,TCP 有下面两个特点:

  1. 对于数据包丢失的情况,TCP 提供重传机制;
  2. TCP可以同时发多个包
  3. 根据拥堵情况动态调整传输率
  4. TCP 引入了数据包排序机制,用来保证把乱序的数据包组合成一个完整的文件。

3.4.1 传输流程

下面看看 TCP 下的单个数据包的传输流程:
在这里插入图片描述
从图中可以看出,TCP传输的主要流程和UDP差不多,不同的地方在于,通过 TCP 头的信息保证了一块大的数据传输的完整性

3.4.2 完整的 TCP 连接过程

下图是完整的 TCP 连接过程,通过这个过程可以明白 TCP 是如何保证重传机制和数据包的排序功能的。

一个完整的 TCP 连接的生命周期包括了“建立连接”“传输数据”和“断开连接”三个阶段
在这里插入图片描述

  1. 首先,建立连接阶段。这个阶段是通过“三次握手”来建立客户端和服务器之间的连接。TCP 提供面向连接的通信传输。
    面向连接是指在数据通信开始之前先做好两端之间的准备工作。
  2. 其次,传输数据阶段。在该阶段,接收端需要对每个数据包进行确认操作,也就是接收端在接收到数据包之后,需要发送确认数据包给发送端,当在规定时间内没有接收到接收端反馈的确认消息,则判断为数据包丢失,并触发发送端的重发机制。
    同样,一个大的文件在传输过程中会被拆分成很多小的数据包,这些数据包到达接收端后,接收端会按照 TCP 头中的序号为其排序,从而保证组成完整的数据。
  3. 最后,断开连接阶段。数据传输完毕之后,就要终止连接了,涉及到最后一个阶段“四次挥手”来保证双方都能断开连接。

3.4.3 三次握手

三次握手是指建立一个 TCP 连接时需要客户端和服务器端总共发送三个包以确认连接的建立。在socket编程中,这一过程由客户端执行connect来触发。

下面来看看三次握手的流程图:
在这里插入图片描述

  • 第一次握手:客户端将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给服务器端,客户端进入SYN_SENT状态,等待服务器端确认
  • 第二次握手:服务器端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务器端将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD状态。
  • 第三次握手:客户端收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给服务器端,服务器端检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。

3.4.4 四次挥手

四次挥手即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发。

由于TCP连接是全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。

下面来看看四次挥手的流程图:(一方主动关闭,另一方被动关闭的情况)
在这里插入图片描述

  • 第一次挥手:客户端发送一个FIN=M,用来关闭客户端到服务器端的数据传送,客户端进入FIN_WAIT_1状态。意思是说"我客户端没有数据要发给你了",但是如果你服务器端还有数据没有发送完成,则不必急着关闭连接,可以继续发送数据。
  • 第二次挥手:服务器端收到FIN后,先发送ack=M+1,告诉客户端,你的请求我收到了,但是我还没准备好,请继续你等我的消息。这个时候客户端就进入FIN_WAIT_2 状态,继续等待服务器端的FIN报文。
  • 第三次挥手:当服务器端确定数据已发送完成,则向客户端发送FIN=N报文,告诉客户端,好了,我这边数据发完了,准备好关闭连接了。服务器端进入LAST_ACK状态。
  • 第四次挥手:客户端收到FIN=N报文后,就知道可以关闭连接了,但是他还是不相信网络,怕服务器端不知道要关闭,所以发送ack=N+1后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。服务器端收到ACK后,就知道可以断开连接了。客户端等待了2ms后依然没有收到回复,则证明服务器端已正常关闭,那好,我客户端也可以关闭连接了。最终完成了四次握手。

上面是一方主动关闭,另一方被动关闭的情况,实际中还会出现同时发起主动关闭的情况

四次挥手的流程图:(同时发起主动关闭)
在这里插入图片描述

3.4.5 TCP的缺点

TCP 为了保证数据传输的可靠性,使用了“三次握手”和“数据包校验机制”等,把传输过程中的数据包的数量提高了一倍,牺牲了数据包的传输速度,所以实际应用当中会综合场景使用UDP和TCP。

3.5 小结

这章主要介绍以下几个方面:
1.TCP/IP不仅是指 TCP 和 IP 两种协议,在很多情况下,它更是利用 IP 进行通信时所必须用到的协议群的统称
2.IP 负责把数据包送达目的主机。UDP 负责把数据包送达具体应用,但在传输过程中容易丢失或出错。而 TCP 保证了数据完整地传输
3.TCP的连接可分为三个阶段:建立连接、传输数据和断开连接。
4.另外介绍三次握手和四次挥手的具体过程

第四章 HTTP网络请求

上一章介绍了一个 TCP 连接过程包括了建立连接、传输数据和断开连接三个阶段。Http协议便是建立在TCP连接的基础上。

HTTP 是一种允许浏览器向服务器获取资源的协议,是 Web 的基础,通常由浏览器发起请求,用来获取不同类型的文件,例如 HTML 文件、CSS 文件、JavaScript 文件、图片、视频等。此外,HTTP 也是浏览器使用最广的协议。

接下来将分析完整的 HTTP 请求过程(简略版的过程):

  1. 浏览器根据域名解析IP地址
  2. 浏览器与WEB服务器建立一个TCP连接
  3. 浏览器给WEB服务器发送一个HTTP请求
  4. WEB服务器响应HTTP请求
  5. 浏览器解析资源
  6. 关闭TCP连接,浏览器对页面进行渲染呈现给用户

4.1浏览器根据域名解析IP地址

当在浏览器地址栏里键入一个网站的地址如https://www.csdn.net/, 那么接下来,浏览器会完成哪些动作呢?

4.1.1 构建请求

首先,浏览器构建请求行信息,构建好后,浏览器准备发起网络请求

4.1.2 查找缓存

在真正发起网络请求之前,浏览器会先在缓存中查询是否有要请求的文件,查找缓存过程如下:

1.浏览器缓存:浏览器缓存是一种在本地保存资源副本,以供下次请求时直接使用的技术。浏览器会先在浏览器缓存中查询是否有要请求的文件(缓存的时间比较短,大概只有1分钟,且只能容纳1000条缓存),看自身的缓存中是否是有域名对应的条目,而且没有过期,如果有且没有过期,则浏览器会拦截请求,返回该资源的副本,并直接结束请求,不会再去源服务器重新下载。

2.系统缓存:如果浏览器自身的缓存里面没有找到对应的条目,那么浏览器会搜索操作系统自身的DNS缓存,如果找到且没有过期则停止搜索解析到此结束。

3.路由器缓存:如果系统缓存也没有找到,则会向路由器发送查询请求

4.ISP(互联网服务提供商)缓存:如果在路由缓存也没找到,浏览器就会发起一个DNS的系统调用,就会向本地配置的首选DNS服务器(一般是电信运营商提供的,也可以使用像Google提供的DNS服务器)发起域名解析请求。运营商的DNS服务器首先查找自身的缓存,找到对应的条目,且没有过期,则解析成功。如果没有找到对应的条目,则有运营商的DNS代我们的浏览器发起迭代DNS解析请求。

DNS:负责把域名和 IP 地址做一 一映射关系。这套域名映射为 IP 的系统就叫做“域名系统”,简称DNS(Domain Name System)。

4.1.3 存储缓存

上面一个小结提到了浏览器会先在缓存中查询,那么浏览器如何进行的缓存?

先看一下缓存查找的流程示意图:
在这里插入图片描述

从图中可以看出,当服务器返回的响应头给浏览器时,浏览器通过响应头中的 Cache-Control 字段来设置是否缓存该资源。此外还需要设置一个缓存过期时间,这个时间通过Cache-Control 中 Max-age来设置。例如图中2000秒过期,就会返回Cache-Control : Max-age = 2000

这也就意味着,在该缓存资源还未过期的情况下如图中1000秒时, 如果再次请求该资源,会直接返回缓存中的资源给浏览器。

但如果缓存过期了如图中2100秒时,浏览器则会继续发起网络请求,并且在HTTP 请求头中带上:If-None-Match:"4f80f-13c-3a1xb12a"

如果没有更新,服务器就返回 304 状态码,相当于服务器告诉浏览器:“这个缓存可以继续使用,这次就不重复发送数据给你了。”

如果资源有更新,服务器就直接返回最新资源给浏览器。

所以很多网站第二次访问能够秒开,是因为这些网站把很多资源都缓存在了本地,浏览器缓存直接使用本地副本来回应请求,而不会产生真实的网络请求,从而节省了时间。同时,DNS 数据也被浏览器缓存了,这又省去了 DNS 查询环节。

对于浏览器缓存机制,推荐一篇讲的很详细的文章:深入理解浏览器的缓存机制

4.2浏览器与WEB服务器建立一个TCP连接

4.2.1 获取IP和端口号

因为浏览器使用HTTP 协议作为应用层协议,用来封装请求的文本信息;并使用TCP/IP 作传输层协议,将文本信息发到网络上,所以在 HTTP 工作开始之前,浏览器需要通过 TCP 与服务器建立连接。

通过第一步的解析域名获取到了对应的IP地址,拿到 IP 之后,接下来就需要获取端口号了。通常情况下,如果 URL 没有特别指明端口号,那么 HTTP 协议默认是 80 端口。

4.2.2 等待TCP队列

现在已经把端口和 IP 地址都准备好了,那么下一步是不是可以建立 TCP 连接了呢?

答案依然是“不行”。Chrome 有个机制,同一个域名同时最多只能建立 6 个 TCP 连接,如果在同一个域名下同时有 10 个请求发生,那么其中 4 个请求会进入排队等待状态,直至进行中的请求完成。

当然,如果当前请求数量少于 6,会直接进入下一步,建立 TCP 连接。

4.2.3 建立TCP连接

排队等待结束之后,终于可以快乐地和服务器握手了。
而完整的TCP连接过程请看第三章内容。

4.3 浏览器给WEB服务器发送一个HTTP请求

一旦建立了TCP连接,浏览器就可以和服务器进行通信了。而HTTP中的数据就是在这个过程中进行传输的。

首先看一下http请求数据的格式:
在这里插入图片描述
一个HTTP请求报文由请求行(request line)、请求头部(headers)和请求体(request body)3个部分组成。

接下来分别介绍这几部分:

4.3.1 请求行

请求行分为三个部分:

  1. 请求方法:HTTP/1.1 定义的请求方法有8种:GET(完整请求一个资源)、POST(提交表单)、PUT(上传文件)、DELETE(删除)、PATCH、HEAD(仅请求响应首部)、OPTIONS(返回请求的资源所支持的方法)、TRACE(追求一个资源请求中间所经过的代理)。最常的两种GET和POST,如果是RESTful接口的话一般会用到GET、POST、DELETE、PUT
  2. 请求URL:统一资源定位符,是一种资源位置的抽象唯一识别方法
    组成:<协议>://<主机>:<端口>/<路径>
    端口和路径有时可以省略(HTTP默认端口号是80)
  3. HTTP协议版本: 协议版本的格式为:HTTP/主版本号.次版本号,常用的有HTTP/1.0和HTTP/1.1

4.3.2 请求头

浏览器除了发送请求行,还要以请求头形式发送其他一些信息,把浏览器的一些基础信息告诉服务器。比如包含了浏览器所使用的操作系统、浏览器内核等信息,以及当前请求的域名信息、浏览器端的 Cookie 信息等。

请求头部为请求报文添加的附加信息,由“名/值”对组成,每行一对,名和值之间使用冒号分隔。
在这里插入图片描述

4.3.3 请求体

可选部分,不在GET方法中使用,而在POST方法中使用。POST方法适用于需要客户填写表单的场合。与请求数据相关的最长使用的请求头部是Cntent-Type和Content-Length。

下面是一个POST方法的请求报文:

POST  /index.php HTTP/1.1    请求行
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:10.0.2) Gecko/20100101 Firefox/10.0.2  请求头
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Language: zh-cn,zh;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: http://localhost/
Content-Length:25
Content-Type:application/x-www-form-urlencoded
  空行
username=aa&password=1234  请求体

4.4 WEB服务器响应HTTP请求

历经千辛万苦,http的请求信息终于到了服务器,接着服务器开始会根据浏览器的请求信息准备相应的内容了。

看一下http请求数据的格式:

在这里插入图片描述
和HTTP请求报文相似,返回的数据包含响应行、响应头和响应体的数据,接下来分别介绍这几部分内容:

4.4.1 响应行

响应行包含协议版本,状态码和状态码描述

其中协议版本和请求报文中的协议版本一样,状态码描述是对状态码简单的描述。

因为不是所有的请求都可以被服务器处理,对于一些无法处理或者处理出错的信息,服务器会通过状态码来告诉浏览器它的处理结果。

接着介绍一下状态码,这个也是经常面试要问的:

状态码是由3位数组成:
1xx : 指示信息 — 表示请求已接受,继续处理
2xx: 成功 — 表示请求已被成功接收、理解、接受
3xx: 重定向 — 要完成请求必须进行更进一步的操作
4xx: 客户端错误 — 请求有语法错误或请求无法实现
5xx: 服务端错误 — -服务器未能实现合法的请求

列举几个常见的:
在这里插入图片描述

4.4.2 响应头

正如浏览器会随同请求发送请求头,服务器也会随同响应发送响应头

响应头包含服务器自身的一些信息,比如服务器返回数据的时间,返回的数据类型,服务器要在客户端保存的cookie信息。

常见的响应头部如下:
在这里插入图片描述

4.4.3 响应体

发送完响应头之后,服务器就可以开始发送响应体的数据了,响应体里面包含了HTML的实际内容。

下面是一个响应报文的实例:

HTTP/1.1 200 OK  响应行
Date: Sun, 17 Mar 2017 08:12:54 GMT  响应头
Server: Apache/2.2.8 (Win32) PHP/5.2.5
X-Powered-By: PHP/5.2.5
Set-Cookie: PHPSESSID=c0huq7pdkmm5gg6osoe3mgjmm3; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 4393
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=utf-8
  空行
<html>  响应体
<head>
<title>HTTP响应示例<title>
</head>
<body>
Hello HTTP!
</body>
</html>

4.5 浏览器解析HTML代码,并请求HTML代码中的资源

浏览器拿到HTML文件后,开始解析HTML代码,遇到静态资源时,就向服务器端去请求下载。

4.6 关闭TCP连接,浏览器对页面进行渲染呈现给用户

当服务器向客户端返回了请求数据,就需要关闭TCP连接。而关闭的流程就是四次挥手,具体的步骤请见 本文章3.4 四次挥手 。

之后浏览器利用自己内部的工作机制,把请求到的静态资源和HTML代码进行渲染,呈现给用户。

到此为止,就是正常情况下整个完整的HTTP请求流程

不过再说下其他情况

4.6.1 Connection:Keep-Alive

通常情况下,一旦服务器向客户端返回了请求数据,它就要关闭 TCP 连接。但是如果浏览器或者服务器在其头信息中加入了Connection:Keep-Alive,那么TCP连接在返回了数据后仍会保持连接状态,这样浏览器就可以使用同一个TCP连接继续发送消息。保持TCP连接可以节约本次断开连接和下次请求重新建立连接的时间,提升资源加载速度。 请求是按照顺序的,也就是符合IP+端口规则的资源都可以复用该连接。

keep-alive本身是为了解决连接效率不高的问题,http1.0时代,http请求都是通过短连接的形式,也就是每次请求一个资源都需要和服务器建立连接+传输数据+断开连接,有时候建立连接和断开连接的时间都有可能超过传输数据的时间,这种效率就太低效了。针对这种情况,提出来keep-alive。

但是使用keep-alive同样存在问题,比如一个页面可能有100张图片素材,假设这些图片素材都保存在同一个域名下面,如果只复用一个http管道的话,那么传输100张图片的素材也是非常耗时间的,这就出现了同一时刻并发连接服务器的需求,也就是文中提到同一时刻,对同一域名下面,可以发起6个请求,这样就可以大大提升请求效率了。但是为什么不能使用比6个还多的请求呢?这是为了服务器性能考虑,如果同一时刻无限制连接,那么可能会导致服务器忙不过来。

4.6.2 重定向

我们有时候会遇到这种问题,我们登入一个地址,但是浏览器却跳转到其他页面,例如登入页面,这种情况就是涉及了重定向。

首先看一下响应报文的响应行和响应头:
在这里插入图片描述
根据上面4.4.1和4.4.2里面提及的,在响应行中的状态码是301,告诉浏览器需要重定向到另一个网址,而重新跳转的路径在响应头的Location 字段中,接下来浏览器获取 Location 字段中的地址,并使用该地址重新导航,这就是一个完整重定向的执行流程。

4.7 登录状态是如何保持

我们有时候登入一个网站,在之后再度访问这个网站时,我们无需再度登入,而是直接保持了当前的登陆状态,接下来就说一下这种情况是如何实现的。

  1. 用户打开登陆页面,在登录框里输入用户名等信息,点击提交等按钮时触发页面脚本生成用户登录信息,然后调用post方法提交用户信息给服务器;
  2. 服务器接收到浏览器提交的用户信息后,查询后台进行验证信息。如果信息正确,就会生成一段表示用户身份的字符串,并把这个字符串写到响应头的Set-Cookie字段里如Set-Cookie: UID=3431uad;
  3. 浏览器在收到服务器的响应头后,开始解析响应头,如果响应头里面有Set-Cookie字段,浏览器就会把这个字段信息保存到本地;
  4. 当用户再次访问时,浏览器会发起HTTP请求,但在发起请求之前,浏览器会读取之前保存的cookie数据,并把数据写进请求头里的Cookie字段里如Cookie: UID=3431uad;,然后浏览器再将请求头发送给服务器;
  5. 服务器在收到HTTP请求头数据之后,就会查找请求头里面Cookie字段信息,当查找到包含UID=3431uad的信息时,服务器查询后台,并判断用户是已登录状态,然后生成该用户信息的页面数据,并把生成的数据发送给浏览器。
  6. 浏览器在接收到该含有当前用户的页面数据后,就可以正确展示用户登录的状态信息了。

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

4.8 小结

这一章主要介绍了HTTP整个网络请求的流程,现在总结的说一下:
在这里插入图片描述
浏览器中整个HTTP请求流程如图所示,一共经历八个阶段:

  1. 构建请求
  2. 查找缓存
  3. 准备IP和端口号
  4. 等待TCP队列
  5. 建立TCP链接
  6. 发起HTTP请求
  7. 服务器处理请求并响应HTTP请求
  8. 断开TCP连接

除了介绍整个流程,还介绍了如何存储缓存,如何重定向和使用Cookie进行状态管理。

第五章 浏览器渲染

当我们拿到了http响应数据之后,就需要开始进行页面的渲染。接下来就开始介绍浏览器的渲染流程。

首先知道一件事渲染是做什么的,渲染可以简单理解为 就是将 html,css和js数据经过一系列的处理之后,最后输出为屏幕上的像素。

渲染的机制十分复杂,整个流程会分为很多部分,我们把这样的流程叫做渲染流水线,其大致流程如下图所示:
在这里插入图片描述

按照渲染的顺序 ,大致分为以下几个部分:

  1. 解析HTML构建DOM树
  2. 样式计算
  3. 布局阶段
  4. 将渲染树每个节点绘制到屏幕

在介绍每个阶段的过程中,应重点关注以下三点内容:

  1. 开始每个子阶段都有其输入的内容;
  2. 然后每个子阶段有其处理过程;
  3. 最终每个子阶段会生成输出内容。

理解了这三部分内容,能更加清晰地理解每个子阶段。

接下来开始整个流程:

4.1 解析HTML构建DOM树

4.1.1 什么是DOM树

因为浏览器无法直接理解和使用HTML,所以需要将HTML转换为浏览器可以理解的结构——DOM树 。

4.1.2 DOM树的构建过程

DOM 树的构建过程,可以参考下图:
在这里插入图片描述
从图中可以看出,DOM树的构建过程很简单,输入内容为一个HTML文件,然后经过HTML解析器进行解析,最后输出一个树状结构的DOM树 。

4.1.3 css加载会阻塞DOM的解析和渲染吗?JS呢?

推荐文章:
css加载会造成阻塞么?
css会阻塞页面dom解析吗?javascript呢?

建议看这部分之前,先看4.2的部分,然后再回头看这个问题。

先说结论:

1.JS的执行会阻塞后续DOM的解析和渲染

如果把JS放在顶部,浏览器再遇到script标记时,DOM构建将会暂停,直到脚本执行完毕,然后才继续DOM的构建。

所以建议我们写代码时候,把html写在页面顶部,JS写在下方,提高效率。

2.CSS加载会阻塞DOM树的渲染,但是不会阻塞DOM树的解析

不用浏览器使用的内核不同,所以他们的渲染过程也是不一样的。目前主要有两个:
在这里插入图片描述
从图中可以看出,DOM解析和CSS解析是两个并行的过程,所以css的加载不会阻塞DOM树的解析,但是渲染是需要DOM树和CSS规则树的,所以渲染需要等待CSS加载完成(或失败)后才能开始渲染,所以css的加载会阻塞DOM的渲染。

对于这种机制是可以理解的,毕竟css没有加载出来,不清楚会不会修改下面DOM节点的样式,如果css加载不阻塞DOM树渲染的话,那么当css加载完之后,有可能会让DOM树重排或者重绘,造成了没必要的消耗。所以干脆就先把DOM树的结构先解析完,把可以做的工作做完,然后等你css加载完之后,在根据最终的样式来渲染DOM树。

3.css的加载会阻塞后面的js语句的执行

由于js可能会操作之前的Dom节点和css样式,因此浏览器会维持html中css和js的顺序,样式表会在后面的js执行前先加载执行完毕。所以css会阻塞后面js的执行。

4.2 样式计算

通过第一步我们已经得到了一个DOM树,但是这些节点的样式我们并不清楚,所以要让 DOM 节点拥有正确的样式,这就需要样式计算了。

样式计算大体分为三个阶段:

  1. 解析CSS文件,生成CSS规则树;
  2. 转换样式表中的属性值,使其标准化;
  3. 计算DOM树每个节点的具体样式;

接下来详细介绍这三个阶段:

4.2.1 解析CSS文件生成CSS规则树

在这里插入图片描述
从图中可以看出CSS引入的样式主要有三种:

  1. 通过link引用外部css文件;
  2. <style>标记内的css;
  3. 元素的style属性内嵌的css

和HTML文件一样,浏览器也无法解析这些CSS的样式,所以也需要将CSS转换为浏览器可以理解的结构——styleSheets. 每个styleSheets里面都包含css规则。

只需要在控制台中输入 document.styleSheets,然后就看到如下图所示的结构:
在这里插入图片描述
当然,这个不是重点,只是单纯展示一下转换后的结构是什么样。在这步里,我们只需要知道渲染引擎会把获取到的 CSS 文本全部转换为 styleSheets 结构中的数据,并且该结构同时具备了查询和修改功能,这会为后面的样式操作提供基础。

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

我们平时写css属性时候,有时候会这么写

body {
    
     font-size: 2em }
p {
    
    color:blue;}
span  {
    
    display: none}
div {
    
    font-weight: bold}
div  p {
    
    color:green;}
div {
    
    color:red; 

对于其中 em,red,bold等这些类型的值也不容易被浏览器理解,所以我们也需要将这些值进行转换为渲染引擎容易理解的、标准化的计算值,这个过程就是属性值标准化。

接下来看一下标准化后的样式:
在这里插入图片描述
可以看出,转换后2em 被解析成了 32px,red 被解析成了 rgb(255,0,0),bold 被解析成了 700等等,其实这个过程也给我们提个醒,在我们写样式的时候,也最好按照标准化后的样式来写,这样也能提高一些渲染的效率。

4.2.3 计算DOM树中每个节点的具体样式

现在样式的属性已被标准化了,接下来就需要计算 DOM 树中每个节点的样式属性了,在这部分涉及到 CSS继承规则和层叠规则

CSS继承规则:每个DOM节点都会有父节点的样式;
层叠规则:层叠是 CSS 的一个基本特征,它是一个定义了如何合并来自多个源的属性值的算法,它在 CSS 处于核心地位;

详细的内容推荐层叠与继承

总之,样式计算阶段的目的是为了计算出 DOM 节点中每个元素的具体样式,在计算过程中需要遵守 CSS 的继承和层叠两个规则。这个阶段最终输出的内容是每个 DOM 节点的样式,并被保存在 ComputedStyle 的结构里。

4.3 布局阶段

现在我们有 DOM 树和 DOM 树中元素的样式,但是还是出不来图,因为我们不知道DOM元素的几何位置,接下来就要计算DOM树中可见元素的几何位置,这个过程叫布局

布局分为两个阶段:创建布局树和布局计算

4.3.1 创建布局树

DOM 树还含有很多不可见的元素,比如 head 标签,还有使用了 display:none 属性的元素。所以在显示之前,我们还要额外地构建一棵只包含可见元素布局树

接下来看看布局树的构造过程:
在这里插入图片描述
从图中可以看出,DOM 树中所有不可见的节点都没有包含到布局树中。

为了构建布局树,浏览器大体上完成了下面这些工作:

  • 遍历 DOM 树中的所有可见节点,并把这些节点加到布局中;
  • 而不可见的节点会被布局树忽略掉,如 head 标签下面的全部内容,再比如
    body.p.span 这个元素,因为它的属性包含 dispaly:none,所以这个元素也没有被包进布局树。

4.3.2 布局计算

现在我们有了一棵完整的布局树。那么接下来,就要计算布局树节点的坐标位置了,获取每个节点的具体位置。

4.4 绘制阶段

有了完整的布局树,并且每个节点也都有了坐标信息,接下来开始进入最后的绘制阶段。
绘制的部分也有很多流程,在绘制之前需要进行分层处理,在绘制中也分为多个部分:绘制、分块、光栅化和合成,接下来对这部分进行详细介绍。

4.4.1 分层

因为页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎需要为这些特殊节点生成专用的图层,并生成对应的图层树( LayerTree).

浏览器的页面实际上被划分为很多图层,这些图层叠加后才形成了最终页面。

接下来看看图层和节点之间的关系:
在这里插入图片描述
通常情况,并不是每一个节点都对应一个图层,如果一个节点没有对应的图层,那边这个节点就从属于父节点的图层。就像图中span的标签没有专属图层,那边他就属于父节点div的图层。

4.4.2 图层绘制

完成图层树构建之后,渲染引擎会对图层树中的每个图层进行绘制。渲染引擎首先会把一个图层的绘制分成多个小的绘制指令,然后再把这些绘制指令按照顺序组成一个待绘制列表,如下图所示:
在这里插入图片描述

4.4.3 光栅化(raster)

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

合成进程需要做的几步:

  1. 为了降低渲染开销,将图层划分为图块;
  2. 合成进程按照视图附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。通常,栅格化过程都会使用 GPU 来加速生成,;

4.4.4 合成化

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

浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。

到这里,经过这一系列的阶段,编写好的 HTML、CSS、JavaScript 等文件,经过浏览器就会显示出漂亮的页面了。

4.5 概念补充 -——重排,重绘和合成

4.5.1 重排 (reflow)

更新元素的几何属性需要重排。

在这里插入图片描述
从上图可以看出,如果通过 JavaScript 或者 CSS 修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排

重排需要更新完整的渲染流水线,所以开销也是最大的。

4.5.2 重绘(repaint)

通过 JavaScript 更改某些元素的背景颜色,会触发重绘

在这里插入图片描述
从图中可以看出,因为只修改了元素的背景颜色,并没有引起几何位置的变换,那么布局阶段将不会被执行,直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘

相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。

4.5.3 直接合成

在这里插入图片描述
如图中显示,只用transform来实现动画效果,既不更改更改页面布局也不更改样式,渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做合成

因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。

4.6 小结

本章主要介绍浏览器渲染的流程,一个完成的渲染流程大致如下:

  1. 生成DOM树;将HTML文件解析成浏览器可以理解的DOM树;
  2. 进行样式计算:分为三步:a.将CSS解析成浏览器可以理解的CSS规则树;b.将属性值标准化;c.计算每个DOM节点的具体样式,这部分涉及CSS继承和成叠的规则;
  3. 进行布局:分为两步:a.构建布局树;b.进行布局计算,布局运算的结果重新写回布局树中;
  4. 进行绘制:分为4步:a.绘制之前需要进行分层,生成分层树;b.分层之后为每个图层生成绘制列表,然后提交的合成线程中;c.在合成线程进行光栅化生成位图; d.最后发送绘制图块命令DrawQuad给浏览器进程;
  5. 浏览器进程根据DrawQuad命令生成页面,显示到显示器上;

第六章 从输入URL到页面展示的总流程

到这里为止整个总流程所需要涉及的知识点大体都已经介绍了,接下来就把这些知识点串联在一起,总体梳理一遍从输入URL到页面展示的总流程。
首先看一下总体结构图,这个在本文开始时候有过展示:
在这里插入图片描述
从图中看出,整个过程首先需要多个进程之前的相互配合:

  • 浏览器进程负责页面展示,用户交互,子进程管理等等
  • 网络进程负责网络资源的加载,如http请求等,面向浏览器进程和渲染进程
  • 渲染进程负责将HTML,CSS,JS,图片等资源进行解析为可以显示和交互的页面

回顾完进程的作用之后,就可以对整个过程进行描述。描述分为两个版本,一个简单版,一个复杂版(面试问答这个题,可以问问面试官你要哪个版本啊~~)

6.1 简单版

  • 首先用户从浏览器输入请求信息
  • 然后网络发起URL请求
  • 服务器响应请求之后,浏览器进程准备渲染进程
  • 渲染进程准备好之后,向渲染进程提交页面数据,称之为提交文档阶段
  • 渲染进程接受完文档信息,开始进行页面解析和资源加载
  • 最后完成整个页面加载

6.2 复杂版

  1. 首先用户从浏览器输入请求信息,这时候地址栏会判断输入的信息是搜索的内容还是请求的URL
    (1)如果是搜索内容,地址栏会使用浏览器默认的搜索引擎,来合成新的带搜索关键字的URL
    (2)如果是请求的URL,会判断是否符合URL规则,如果符合,就把这个内容加上协议,合成完整的URL
  2. 接着浏览器进程会通过进程间通信(IPC)把URL请求发送给网络进程。网络进程接收到这个URL请求之后,首先查找缓存是否缓存了这个资源,如果有缓存则直接返回资源给浏览器进程;如果在缓存中没有查到资源,那么直接进入网络请求流程。
  3. 通过上一步获得的域名和端口开始进行网络请求,在请求之前,如果是HTTPS,还需要建立TLS链接。然后利用IP地址和服务器建立TCP连接。建立连接之后,浏览器开始构建请求行,请求头等信息,并把和该域名相关的cookie等数据附加到请求头中,然后向服务器发送构建的请求信息。
  4. 服务器接收到请求信息后,会根据请求信息生成响应数据,并发给浏览器。浏览器拿到响应数据后,开始解析里面的内容,遇到静态资源时,就向服务器端去请求下载。
    (1)如果响应头里面的 状态码为301或302,则说明服务器需要浏览器进行重定向到其他URL,这时网络进程或从响应头的location字段中读取重定向的地址,然后在发起网络请求,一切又回到了开始地方。
    (2)如果状态码为200,则表示一切正常,可以开始处理接下来的数据
    (3)在处理数据时候要注意区分数据类型,不同的数据类型决定了接下来的流程。判断响应体数据类型的字段是content-type,浏览器会根据 Content-Type 的值来决定如何显示响应体的内容。如果是 text/html,就是告诉浏览器返回的是HTML格式,浏览器则会继续进行导航流程;如果是application/octet-stream,则是字节流类型的,会按照下载类型来处理,那么该请求会被提交给浏览器的下载管理器,同时该 URL 请求的导航流程就此结束。
  5. 处理完 响应数据之后,就需要开始准备渲染进程。渲染进程准备好之后,还不能立即进入文档解析状态,因为此时的文档数据还在网络进程中,并没有提交给渲染进程,所以下一步就进入了提交文档阶段。
    (1)提交文档中的文档其实是URL请求的响应体数据,“提交文档”的消息是浏览器进程发出的,渲染进程接到这个消息后,会和网络进程建立进程间通信。
    (2)等文档数据传输完成之后,渲染进程会返回“确认提交”的消息给浏览器进程
    (3)浏览器进程在收到“确认提交”的消息后,会更新浏览器界面状态,包括安全状态,地址栏的URL,前进后退的历史状态,并更新Web页面。
  6. 一旦文档被提交了之后,渲染进程开始页面解析和资源加载。包括构建DOM树,进行样式计算,构建布局树和绘制。最后产出漂漂亮亮的图片了 ^ - ^

参考文献

  1. 极客时间的浏览器工作原理与实现
  2. 进程和线程
  3. Understanding “same-site” and “same-origin”
  4. 一篇文章带你熟悉 TCP/IP 协议(网络协议篇二)
  5. 一次完整的HTTP请求过程
  6. 深入理解浏览器的缓存机制
  7. HTTP请求的完全过程
  8. 一个完整的HTTP请求过程详细
  9. 【干货】十分钟读懂浏览器渲染流程
  10. 深入浅出浏览器渲染原理
  11. 层叠与继承
  12. css加载会造成阻塞么?
  13. css会阻塞页面dom解析吗?javascript呢?

猜你喜欢

转载自blog.csdn.net/baidu_33438652/article/details/106586413
今日推荐