总结为以下六个点
-
浏览器接收到URL,到网络请求线程的开启。
-
一个完整的HTTP请求并的发出。
-
服务器接收到请求并转到具体的处理后台。
-
前后台之间的HTTP交互和涉及的缓存机制。
-
浏览器接收到数据包后的关键渲染路径。
-
JS引擎的解析过程。
网络请求线程开启
浏览器接收到我们输入的url到开启网络请求线程,这个阶段是在浏览器内部完成的。
对URL的解析:协议头、域名或IP地址、端口号、目录路径、查询参数、片段。
将URL解析之后,会组装成http请求报文,传输到服务器。
如果是HTTP协议,浏览器会新建一个网络请求线程去下载所需的资源。
进程和线程
进程就是一个程序运行的实例,操作系统会为进程创建独立的内存,用来存放运行所需的代码和数据;而线程是进程的组成部分,每个进程至少有一个主线程及可能的若干个子线程,这些线程由所属的进程进行启动和管理。由于多个线程可以共享操作系统为其所属的同一个进程所分配的资源,所以多线程的并行处理能有效提高程序的运行效率。
进程和线程的区别
- 只要某个线程执行出错,将会导致整个进程崩溃。
- 进程与进程之间相互隔离。这保证了当一个进程挂起或崩溃的情况发生时,并不会影响其他进程的正常运行,虽然每个进程只能访问系统分配给自己的资源,但可以通过IPC机制进行进程间的通信。
- 进程所占用的资源会在其关闭后由操作系统回收。即使进程中存在某个线程产生的内存泄漏,当进程退出时,相关的内存资源也会被回收。
- 线程之间可以共享所属进程的数据。
建立HTTP请求
这个阶段的主要工作分为两部分:DNS解析和通信链路的建立。
首先发起请求的客户端浏览器要明确知道索要访问的服务器地址,然后建立通往该服务器地址的路径。
DNS解析
首先查询浏览器自身的DNS缓存,如果查到IP地址就结束解析,由于缓存时间限制比较大,一般只有1分钟,同时缓存容量也有限制,所以在浏览器缓存中没找到IP地址时,就会搜索系统自身的DNS缓存;如果还未找到,接着就会尝试从系统的hosts文件中查找。
在本地主机进行的查询若都没有获取到,接下来便会在本地域名服务器上查询。如果本地域名没有直接的目标IP地址可供返回,则本地域名服务器便会采取迭代的方式一次去查询根域名服务器、COM顶级域名服务器和权限域名服务器等,最终将所要访问的目标服务器IP地址返回本地主机,若查询不到,则返回报错信息。
由此可见看出DNS解析是个很耗时的过程,若解析的域名过多,势必会延缓首屏的加载时间。
网络模型
在通过DNS解析获取到目标服务器IP地址后,就可以建立网络连接进行资源的请求与响应了。但在此之前,我们需要对网络架构模型有一些基本的认识,在互联网发展初期,为了使网络通信更加灵活、稳定及可互相操作,国际标准化组织提出了一些网络架构模型:OSI模型、TCP/IP模型,二者的网络模型图示如下。
TCP连接
根据对网络模型的介绍,当使用本地主机连上网线接入互联网后,数据链路层和网络层就已经打通了,而要向目标主机发起HTTP请求,就需要通过传输层建立端到端的连接。
传输层常见的协议有TCP协议和UDP协议,由于本章关注的重点是前端页面的资源请求,这需要面向连接、丢包重发及对数据传输的各种控制,所以接下来详细介绍TCP协议的“三次握手”和“四次挥手”
TCP是面向有连接的通信协议,在数据传输之前需要建立好客户端与服务端之间的连接,即通常所说的“三次握手”。
“三次握手”的作用是双方都能明确自己和对方的收、发能力是正常的。
具体步骤如下:
第1次握手:客户端生成一个随机数seq,假设其值为t,并将标志位SYN设为1,将这些数据打包发给服务器端后,客户端进入等待服务器端确认的状态。
第2次握手:服务端收到客户端发来的SYN=1的数据包后,知道这是在请求建立连接,于是服务端将SYN与ACK均置位1,并将请求包中客户端发来的随机数t加1后赋值给ack,然后生成一个服务器端的随机数seq=k,完成这些操作后,服务端将这些数据打包再发回给客户端,作为对客户端建立连接请求的确认应答。
第3次握手:客户端收到服务器端的确认应答后,检查数据包中的ack的字段值是否位t+1,ACK是否等于1,若都正确就将服务器端发来的随机数加1(ack=k+1),将ACK=1的数据包再发送给服务端以确认服务器端的应答,服务器端收到应答包后通过检查ack是否等于k+1来确认连接是否建立成功。
当用户关闭标签页或请求完成后,TCP连接会进行“四次挥手“断开连接,具体过程如下:
1.由客户端先向服务器端发送FIN=M的指令,随后进入完成等待状态FIN_WAIT_1,表明客户端已经没有再向服务器端发送的数据,但若服务器端此时还有未完成的数据传递,可继续传递数据。
2.当服务器端收到客户端的FIN报文后,会先发送ack=M+1的确认,告知客户端关闭请求已收到,但可能由于服务器端还有未完成的数据传递,所以请客户端继续等待。
3.当服务器端确认已完成所有数据传递后,便发送带有FIN=N的报文给客户端,准备关闭连接。
4.客户端收到FIN=N的报文后可进行关闭操作,但为保证数据正确性,会回传给服务器端一个确认报文ack=N+1,同事服务器端也在等待客户端的最终确认,如果服务器端没有收到报文则会进行重传,只有收到报文后才会真正断开连接。而客户端在发送了确认报文一段时间后,没有收到服务器端任何信息则认为服务器端连接已关闭,也可关闭客户端信息。
前后端的交互
当TCP连接建立好之后,便可通过HTTP等协议进行前后端的通信,但在实际的网络访问中,并非浏览器与确定IP地址的服务器之间直接通信,往往会在中间加入反向代理服务器。
反向代理服务器
对需要提供复杂功能的网站来说,通常单一的服务器资源是很难满足期望的。一般采用的方式是将多个应用服务器组成的集群由反向代理服务器提供给客户端用户使用,这些功能服务器可能具有不同类型,比如文件服务器、邮件服务器及Web应用服务器,同时也可能是相同的Web服务部署到多个服务器上,以实现负载均衡的效果,反向代理服务器的作用如图所示。
反向代理服务器根据客户的请求,从后段服务器上获取资源后提供给客户端。反向代理服务器通常的作用如下:
- 负载均衡
- 安全防火墙
- 加密及SSL加速
- 数据压缩
- 解决跨域
- 对静态资源缓存
后端处理流程
经反向代理收到请求后,具体的服务器后台处理流程大致如下。
- 首先会有一层统一的验证环节,如跨域验证、安全校验拦截等。如果发现是不符合规则的请求,则直接返回相应的拒绝报文。
- 通过验证后才会进入具体的后台程序代码执行阶段,如具体的计算、数据库查询等。
- 完成计算后,后台会以一个HTTP响应数据包的形式发送回请求的前端,结束本次请求。
HTTP 相关协议特性
HTTP是建立在传输层TCP协议之上的应用层协议,在TCP层面上存在长连接和短连接的区别。所谓长连接,就是在客户端与服务端建立的TCP连接上,可以连续发送多个数据包,但需要双方发送心跳检查包来维持这个连接。
短连接就是当客户端需要向服务器端发送请求时,会在网络层IP协议之上建立一个TCP连接,当请求发送并收到响应后,则断开此链接。根据前面关于TCP链接建立过程的描述,我们知道如果这个过程频繁发生,就是个很大的性能耗费,所以从HTTP的1.0版本开始对于链接的优化一直在进行。
在HTTP1.0时,默认使用短链接,浏览器的每一次HTTP操作就会建立一个连接,任务结束则断开连接。
在HTTP1.1时,默认使用长连接,在此情况下,当一个网页的打开操作完成时,其中所建立用于传输HTTP的TCP连接并不会断开关闭,客户端后续的请求操作便会继续使用这个已建立的连接。如果我们对浏览器的开发者工具留心,在查看请求头时会发现一行Connection: keep-alive。长连接并非永久保持,它有一个持续时间,可在服务器中进行配置。
而在HTTP2.0到来之前,每一个资源的请求都需要开启一个TCP连接,由于TCP本身有并发数的限制,这样的结果就是,当请求的资源变多时,速度性能就会明显下降。为此,经常会采用的优化策略包括,将静态资源的请求进程多域名拆分,对于小图标活图片使用雪碧图等。
在HTTP2.0之后,便可以在一个TCP连接上请求多个资源,分割成更小的帧请求,其速度性能便会明显上升,所以之前针对HTTP1.1限制的优化方案也就不再需要了。
HTTP2.0 除了一个连接可请求多个资源这种多路复用的特性,还有如下一些新特性。
- 二进制分帧:在应用层和传输层之间,新加入了一个二进制分帧层,以实现低延迟和高吞吐量。
- 服务器端推送:以往是一个请求带来一个响应,现在服务器可以向客户端的一个请求发出多个响应,这样便可以实现服务器端主动向客户端推送的功能。
- 设置请求优先级:服务器会根据请求所设置的优先级,来决定需要多少资源处理该请求。
- HTTP头部压缩:减少报文传输体积。
浏览器缓存
在基于HTTP的前后端交互过程中,使用缓存可以使性能得到显著提升。具体的缓存策略分为两种:强缓存和协商缓存。
强缓存就是当浏览器判断出本地缓存未过期时,直接读取本地缓存,无须发起HTTP请求,此时状态为200 from cache。在HTTP1.1版本后通过头部的cache-control字段的max-age属性值规定的国旗时长来判断缓存是否过期失效,这比之前使用expires标识的服务器过期时间更准确而且安全。
协商缓存则需要浏览器向服务器发起HTTP请求,来判断浏览器本地缓存的文件是否仍未修改,若未修改则从缓存中读取,此时的状态码为:304。具体过程是判断浏览器头部if-none-match与服务器端的e-tag是否匹配,来判断所访问的数据是否发生改变。这相比HTTP1.0版本通过last-modified判断上次文件修改时间来说也更加准确。
关键渲染路径(CRP)
拿到数据之后,需要将资源渲染到页面当中。
构建对象模型
首先浏览器会通过解析HTML和CSS文件,来构建DOM(文档对象模型)和CSSOM(层叠样式表对象模型)。
浏览器接收读取到的HTML文件,其实是文件根据制定编码(UTF-8)的原始字节,形如3C 62 6F 79 3E 65…。需要将字节转换成字符,即原本的代码字符串,接着再将字符串转化为W3C标准规定的令牌结构,所谓令牌就是HTML中不同标签代表不同含义的一组规则结构。然后经过词法分析将令牌转化成定义了属性和规则值的对象,最后将这些标签节点根据HTML表示的父子关系,连接成树形结构。
构建CSSOM
DOM树表示文档标记的属性和关系,但未包含其中各元素经过渲染后的外观呈现,这便是接下来CSSOM的职责了,与将HTML文件解析未文档对象模型的过程类似,CSS文件也会首先经历从字节到字符串,然后令牌化及词法分析后构建为层叠样式表对象模型。
渲染绘制
当完成文档对象模型和层叠样式表对象模型的构建后,所得到的其实是描述最终渲染页面两个不同方面信息的对象:一个是展示的文档内容,另一个是文档对象对应的样式规则,接下来就需要将两个对象模型合并为渲染树,渲染树中只包含渲染可见的节点。
渲染绘制的步骤大致如下。
- 从所生产DOM树的根节点开始向下遍历每个子节点,忽略所有不可见的节点(脚本标记不可见、CSS隐藏不可见),因为不可见的节点不会出现在渲染树中。
- 在CSSOM中为每个可见的子节点找到对应的规则并应用。
- 布局阶段,根据所得到的渲染树,计算它们在设备时图中的具体位置和大小,这一步输出的是一个“盒模型”。
- 绘制阶段,将每个节点的具体绘制方式转化为屏幕上的实际像素。
执行构建渲染树、布局及绘制过程所需要的时间取决于实际文档的大小。文档越大,浏览器需要处理的任务就越多,样式越复杂,绘制需要的时间就越长,所以关键渲染路径执行快慢,将直接影响首屏加载时间的性能指标。