您的当前位置:首页正文

浏览器工作原理:从 URL 输入到页面展现到底发生了什么?

来源:花图问答

前端为什么要研究渲染原理?

像素完美(Pixel Perfection)、分辨率无关(Resolution Independent)和多平台体验一致性是设计师的追求。而访问性(Accessability)、加载性能和重构灵活性则是前端工程师必懂的技能。最重要的加载性能又与浏览器渲染机制深深挂钩,所以只有弄明白了浏览器背后的渲染机制,才能在日常的前端开发中明白如何进行性能优化。
还有就是像类似:
从输入 URL 到页面加载完成的过程中都发生了什么事情
从按下键盘到屏幕上出现字符,中间都发生了什么事情
用户反应网站卡,请问都有哪些可能性,以及解决方法
这样的问题面试官都是比较喜欢问的,今天尝试来详细说一下这个过程。

从输入 URL 到页面加载完成的过程中都发生了什么?

简单路径线

  1. 键盘或触屏输入URL并回车确认
  2. URL解析/DNS解析查找域名IP地址
  3. 网络连接发起HTTP请求
  4. HTTP报文传输过程
  5. 服务器接收数据
  6. 服务器响应请求/MVC
  7. 服务器返回数据
  8. 客户端接收数据
  9. 浏览器加载/渲染页面
  10. 打印绘制输出

实际上并没有这么简单,下面说说它的详细路径线

1. 键盘或触屏输入URL并回车确认

2. URL 解析/DNS 查询

DNS递归解析示例图
所以,我们看到DNS的域名解析是递归的,递归的DNS首先会查看自己的DNS缓存,如果缓存能够命中,那么就从缓存中把IP地址返回给浏览器,如果找不到对应的域名的IP地址,那么就依此层层向上转发请求,从根域名服务器到顶级域名服务器再到极限域名服务器依次搜索对应目标域名的IP,最高达到根节点,找到或者全部找不到为止。然后把找到的这个域名对应的 nameserver 的地址拿到,再向这个 namserver 去请求域名对应的IP,最后把这个IP地址返回给浏览器,在这个递归查询的过程中,对于浏览器来说是透明的,如果DNS客户端的本地名称服务器不能解析的话,则后面的查询都会以本地名称服务器为中心,全交由本地名称服务器代替DNS客户端进行查询,DNS客户端只是发出原始的域名查询请求报文,然后就一直处于坐等状态,直到本地名称服务器最终从权威名称服务器得到了正确的IP地址查询结果并返回给它。虽然递归查询是默认的DNS查询方式,但是如果有以下情况发生的话,则会使用迭代的查询方式进行。
情况一:DNS客户端的请求报文中没有申请使用递归查询,即在DNS请求报头部的RD字段没有置1。
情况二:DNS客户端的请求报文中申请使用的是递归查询(也就是RD字段置1了),但在所配置的本地名称服务器上是禁用递归查询了(即在应答DNS报文头部的RA字段置0)。

迭代查询的流程如下
开始也是从浏览器缓存到系统缓存到路由缓存,如果还是没找到则客户端向本机配置的本地名称服务器(在此仅以首先DNS服务器为例进行介绍,其它备用DNS服务器的解析流程完全一样)发出DNS域名查询请求。本地名称服务器收到请求后,先查询本地的缓存,如果有该域名的记录项,则本地名称服务器就直接把查询的结果返回给客户端;如果本地缓存中没有该域名的记录,则向DNS客户端返回一条DNS应答报文,报文中会给出一些参考信息,如本地名称服务器上的根名称服务器地址等。DNS客户端在收到本地名称服务器的应答报文后,会根据其中的根名称服务器地址信息,向对应的根名称服务器再次发出与前面一样的DNS查询请求报文。根名称服务器在收到DNS查询请求报文后,通过查询自己的DNS数据库得到请求DNS域名中顶级域名所对应的顶级名称服务器信息,然后以一条DNS应答报文返回给DNS客户端。DNS客户端根据来自根名称服务器应答报文中的对应顶级名称服务器地址信息,向该顶级名称服务器发出与前面一样的DNS查询请求报文。顶级名称服务器在收到DNS查询请求后,先查询自己的缓存,如果有请求的DNS域名的记录项,则直接把对应的记录项返回给DNS客户端,否则通过查询后把对应域名中二级域名所对应的二级名称服务器地址信息以一条DNS应答报文返回给DNS客户端。然后DNS客户端继续按照前面介绍的方法一次次地向三级、四级名称服务器查询,直到最终的权威名称服务器返回到最终的记录。如果权威名称服务器也找不到对应的域名记录,则会向DNS客户端返回一条查询失败的DNS应答报文。当然,如果这个权威名称服务器上配置了指向其它名称服务器的转发器,则权威名称服务器还会在转发器指向的名称服务器上进一步查询。另外,如果DNS客户端上配置了多个DNS服务器,则还会继续向其它DNS服务器查询。

DNS迭代解析示意图
所以,我们发现在递归查询中后面的查询工作是由本地名称服务器替代DNS客户端进行的(以“本地名称服务器”为中心),只需要本地名称服务器向DNS客户端返回最终的查询结果即可。而DNS迭代查询的所有查询工作则全部是DNS客户端自己进行(以“DNS客户端”自己为中心)。

DNS递归查询和迭代查询的区别
递归查询是以本地名称服务器为中心的,是DNS客户端和服务器之间的查询活动,递归查询的过程中“查询的递交者” 一直在更替,其结果是直接告诉DNS客户端需要查询的网站目标IP地址。
迭代查询则是DNS客户端自己为中心的,是各个服务器和服务器之间的查询活动,迭代查询的过程中“查询的递交者”一直没变化,其结果是间接告诉DNS客户端另一个DNS服务器的地址。

递归和迭代查询
举个例子来说,一次选修课上你碰到了你的女神,但你只知道她的名字并不知道她的电话,于是在手机通讯录里找,没找到,然后你回到寝室把她名字告诉了一个很仗义的哥们,让他帮你找,这个哥们儿(本地名称服务器)二话没说,开始替你查(此处完成了一次递归查询,即问询的人由你变成了你的哥们)。然后你哥们带着名字去问了学院大四的学长,学长一看,我擦,这姑娘我认识啊,于是在手机里找到了她的电话告诉了你哥们,你哥们回来告诉了你,你于是知道了她的电话(这里一次递归查询结束)。还有一种可能你哥们跑去问学长,学长也没她电话,但学长告诉你哥们,这姑娘是xx系的;然后你哥们儿马不停蹄又问了xx系的办公室主任助理同学,助理同学说是xx系yy班的,然后很仗义的哥们儿去xx系yy班的班长那里取到了该女孩的电话(此处完成若干次迭代查询,即问询的人一直是你哥们不变,但反复更替的是问询对象)。最后,他把号码交到了你手里,完成整个查询过程。

3. 应用层客户端发送HTTP请求

互联网内各网络设备间的通信都遵循TCP/IP协议,利用TCP/IP协议族进行网络通信时,会通过分层顺序与对方进行通信。分层由高到低分别为:应用层、传输层、网络层、数据链路层。发送端从应用层往下走,接收端从数据链路层网上走。如图所示:



从上面的步骤中得到 IP 地址后,浏览器会开始构造一个 HTTP 请求,应用层客户端向服务器端发送的HTTP请求包括:请求报头和请求主体两个部分,其中请求报头(request header)包含了至关重要的信息,包括请求的方法(GET / POST和不常用的PUT / DELETE以及更不常用的HEAD / OPTION / TRACE,一般的浏览器只能发起 GET 或者 POST 请求)、目标url、遵循的协议(HTTP / HTTPS / FTP…),返回的信息是否需要缓存,以及客户端是否发送Cookie等信息。需要注意的是,因为 HTTP 请求是纯文本格式的,所以在 TCP 的数据段中可以直接分析 HTTP 文本的。

4. 传输层TCP传输报文

当应用层的 HTTP 请求准备好后,浏览器会在传输层发起一条到达服务器的 TCP 连接,位于传输层的TCP协议为传输报文提供可靠的字节流服务。它为了方便传输,将大块的数据分割成以报文段为单位的数据包进行管理,并为它们编号,方便服务器接收时能准确地还原报文信息。TCP协议通过“三次握手”等方法保证传输的安全可靠。“三次握手”的过程是,发送端先发送一个带有SYN(synchronize)标志的数据包给接收端,在一定的延迟时间内等待接收的回复。接收端收到数据包后,传回一个带有SYN/ACK标志的数据包以示传达确认信息。接收方收到后再发送一个带有ACK标志的数据包给接收端以示握手成功。在这个过程中,如果发送端在规定延迟时间内没有收到回复则默认接收方没有收到请求,而再次发送,直到收到回复为止。


这里需要谈一下 TCP 的 Head-of-line blocking 问题:假设客户端的发送了 3 个 TCP 片段(segments),编号分别是 1、2、3,如果编号为 1 的包传输时丢了,即便编号 2 和 3 已经到达也只能等待,因为 TCP 协议需要保证顺序,这个问题在 HTTP pipelining 下更严重,因为 HTTP pipelining 可以让多个 HTTP 请求通过一个 TCP 发送,比如发送两张图片,可能第二张图片的数据已经全收到了,但还得等第一张图片的数据传到。为了解决 TCP 协议的性能问题,Chrome 团队提出了 协议,它是基于 UDP 实现的可靠传输,比起 TCP,它能减少很多来回(round trip)时间,还有前向纠错码(Forward Error Correction)等功能。目前 Google Plus、 Gmail、Google Search、blogspot、Youtube 等几乎大部分 Google 产品都在使用 QUIC,可以通过 页面来发现。另外,浏览器对同一个域名有连接数限制,,但并非将这个连接数改大后就会提升性能,Chrome 团队有做过实验,发现从 6 改成 10 后性能反而下降了,造成这个现象的因素有很多,如建立连接的开销、拥塞控制等问题,而像 SPDY、HTTP 2.0 协议尽管只使用一个 TCP 连接来传输数据,但性能反而更好,而且还能实现请求优先级。

5. 网络层IP协议查询MAC地址

IP协议的作用是把TCP分割好的各种数据包封装到IP包里面传送给接收方。而要保证确实能传到接收方还需要接收方的MAC地址,也就是物理地址才可以。IP地址和MAC地址是一一对应的关系,一个网络设备的IP地址可以更换,但是MAC地址一般是固定不变的。ARP协议可以将IP地址解析成对应的MAC地址。当通信的双方不在同一个局域网时,需要多次中转才能到达最终的目标,在中转的过程中需要通过下一个中转站的MAC地址来搜索下一个中转目标。

6. 数据到达数据链路层

在找到对方的MAC地址后,已被封装好的IP包再被封装到数据链路层的数据帧结构中,将数据发送到数据链路层传输,再通过物理层的比特流送出去。这时,客户端发送请求的阶段结束。

这些分层的意义在于分工合作,数据链路层通过 CSMA/CD 协议保证了相邻两台主机之间的数据报文传递,而网络层的 IP 数据包通过不同子网之间的路由器的路由算法和路由转发,保证了互联网上两台遥远主机之间的点对点的通讯,不过这种传输是不可靠,于是可靠性就由传输层的 TCP 协议来保证,TCP 通过慢开始,乘法减小等手段来进行流量控制和拥塞避免,同时提供了两台遥远主机上进程到进程的通信,最终保证了 HTTP 的请求头能够被远方的服务器上正在监听的 HTTP 服务器进程收到,终于,数据包在跳与跳之间被拆了又封装,在子网与子网之间被转发了又转发,最后进入了服务器的操作系统的缓冲区,服务器的操作系统由此给正在被阻塞住的 accept 函数一个返回,将他唤醒。

7. 服务器接收数据

接收端的服务器在链路层接收到数据包,再层层向上直到应用层。这过程中包括在传输层通过TCP协议将分段的数据包重新组成原来的HTTP请求报文。

8. 服务器响应请求并返回相应文件

我们以 php 语言为例来说的话,请求到达一个 php 的 mvc 框架之后,框架首先应该会初始化一些环境的参数,例如远端 ip,请求参数等等,然后根据请求的 url 送到一个路由器类里面去匹配路由,路由由上到下逐条匹配,一旦遇到 url 能够匹配的上,而且请求的方法也能够命中的话,那么请求就会由这个路由所定义的处理方法去处理。

请求进入处理函数之后,如果客户端所请求需要浏览的内容是一个动态的内容,那么处理函数会相应的从数据源里面取出数据,这个地方一般会有一个缓存,例如 memcached 来减小 db 的压力,如果引入了 orm 框架的话,那么处理函数直接向 orm 框架索要数据就可以了,由 orm 框架来决定是使用内存里面的缓存还是从 db 去取数据,一般缓存都会有一个过期的时间,而 orm 框架也会在取到数据回来之后,把数据存一份在内存缓存中的。

orm 框架负责把面向对象的请求翻译成标准的 sql 语句,然后送到后端的 db 去执行,db 这里以 mysql 为例的话,那么一条 sql 进来之后,db 本身也是有缓存的,不过 db 的缓存一般是用 sql 语言 hash 来存取的,也就是说,想要缓存能够命中,除了查询的字段和方法要一样以外,查询的参数也要完全一模一样才能够使用 db 本身的查询缓存,sql 经过查询缓存器,然后就会到达查询分析器,在这里,db 会根据被搜索的数据表的索引建立情况,和 sql 语言本身的特点,来决定使用哪一个字段的索引,值得一提的是,即使一个数据表同时在多个字段建立了索引,但是对于一条 sql 语句来说,还是只能使用一个索引,所以这里就需要分析使用哪个索引效率最高了,一般来说,sql 优化在这个点上也是很重要的一个方面。

9)浏览器开始处理数据信息并渲染页面

dom 树构造好了之后,就是根据 dom 树和 css 样式表来构造 render 树了,这个才是真正的用于渲染到页面上的一个一个的矩形框的树,网页渲染是浏览器最复杂、最核心的功能,对于 render 树上每一个框,需要确定他的 x y 坐标,尺寸,边框,字体,形态,等等诸多方面的东西,render 树一旦构建完成,整个页面也就准备好了,可以上菜了。需要说明的是,下载页面,构建 dom 树,构建 render 树这三个步骤,实际上并不是严格的先后顺序的,为了加快速度,提高效率,让用户不要等那么久,现在一般都并行的往前推进的,现代的浏览器都是一边下载,下载到了一点数据就开始构建 dom 树,也一边开始构建 render 树,构建了一点就显示一点出来,这样用户看起来就不用等待那么久了。

10)将渲染好的页面图像显示出来,并开始响应用户的操作。

这一步主要涉及显卡,内存及显示器原理等知识,不做详细解说,大概就是从内存到 LCD/LED,再由光线进入人眼的一个过程。

浏览器解析渲染页面原理




参考资料