面试常问:超详细扩展解读浏览器输入URL后发生了什么

567 阅读25分钟

通过对书籍和各种资料的整理加上自己的理解总结如下,有不对或者不准确的地方大家指出及时更正。

先说发生了哪些步骤

  • 浏览器主进程网络模块接管获取资源
  • DNS查询获取域名对应的ip地址
  • 与目标服务器建立TCP连接
  • https协议会进行ssl握手
  • 向目标服务器发起http请求
  • 服务器处理请求并返回http响应
  • 浏览器进行渲染显示内容并获取其他资源并执行js脚本(包括js执行机制和缓存内容)

每步详解:

一.浏览器相关

之前一直了解浏览器发起请求去获取资源,但是一直不知道浏览器具体是怎么发起请求靠什么发起请求,所以 首先了解浏览器的结构,浏览器是多进程多线程架构,主要包括:

Browser进程(浏览器主进程):

  • 与用户交互(前进后退等)
  • 负责各个进程的管理,是其他进程的祖先,有且只有一个
  • 网络资源的下载
  • 负责浏览器界面的显示,将render进程得到的BitMap绘制到界面上

render进程(可以理解为每一个打开的tab页):

  • 网页的渲染进程,负责页面的渲染工作,渲染和绘制不一样,渲染得到bitmap交给主进程绘制
  • 内部多线程,包括GUI渲染线程,js引擎线程,事件触发线程,定时器线程,http线程

GPU进程:

  • 最多只有一个,当且仅当GPU硬件加速打开的时候才会被创建,主要用于对3D图形加速。

上图中的连线代表进程间的通信,如果没有表明两种不同类型进程之间没有通信。进程间通信比如说Browser进程和Renderer进程依赖于Browser进程中的RenderHost接口和Renderer进程中的renderer接口通信。需要着重理解的一点事,Renderer进程在网页的加载执行中需要获取资源比如需要html,css,js或者ajax请求,Renderer进程是没有权限去获取资源的,处于安全性和资源共享等问题,Renderer进程中的资源实际上是通过进程间通信将任务交给Browser进程来完成的。

发起的http请求的是主进程中的网络模块,它承担了建立网络连接,发送请求数据和接收数据的任务,还包括对http协议,dns解析,ssl等网络方面的支持。

二.DNS寻址

请求一旦发起,在TCP连接前,浏览器首先要做的是域名解析,也就是找到域名对应的IP地址。DNS是域名系统是保存着ip地址和域名映射关系的一个分布式数据库。首先了解几个概念:

  • 根DNS服务器: 有13个根DNS服务器(A-M),可以将13个根DNS服务器视为单个服务器,实际上每台服务器是服务器集群 *顶级域名服务器(TLD): 这些服务器负责顶级域名(com,org,net等)
  • 权威DNS服务器: 因特网上具有公共可访问主机(web服务器)的每个组织机构必须提供可访问的DNS记录,这些记录是域名和IP地址的映射,权威DNS服务器负责保存这些DNS记录。组织结构可以选择实现自己的权威DNS服务器来板保存自己的DNS映射,也可以选择支付费用储存在服务商的权威DNS服务器中。
  • 本地DNS服务器: 当你的电脑通过电信运营商接入网络后,电信运营商会分配给你一个DNS服务器,一般为上网默认的DNS服务器,通常就是你电脑或者手机网络设置设置的DNS服务器(一般都是自动获取),它可以帮你迭代解析查询。
    还有一种情况是,比如说当你遭遇了电信运营商的DNS劫持也就是篡改了某个域名的ip解析结果指向了另一个网站,你可以把手机或电脑里的DNS配置改为114.114.114.114也就是114DNS服务器,是一个第三方可靠的DNS服务平台。

DNS记录:每个DNS应答报文包含一条或多条资源记录,格式为{name, value, type ,ttl}

  • A记录: A记录指的是域名直接和IP映射,name为域名,value为域名对应的ip地址,{xxx.com, 11.11.11.11,A}
  • CNAME: CNAME指的是域名和域名映射也就是别名,name为别名,value为主机名 {'cname', xxx.com, cname}
  • NS记录: NS记录指的是由哪台服务器对该域名进行解析,name是域,value被指向的域名,{xxx.com,dns.xxx.com,NS}
  • 注册域名后再数据库中插入DNS记录的过程: 当向某些注册登记机构注册域名xxx.com的时候,需要向其提供你的权威DNS服务器的域名和IP,假设域名是dns1.xxx.com,ip为111.111.111.111,机构确保将一个NS记录如{xxx.com, dns1.xxx.xom, NS}和一个A记录如{dns1.xxx.com,111.111.111.111,A}输入com的顶级域名服务器,你也必须确保www.xxx.com的A记录输入你的权威DNS服务器中。

DNS查找过程

  • 1.浏览器DNS缓存

因为浏览器网络模块中的HostCache类用来保存解析后的域名。所以先查询浏览器DNS缓存能否命中,命中就返回

  • 2.系统DNS缓存

linux系统中就是/etc/hosts文件,命中返回

  • 3.本地DNS服务器或者网络设置的DNS服务器

先检查能否命中缓存,命中返回,此时拿到的ip地址被标记为非权威服务器应答,终端向本地DNS服务器的查询是递归查询,所谓的递归查询就是终端向本地DNS服务器查询没有得到结果,本地DNS服务器就继续发出查询请求,而不是让终端自己进行下一步查询,所以递归查询返回的是结果IP地址或者报错。客户端只发一次请求,要求对方给出结果。

  • 4.本地DNS服务器向根域名服务器迭代查询

迭代查询可以理解为经历很多的指引操作,本地DNS服务器向根域名服务器查询的过程为迭代查询,根域名服务器收到本地DNS服务器的请求时告诉本地DNS服务器你下一步应该去哪一个域名服务器查询,然后本地服务器继续查询。如果没有在本地DNS服务器命中缓存则在本地DNS服务器的配置文件中找到13个根域名服务器的地址然后向其发起请求,根域名服务器收到这个请求后,比如我们请求www.xxx.com,它知道你这个域名是.com顶级域名下的,就返回负责.com域名的TLD的通常也是13台的NS记录和A记录,根域名服务器不会为任何请求做递归。这步可以用dig +trace www.xxx.com来查看过程

这步是从你的本地DNS服务器中查到了13台根域名服务器的NS记录和A记录,,向其中一台发起请求

这步是本地DNS服务器向其中一台根域名服务器发起查询请求,返回的可以com顶级域名的13个NS记录和A记录 然后本地DNS服务器再向comTLD域名服务器发起查询请求,其实就是去查询我存储在TLD中的域名对应的权威域名服务器的A和NS记录,TLD看到xxx.com就返回负责该域名的权威DNS服务器的NS和A记录,本地DNS服务器在向该权威域名服务器发起查询请求,查询到了www.xxx.com对应的A记录(如果一个域名对应多个IP地址,会负载均衡后返回一个IP地址 )返回给本都DNS服务器再返回给终端。

TIP:CNAME常用于配置CDN,大致过程是将要访问的域名指向CDN服务器的域名,当我们解析我们的域名后得到一个CNAME,重新发起对CNAME的解析,得到一个A记录和一个NS记录是CDN厂商自己的DNS服务器,通常是一个智能DNS服务器,它会根据你的IP地址智能的返回对应的CDN节点的IP地址,然后返回给本地DNS服务器返回给用户。

三.与目标服务器建立TCP连接

浏览器拿到IP地址后就是向目标服务器建立TCP连接,计算机与计算机要相互通信,双方就必须基于相同的方法相同的协议,TCP就是其中一种,TCP提供可靠的字节流服务,为了更容易传送大数据把数据分割,而且TCP协议能够确认数据最终是否到达对方。 TCP建立连接的过程是三次握手,其实也就是客户端和服务器的TCP软件来交换一系列的Ip分组来确认双方收发消息的能力,首先了解下交换的数据的结构。

  • IP分组:从TCP一端填入的字节会从另一端以原有的顺序传出来。TCP的数据是通过名为IP分组的小数据块发送来的,HTTP要传送一条报文时,会以流的形式将报文数据的内容通过一条打开的TCP连接按序传输,TCP收到http报文数据流后,会将数据流分成一段段的小数据块并封装在IP分组中,每个IP分组的结构为一个IP分组首部,一个TCP段首部,一个TCP数据块
    TCP段首部中FIN,ACK,SYN表示报文标识位,当值为1的时候有意义
  • SYN:表示该数据块为SYN报文在建立TCP连接的时候使用
  • ACK:表示确认序列号ack有意义
  • FIN:表示没有数据要发送了,在关闭报文时候使用
  • TCP段序号(seq):该TCP段的第一个字节序列号,序列号是按顺序给发送数据的每一个字节都标上号码编号,序列号的初始值并非为0,而是随机生成的数,后面的字节每一个字节加1。
  • 捎带的确认(ack):确认序列号,表明自己下一步应该接收的序列号,也就是期待IP分组的序列号,这个序列号是根据接收到的IP分组的首部中的序列号(seq)和TCP数据块中的数据字节长度计算出来的,是根据最后一个字节的序列号加+1,如果要是三次握手阶段前两次握手传递的IP分组是没有TCP数据块的也就是TCP数据块是0个字节,这两次传递的ACK就相当于seq+1,首先要理解一个逻辑就是TCP再传送数据包的时候也就是IP分组承载的TCP数据段发送给对方之后需要接到对方的确认应答(ACK),如果一定时间内没有确认应答则发生了丢包,会进行重发。之所以说前两次握手不带TCP数据段是因为现代的TCP栈都允许在第三次握手的时候发送数据。还要注意的是TCP的数据块长度没有写入IP分组中,实际中是靠IP首部中的数据包长度-IP首部长度-TCP首部长度得到。如图

三次握手过程

为什么要进行三次握手: 为了确认双方接收与发送能力是否正常 第一次: 服务端收到,服务端可以确认客户端发送能力正常,服务器接收能力正常 第二次:客户端收到,客户端可以确认,服务端接收发送能力正常,客户端接收发送能力正常 第三次:服务器收到,服务器可以确认客户端接收消息能力正常。

  • 客户端向服务器发送一个IP分组,包含SYN标志位,说明是一个请求建立连接的报文,seq随机生成为k
  • 服务器端接收到包含SYN的报文,回应一个IP分组包含SYN标志位表明服务器也要跟客户端建立连接,seq随机生成b,还包含ACK标志,ack为k+1
  • 客户端收到服务器的包含SYN,ACK的报文回一个包含ACK标志,ack=b+1,seq=k+1 至此TCP连接建立成功可以传输数据。

四次挥手过程

断开连接的过程需要传递四次数据,为什么是四次挥手: 当客户端要关闭连接时,服务器端收到了FIN,仅仅表示客户端不在发送数据了,但是如果这时候服务器还在向客户端发送正常数据,客户端还可以接收,所以先回一个ack告诉客户端,我知道了,然后等客户端接收正常数据完毕后在发送FIN说,服务器也要关闭了,所以断开连接时服务器的ack和FIN分开发送

整个过程至少需要来回发送7个数据包才能完成。

滑动窗口

以每个段为单位进行一次确认应答,网络吞吐量较差,为解决这个问题TCP引入窗口的概念,确认 应答不再是以每个分段而是以更大的单位进行确认,在一定范围内发送一个包后不必一直等待确认应答而是继续发送,窗口大小就是指无需等待确认应答而可以继续发送的数据的最大值,窗口中的数据不必发送某一段之后等待确认应答而是可以继续发送,某段数据在收到确认应答之前必须在缓冲区中保留这份数据,缓冲区(buffer)是临时存放收发数据的场所计算机中的一块内存,收到确认应答后就不必在重发就可以在缓存区中清除。收到确认应答后,将窗口移到应答中序列号的位置,这样可以顺序的将多个段同时发送提高通信性能,这种机制被称为滑动窗口机制。

如果要是滑动窗口发送过程中段丢失,第一种情况是数据到达服务器但是确认应答过程中丢失,这种是不需要在进行重发的,可以通过下一个确认应答进行确认,因为服务器端一定会按顺序返回ACK(也按顺序接收),如果收到了第三段的ACK就说明第二段一定发到了服务器只不过回程的过程丢了,不必再重发。第二种情况是发送过程中段丢失,如果接收端收到应该接收的序号意外的数据,也就是收到的段序号不连续不会将数据丢弃而是保存在缓冲区,会针对当前为止收到数据返回ACK,比如如果第二段丢失服务器目前只收到了第一段seq为1,回的ack为1001期待下一段的数据seq为1001,但是第二段数据丢失,服务器收到第三段的seq为2001,会把第三段放到缓冲区,回第一段数据的ACK期待下一段数据seq为1001。当发收端连续三次接收到同一个确认应答会对此包进行重发。如果重新收到发送端发送的段,接收端会回应一个缓冲区中最新段的ACK。

四.SSL握手

TCP连接建立成功后,可以开始传输HTTP报文,但是要是HTTPS协议的话就要先进行SSL握手,然后通信受SSL保护,然后开始发送HTTP请求。HTTPS并不是应用层的新协议可以理解成身披SSL协议外壳的HTTP。先说下为什么要用HTTPS通信,因为HTTP协议的缺点:

  • 传输数据使用明文,没有加密,容易被窃听: 比如传输报文的时候没有对请求和响应内容进行加密,通过抓包工具就能抓到传输的内容
  • 无法证明报文完整性,收到的报文可能已被篡改,无法判断收到的信息是否准确
  • 没有验证通信方的身份,有可能遭遇伪装 所以HTTPS=HTTP+加密+认证+完整性保护。

加密算法

  • 对称密钥加密 是指加密解密使用同一个密钥的加密方式,这种方式加密时必须将密钥也发送给对方,但是怎样能安全的传输密钥是个问题,如果通信被监听密钥也会暴露,加密就没有意义,而且可以这么想,如果密钥能够安全送达数据也就可以安全送达就没必要加密了。
  • 非对称加密 发送一方使用对方的公开密钥进行加密,公开密钥可以随意发布,对方收到加密信息后再使用私有密钥进行解密,私有密钥不能让其他人知道。非对称加密也有问题,那就是发送方使用对方的公钥也是要传送过来的,如果传送过来的公钥被替换了也无法证明收到的公钥是对方发送的公钥。所以要使用数字证书认证机构(CA)颁发的公开密钥证书。先要向CA申请证书,提供给CA自己的公钥,然后CA会对公钥做数字签名,然后把公钥和数字签名绑定在一起为数字证书。数字证书=数字签名+公钥信息。数字签名=对公钥信息进行hash然后再对hash进行私钥加密。

HTTPS

HTTPS采用的是混合加密机制,在交换密钥环节使用非对称加密,交换报文阶段使用对称加密。因为非对称加密更复杂,所以在交换报文阶段使用非对称加密效率会比较低。也就是说用非对称加密的公钥对对称加密的密钥进行加密。然后用对称密钥的密钥进行加密报文。交换公钥的过程如图:

第三步验证数字签名的过程是用同样的hash算法对公钥信息进行生成hash和用埋在浏览器中的公钥对数字签名进行解密的得到的hash进行对比,相同则验证通过。这样就可以确保拿到的公钥是没被篡改的服务器的公钥。下面整体看下https通信的步骤也就是ssl握手的步骤,此步骤发生在TCP建立连接之后,HTTP报文传送之前。

  • 客户端发送报文开始SSL通信
  • 服务器可进行SSL通信回应响应
  • 服务器发送公钥证书
  • 客户端验证证书,并用证书中的公钥对对称加密的密钥进行加密发送给服务器,然后服务器用非对称加密的私钥对加密的对称加密密钥解密,之后得到的报文或者发送的报文都用对称加密密钥加解密,客户端也是用对称加密的密钥对收发消息进行加解密。
  • 交换报文完毕SSL连接建立完成,通信收到SSL保护,然后开始应用层的HTTP通信。

发送HTTP请求接收HTTP响应

TCP连接建立完成,开始发送HTTP请求,继续传输IP分组。HTTP协议规定,请求从客户端发出,服务器端响应该请求并返回。

HTTP报文分为请求报文和响应报文。HTTP报文本身是由多行数据构成的字符串文本。

  • 请求报文:包括请求行,请求头,请求实体。请求行包括方法,URI,协议版本
  • 响应报文:包括响应行,响应头,响应主体积。响应行包括,协议版本,状态码,原因短句

状态码:描述请求的处理结果,三位数字组成,第一位指定了响应类别

  • 1XX: 信息性状态码 接收的请求正在处理
  • 2xx: 成功状态码 请求正常处理完毕
  • 3xx: 重定向状态码 需要执行附加操作完成请求
  • 4xx: 客户端错误 服务器无法处理请求
  • 5xx: 服务器错误 服务器处理请求出错

报文首部:首部内容为客户端和服务器分别处理内容提供所需要的信息

服务器接收请求并处理然后返回内容进行HTTP响应。

浏览器进行渲染显示页面

浏览器收到服务器的HTTP响应后,Browser主进程将资源通过RenderHost接口交给Renderer进程,GUI渲染线程接管,html解析器拿到字节流之后经过解码之后是字符流(转换为文件指定编码的字符串),然后词法分析成有意义的代码块也就是词法单元token,语法分析器构建节点,节点组成一颗DOM树。

  • 词法分析: 词法分析可以理解为一个状态机,输入字符串根据特定的规则转换成我们能理解的token,相当于输入字符串循环调用nextTkoen函数,每次输出一个token而且还会标记字符串表明哪些字符已经被处理了。
  • 语法分析: 会关联一个document对象作为根节点,借用了栈结构,因为tag标记是有结束和开始标记的,如果token是一个开始标记,创建一个元素添加到DOM TREE中,并压入还未遇到结束标记的开始栈中。
  • 构建DOM树:词法分析后,构建DOM树节点的时候,也就是从上到下解释html的时候,如果遇到了script标记,会与Browser进程通信去请求下载js资源,拿到资源后编译并执行,这个过程中如果script标签没有async或者defer标记就是同步加载资源,会阻塞后面的资源下载,htm解析生成DOM树节点也会阻塞,因为js可以操作DOM,如果遇到csslink标记css资源会异步加载(图片加载也是),加载完成后会用css解析器解析,此时不会影响html解析也就是DOM树的创建,因为是两颗独立的树互不相应,但是会影响后续RenderObject渲染树的构建,因为RenderObject需要用到css的信息,同时会暂停js的执行,因为js也可以修改css。可以理解为css的解析和DOM树的构建可以同时进行。
  • 生成布局渲染树RenderObject:css经过词法分析语法分析生成一个CSSStylesheet对象,css匹配html的过程是逆向匹配的过程,再CSSOMRTREE和DOMTREE构建完毕后,才会生成RenderObject Tree,会为DOM树上的每一个可视化节点创建一个RenderObject对象然后组成一颗RenderObject树。
  • RenderLayer树:我们的页面是分层的,在RenderObject创建的同时具有相同坐标空间的RenderObject属于同一个RenderLayer层,document,html,canvas,video等都会创建单独的层
  • 布局:每个RenderObject对象保存了样式布局信息,但是还需要重新计算位置和大小进行布局,因为某些属性信息,比如margin:0 auto需要转换成实际的大小才能绘制,先计算子节点再计算父节点,有的父节点高度是靠子节点撑起来的。
  • 绘制:RenderObject会调用绘图上下文绘制内容,cpu绘制属于软件渲染,gpu绘制属于硬件渲染。软件渲染绘制的结果是一个位图bitmap,绘制每一层的时候都用这个位图,只不过绘制的位置不一样,这个位图实际上就是cpu使用的一块内存空间,再把这个位图交给Browser进程进行显示。webkit的软件渲染过程是在renderer进程中,网页的显示是在Browser进程中进行的。硬件渲染是每个层都有自己的绘制区域,会有自己的后端存储(位图)来保存绘图结果,绘制到自己的后端存储而不是整个网页共享的位图中,最后将这些层的内容合并到一个图像中称为合成,GPU进程最终绘制的结果不再像软件渲染那样通过共享内存传递给Browser进程,而是直接将页面内容绘制在浏览器上。
  • 重排重绘:当触发重排重绘时,要经历三个阶段,计算布局,绘制,合成三个阶段

tip1:缓存

当浏览器去请求资源文件的时候中间还有验证文件是否过期是否从浏览器中读强缓存或者协商缓存的步骤:当请求资源文件的时候比如请求xx.js或者xx.css。首先在浏览器中的缓存中查找一下文件名,现在的资源文件一般都是通过webpack打包生成带版本号的,浏览器的缓存相当于一个hashMap,资源名字当做key,value是当时返回的响应头和资源的内容。首先找一下如果名字不在缓存中的话直接再去服务器重新请求。 如果文件名字存在于缓存中 的话。还分为两种情况,强缓存和协商缓存。 步骤为: 先查看对应资源响应头的expires或者cache-control:max-age +date与当前时间进行对比如果大于当前时间则表示没过期,则直接使用浏览器的缓存,这就是强缓存。如果要是过期了,则会在发起一次请求,拿到缓存中的响应头中的lastModified和E-tag字段放在这次请求的请求头中对应放到if-modified-since和if-none-match字段中,如果同时存在的话以e-tag为准,e-tag一般是资源的md5值或者hash的几位可以拿去和服务器上的作对比,lastModified表示上次更改时间与服务器文件时间对比一下,一样的话表示没过期。对比后如果要是没过期,服务器返回304 not-modified 在从浏览器中拿到缓存资源,这就是协商缓存,如果过期了则返回新的资源。 浏览器缓存有两个来源,from disk cache磁盘缓存,from memeory cache内存缓存,大概理解为。开了一次浏览器访问过一次页面,不关浏览器,在请求一次就是from memory cache,关掉了,在请求一次就是from disk cache.

tip2:js相关及执行机制

js引擎会对源代码进行词法分析,语法分析,代码生成然后解释执行,代码生成的步骤会进行变量提升分号补全。也可能会在词法分析生成AST后转成中间表示也就是字节码,然后通过JIT技术转成本地代码(汇编代码)被CPU直接执行,或者直接将AST生成本地代码。

js执行机制

js代码分为同步任务和异步任务,所有的同步任务都都在主线程掌管的执行栈中执行,事件触发线程掌管着一个任务队列,宏任务包括整体js代码,事件回调,xhr回调,定时器,UI Render,微任务包括promise回调。首先全局代码压入执行栈执行,遇到setTimeout或者xhr或者事件触发的时候,交给事件触发线程处理,比如当定时器的时间到时后(由定时器线程进行计数)将事件以及它的回调放进任务队列中等待执行栈空闲再拿到执行栈中执行。遇到微任务比如当Promise的回调触发时放入微任务队列中。当执行栈中的代码执行完毕后。会去处理微任务队列中的任务,而且会执行完微任务队列中的所有任务。接下来会进行UI渲染工作(有可能做,浏览器自己决定).此时一次eventLoop完成,进行下一次eventLoop,去任务队列中取出待执行任务放到执行栈中执行。过程会不断循环所以叫eventLoop。UI渲染的时机在一次eventLoop的最后,浏览器有自己的优化策略不是每轮时间循环都会进行UI渲染,但是UI渲染重绘之前一定会调用requestAnimationFrame的回调。