从输入网址到页面渲染全过程

作为一名前端开发工程师,无论是在面试,还是在日常开发debug中,一定都会遇到一个常见的问题,当一个用户在浏览器输入网址并敲下回车键之后,直到页面完全渲染的这短时间里,到底发生了哪些事情?

DNS解析

DNS是一个把域名和ip地址相互映射的分布式数据库,这样的方式可以让用户能够以更加方便的方式去访问页面,当用户输入了想要访问的网址之后,浏览器便会对这个网址进行解析,以找到对应的服务IP地址。那么解析的过程及顺序是怎样的呢?

首先会找到浏览器缓存,本地的hosts文件DNS缓存。如果这些地方都没有找到对应的记录,则会去请求DNS服务器。而DNS服务又分为三步,首先会走到本地DNS服务,然后依次请求根域名服务器和顶级域名服务器,最终请求权威域名服务器的地址,权威域名服务器会对网址进行解析并返回最终的IP地址给到本地DNS服务器,由它传递到浏览器。此外,本地DNS服务器不仅要把IP地址返回给用户浏览器,还会把这个对应关系保存在缓存中,以备下次别的用户查询时,就可以直接返回结果,提升访问效率。

也就是:

根据上面阐述的过程,我们可以得知域名的DNS解析也是一个性能上的开销,在完成域名解析之前,浏览器不能从服务器加载到任何东西。那么有什么方法可以去尽量减少或者避免这个开销呢?

  • 尽可能的减少DNS请求次数
  • 代码层面上去做DNS预解析
js 复制代码
<link rel='dns-prefetch'href='baidu.com'>
  • 充分利用好CDN进行负载均衡。每个域名在经过DNS解析后会把域名的解析权交给cname()指向的内容分发(CDN)专用的DNS服务器。CDN专用的DNS服务器把CDN的全局负载均衡设备的ip地址返回给用户。最终提高用户的访问速度。

建立TCP连接

在获取准确的IP地址之后,浏览器会先建立TCP连接。TCP是一种面向连接的,可靠的,基于字节流的传输层通信协议。在建立连接的时候,我们需要至少三次握手: 主要的握手过程:

  • 第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN(c),此时客户端处于 SYN_SENT 状态

  • 第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,为了确认客户端的 SYN,将客户端的 ISN+1作为ACK的值,此时服务器处于 SYN_RCVD 的状态

  • 第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,值为服务器的ISN+1。此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接

为什么至少需要三次握手呢?

  • 第一次握手确认了客户端的发送数据能力

  • 第二次握手确认了服务端的接收数据能力

  • 第三次握手确认了服务端的发送数据能力和客户端的接收数据能力。

发起HTTP/HTTPS的请求

在建立完TCP连接之后就可以通过 HTTP 进行数据传输了。HTTP本身是一个明文传输的超文本传输协议,在传输的途中容易遭受到中间人攻击。所以,我们大多数的网站都会使用HTTPS的传输服务,会在 TCP 与 HTTP 之间多添加一层协议做加密及认证的服务。这里还会涉及到一个TLS的握手过程,主要是利用了对称加密和非对称加密相结合的方式,来保障数据传输的安全性。

主要的加密原理:

  1. 用户在浏览器发起HTTPS请求,默认使用服务端的443端口进行连接;
  2. HTTPS需要使用一套CA数字证书 ,证书内会附带一个公钥Pub ,而与之对应的私钥Private保留在服务端不公开;
  3. 服务端收到请求,返回配置好的包含公钥Pub的证书给客户端;
  4. 客户端收到证书,校验合法性,主要包括是否在有效期内、证书的域名与请求的域名是否匹配,上一级证书是否有效(递归判断,直到判断到系统内置或浏览器配置好的根证书),如果不通过,则显示HTTPS警告信息,如果通过则继续;
  5. 客户端生成一个用于对称加密的随机Key ,并用证书内的公钥Pub进行加密,发送给服务端;
  6. 服务端收到随机Key 的密文,使用与公钥Pub 配对的私钥Private 进行解密,得到客户端真正想发送的随机Key
  7. 服务端使用客户端发送过来的随机Key对要传输的HTTP数据进行对称加密,将密文返回客户端;
  8. 客户端使用随机Key对称解密密文,得到HTTP数据明文;
  9. 后续HTTPS请求使用之前交换好的随机Key进行对称加解密。

服务器响应数据

服务器收到了我们的请求之后,会根据相应的请求信息,把它的处理结果返回,也就是返回一个HTPP响应。

HTTP响应与HTTP请求相似,HTTP响应也由3个部分构成,分别是:

  • 状态行
  • 响应头(Response Header)
  • 响应正文

状态行:

状态行由协议版本、数字形式的状态代码、及相应的状态描述,各元素之间以空格分隔。

格式: HTTP-Version Status-Code Reason-Phrase CRLF例如: HTTP/1.1 200 OK

协议版本: 是用http1.0还是其他版本

状态描述: 状态描述给出了关于状态代码的简短的文字描述。比如状态代码为200时的描述为 ok

状态码: 状态代码由三位数字组成,第一个数字定义了响应的类别,且有五种可能取值,如下:

1xx:信息性状态码,表示服务器已接收了客户端请求,客户端可继续发送请求。

  • 100 Continue
  • 101 Switching Protocols
  • 2xx:成功状态码,表示服务器已成功接收到请求并进行处理。

200 OK 表示客户端请求成功

  • 204 No Content 成功,但不返回任何实体的主体部分
  • 206 Partial Content 成功执行了一个范围(Range)请求

3xx:重定向状态码,表示服务器要求客户端重定向。

  • 301 Moved Permanently 永久性重定向,响应报文的Location首部应该有该资源的新URL
  • 302 Found 临时性重定向,响应报文的Location首部给出的URL用来临时定位资源
  • 303 See Other 请求的资源存在着另一个URI,客户端应使用GET方法定向获取请求的资源
  • 304 Not Modified 服务器内容没有更新,可以直接读取浏览器缓存
  • 307 Temporary Redirect 临时重定向。与302 Found含义一样。302禁止POST变换为GET,但实际使用时并不一定,307则更多浏览器可能会遵循这一标准,但也依赖于浏览器具体实现

4xx:客户端错误状态码,表示客户端的请求有非法内容。

  • 400 Bad Request 表示客户端请求有语法错误,不能被服务器所理解
  • 401 Unauthonzed 表示请求未经授权,该状态代码必须与 WWW-Authenticate 报头域一起使用
  • 403 Forbidden 表示服务器收到请求,但是拒绝提供服务,通常会在响应正文中给出不提供服务的原因
  • 404 Not Found 请求的资源不存在,例如,输入了错误的URL

5xx:服务器错误状态码,表示服务器未能正常处理客户端的请求而出现意外错误。

  • 500 Internel Server Error 表示服务器发生不可预期的错误,导致无法完成客户端的请求
  • 503 Service Unavailable 表示服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常

响应头部:

由关键字/值对组成,每行一对,关键字和值用英文冒号":"分隔,典型的响应头有:

location:服务器通过这个头告诉浏览器跳到哪里。

server:服务器通过这个头告诉浏览器服务器的型号。

content-encoding:服务器通过这个头告诉浏览器数据的压缩格式。

content-length:服务器通过这个头告诉浏览器回送数据的长度。

content-language:服务器通过这个头告诉浏览器语言环境。

content-type:服务器通过这个头告诉浏览器回送数据的类型。

refresh:服务器通过这个头告诉浏览器定时刷新。

content-disposition:服务器通过这个头告诉浏览器以下载方式打开数据。

transfer-encoding:服务器通过这个头告诉浏览器数据是以分块方式回送的

以下三个表示服务器通过这个头告诉浏览器不要缓存

expires:-1

cache-control:no-cache

pragma:no-cache

响应正文

包含着我们需要的一些具体信息,比如cookie,html,image,后端返回的请求数据等等。

加载静态资源并渲染页面

在浏览器没有完整接受全部HTML文档时,它就已经开始显示这个页面了,浏览器是如何把页面呈现在屏幕上的呢?不同浏览器可能解析的过程不太一样,这里我们只介绍webkit的渲染过程,下图对应的就是WebKit渲染的过程,这个过程包括:

解析html以构建dom树 -> 构建render树 -> 布局render树 -> 绘制render树

浏览器在解析html文件时,会"自上而下"加载,并在加载过程中进行解析渲染。在解析过程中,如果遇到请求外部资源时,如图片、外链的CSS、iconfont等,请求过程是异步的,并不会影响html文档进行加载。

解析过程中,浏览器首先会解析HTML文件构建DOM树,然后解析CSS文件构建渲染树,等到渲染树构建完成后,浏览器开始布局渲染树并将其绘制到屏幕上。这个过程比较复杂,涉及到两个概念: reflow(回流)和repain(重绘)。

CSS文件的下载和解析并不会阻塞DOM的解析,但是会阻塞Render树的渲染

DOM节点中的各个元素都是以盒模型的形式存在,这些都需要浏览器去计算其位置和大小等,这个过程称为relow;当盒模型的位置,大小以及其他属性,如颜色,字体,等确定下来之后,浏览器便开始绘制内容,这个过程称为repain。

页面在首次加载时必然会经历reflow和repain。reflow和repain过程是非常消耗性能的,尤其是在移动设备上,它会破坏用户体验,有时会造成页面卡顿。所以我们应该尽可能少的减少reflow和repain

当文档加载过程中遇到js文件,html文档会挂起渲染(加载解析渲染同步)的线程,不仅要等待文档中js文件加载完毕,还要等待解析执行完毕,才可以恢复html文档的渲染线程。因为JS有可能会修改DOM,最为经典的document.write,这意味着,在JS执行完成前,后续所有资源的下载可能是没有必要的,这是js阻塞后续资源下载的根本原因。所以我明平时的代码中,js是放在html文档末尾的。

JS的解析是由浏览器中的JS解析引擎完成的,比如谷歌的是V8。JS是单线程运行,也就是说,在同一个时间内只能做一件事,所有的任务都需要排队,前一个任务结束,后一个任务才能开始。但是又存在某些任务比较耗时,如IO读写等,所以需要一种机制可以先执行排在后面的任务,这就是:同步任务(synchronous)和异步任务(asynchronous)。

JS的执行机制就可以看做是一个主线程加上一个任务队列(task queue) 。同步任务就是放在主线程上执行的任务,异步任务是放在任务队列中的任务。所有的同步任务在主线程上执行,形成一个执行栈;异步任务有了运行结果就会在任务队列中放置一个事件;脚本运行时先依次运行执行栈,然后会从任务队列里提取事件,运行任务队列中的任务,这个过程是不断重复的,所以又叫做事件循环(Event loop)。

断开TCP连接

现在的页面为了优化请求的耗时,默认都会开启持久连接(keep-alive),那么一个 TCP 连接确切关闭的时机,是这个 tab 标签页关闭的时候。这个关闭的过程就是四次挥手。关闭是一个全双工的过程,发包的顺序是不一定的。一般来说是客户端主动发起的关闭,过程如下图所示:

  • 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
  • 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
  • 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
  • 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。

为什么交手只需要是三次,而挥手需要四次呢?

交手,是双端从关闭状态转向开启状态,这时候双端并不需要做什么准备。 而相反,结束的时候,双端都需要确认对方的数据已经全部传送完毕了,才能结束。 所以把释放连接的报文FIN和确认接收的报文ACK,分开发送。

相关推荐
passerby606124 分钟前
完成前端时间处理的另一块版图
前端·github·web components
掘了31 分钟前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅34 分钟前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment1 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊2 小时前
jwt介绍
前端
爱敲代码的小鱼2 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte2 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc