从一行url到一个页面我们经历了什么?(更新中)

Author Avatar
GeniusFunny 5月 03, 2018
  • 在其它设备中阅读本文章

当我们在浏览器地址栏输入一行url,整个页面呈现,整个过程发生了什么?我大致把整个过程分为两个部分,即网络行为与页面周期。
浏览器缓存 —> DNS —> TCP连接 –> HTTP请求/响应 —> 构建页面 —> 事件处理

网络行为

浏览器缓存机制

浏览器和服务器是应答模式,即:浏览器发起HTTP请求 —> 服务器返回响应。如果是第一次发起请求,浏览器会根据拿到的HTTP响应头的配置来决定是否缓存此次的响应结果。过程如下:
第一次HTTP请求.png
1. 浏览器每次发起HTTP请求时,都会根据请求去浏览器缓存中匹配对应的请求结果和缓存标识。
2. 浏览器每次成功获取到请求结果,都会将请求结果和缓存标识存入浏览器缓存。
根据浏览器是否需要向服务器重新发起HTTP请求,将缓存过程分为了两部分:强制缓存协商缓存

强制缓存

概念

强制缓存就是向浏览器缓存查找对应的请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程。

场景一:不存在该请求对应的缓存结果和缓存标识,强制缓存失效,直接向服务器发起请求

如果浏览器缓存中不存在对应的缓存结果和缓存标识,那么浏览器会重新向服务器发起HTTP请求。
ForcingCache-1.png

场景二: 存在该请求对应的缓存结果和缓存标识但已失效,强制缓存失效,采用协商缓存

如果浏览器缓存中存在对应的缓存结果和缓存标识,但缓存已经失效(过期),那么浏览器会采用协商缓存。
ForcingCache-2.png

场景三: 存在该请求对应的缓存结果和缓存标识且尚未失效,强制缓存生效,直接返回该结果

如果浏览器缓存中存在对应的缓存结果和缓存标识且缓存尚未失效,那么浏览器缓存直接返回缓存结果。
ForcingCache-3.png

规则

强制缓存与HTTP响应头中的字段Cache-ControlExpires有关,Cache-Control优先于Expires。
Cache-Control: HTTP/1.1,主要取值如下:

  1. public: 响应可以被任何缓存区(客户端、代理服务器)缓存
  2. private: 响应只可以被客户端缓存(Cache-Control默认值)
  3. no-cache: 可以存储在本地缓存中,但需要与服务器进行验证后方可使用
  4. no-store: 禁止缓存对响应进行复制
  5. max-age=xxx: 缓存将在xxx秒后过期
    Expires: HTTP/1.0, 附加一个Expires日期首部到响应中去,这个日期为绝对日期,例如:Expires: Fri, 05 Jul 2002, 05:00:00 GMT

协商缓存

概念

协商缓存就是在强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程。

场景一: 协商缓存生效,返回304

如果服务器提示资源尚未失效,则返回304,浏览器可继续使用浏览器缓存中该资源的缓存结果。
consult-cache-1.png

场景二: 协商缓存失效,返回200

如果服务器提示资源尚已失效,则返回200,服务器重新返回最新的资源,浏览器将缓存结果和缓存标识存入浏览器缓存。
consult-cache-2.png

规则

HTTP允许缓存向原始服务器发送一个“条件GET”,请求服务器只有在文档与缓存中现有的副本不同时,才回送对象的主体。—- 《HTTP权威指南》
协商缓存与HTTP头中的字段Last-Modified / If-Modified-Since(请求头)Etag / If-None-Match(请求头)有关,Etag / If-None-Match优先级比Last-Modified / If-Modified-Since高。
Last-Modified / If-Modified-Since:
If-Modified-Since:Date再验证请求通常被称为IMS请求,只有某个日期之后资源发生了改变,IMS请求才会指示服务器执行请求:
1.如果自指定日期后,资源更新了,If-Modified-Since条件就为真,通常GET就会执行,携带新首部(除了其他信息还有一个新的过期日期)的新资源就会返回给缓存。
2.如果自指定日期后,资源未更新,If-Modified-Since条件就为假,就会向客户端返回一个小的304 Not Modified 响应报文,为了提高有效性,报文中不会包含资源的主体。
例:If-Modified-Since: Sat, 29 Jun 2002, 14:30:00 GMT
Etag / If-None-Match 实体标签再验证:
有些情况(周期性地重写、资源修改无影响的部分、有些服务器无法准确判断其页面的最后修改时间等等)下仅根据修改日期进行再验证是不够的,HTTP允许用户对被称为实体标签(Etag)的“版本标识符”进行比较。当发布者对资源进行修改时,就可以修改资源的实体标签来说明这是一个新版本。
1.如果实体标签仍匹配,If-None-Match再验证成功,返回304 Not Modified。
2.如果服务器上的实体标签已经发生了变化,服务器会在一个200 OK 响应中返回新的内容及相应的新Etag。
例:
If-None-Match: “v2.6”
If-None-Match: “v2.4”, “v2.5”

浏览器的缓存放在哪里?

浏览器的缓存通常存放入硬盘(from disk cache)或内存(from memory cache)。

from disk cache

硬盘缓存(from disk cache)则是直接将缓存写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行I/O操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。

from memory cache

内存缓存(from memory cache):内存缓存具有两个特点,分别是快速读取时效性
快速读取:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取。
时效性:一旦该进程关闭,则该进程的内存则会清空。

规则

在浏览器中,浏览器会在js和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取(from memory cache);而css文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存(from disk cache)。

缓存有什么优点?

  1. 减少了数据的冗余传输
  2. 缓解了网络的带宽瓶颈问题
  3. 减轻了服务器的负担
  4. 减轻了距离时延

缓存与cookie

缓存那些与cookie事务有关的文档时要特别小心,cookie和缓存的规则没有很好地建立起来。下面是处理缓存时的一些指导性规则:

  1. 如果无法缓存文档,要将其表示出来。
    如对不可缓存的文档设置:Cache-Control:no-cache=”Set-Cookie”,对可缓存的文档设置: Cache-Control:public。
  2. 缓存Set-Cookie首部要小心。
    如果向多个用户发送了相同的Set-Cookie首部可能会破坏用户的定位;有些缓存会在将响应缓存起来之前删除Set-Cookie首部,那么请求缓存的时候客户端就拿不到cookie了。
    强制缓存与原始服务器重新验证每条请求,并将Set-Cookie首部合并到客户端的响应中去,就可以改善这种情况,原始服务器可向缓存副本添加这个首部来进行这种验证:Cache-Control: must-revalidate, max-age=0
  3. 小心处理带有cookie首部的请求
    带有cookie的请求结果到达时,就是提示我们,这个请求结果可能是私有内容,必须将私有内容标识为不可缓存。

DNS

概览

DNS(Domain Name System,域名解析系统)的作用是将主机名(hostname)解析为对应的IP地址。DNS是一个由分层的DNS服务器实现的分布式数据库,一个使得主机能够查询分布式数据库的应用层协议。DNS协议运行在UDP上,使用53号端口。

常规流程

  1. 浏览器从URL中抽出主机名,将其发送给运行中的DNS应用客户端。
  2. DNS客户端向DNS服务器发送一个带有主机名的请求。
  3. DNS客户端会收到来自DNS服务器的回答报文,其中包含了与主机名对应的IP地址。
  4. 浏览器获取到IP地址,向位于该地址的服务器进程的某个端口(HTTP为80端口,HTTPS为443端口)发起一个TCP连接。

分布式、层次数据库

DNS服务器层次结构中的DNS服务器大致有三种,根DNS服务器顶级域DNS服务器权威DNS服务器。根DNS服务器—>顶级域DNS服务器—>权威DNS服务器(自顶向下)。
如果要查询www.baidu.com对应的IP地址,查询过程大致如下(分级查询):

  1. 首先跟根服务器之一联系,获得顶级域名com的TLD服务器的IP地址。
  2. 然后TLD服务器之一联系,获得baidu.com权威服务器的IP地址。
  3. 最后与baidu.com权威服务器之一联系,获得主机名www.baidu.com对应的IP地址。

除此之外,还有一类DNS服务器很重要,即:本地DNS服务器(尽管它不在DNS服务器层次结构中)。每个ISP都有一个本地DNS服务器,当主机与该ISP连接时,ISP会返回一个IP地址,该地址具有一台或多台的本地DNS服务器的IP地址。当主机发起DNS请求时,请求被发送到本地DNS服务器,它起着代理和将请求转发到DNS服务器层次结构中(类似HTTP的代理服务器)
DNS查询(一).png
从请求主机到本地DNS服务器的查询是递归查询(因为查询请求是以自己的名义,查询结果直接返回给请求主机),其余为迭代查询(查询请求是以本地DNS服务器为名义,返回的结果也是给本地DNS服务器)

DNS缓存

为了改善实验性能并减少在因特网上到处传播的DNS报文数量,DNS广泛使用了缓存技术。在一个请求链中,当某个DNS服务器接收到一个DNS回答时,它能将该信息缓存在本地存储器中(例如,共用一个ISP的小明、小红,如果小明查询了www.baidu.com的IP地址,那么这个IP地址就存在了本地DNS服务器中,在缓存失效前,小红可以直接在本地DNS服务器中获取到www.baidu.com的IP地址)。本地服务器能够缓存顶级域服务器的IP地址,因而允许本地DNS绕过查询链中的根DNS服务器。

TCP连接

一个TCP连接是由一对端点或套接字构成,其中通信的每一端都由一对(IP地址,端口号)所唯一标识。
一个TCP连接通常分为三个阶段:启动(三次握手)、数据传输、退出(四次挥手)。

三次握手

概览

three-shake.png
如图所示,客户端中的TCP会用一下方式与服务器中TCP建立一条TCP连接:

  1. 第一步,客户端的TCP首先向服务器的TCP发送一个特殊的TCP报文段,该报文段不包含应用层的数据,但是在报文段的首部中的一个标志位(即SYN比特)被置为1,因此这个特殊报文段被称为SYN报文段。客户端会随机选择一个初始序号(J)并将此序号放置于该起始的TCP SYN报文段的序号字段里,整个报文字段会封装在一个IP数据报中并发给服务器。此时,客户端进入SYN-SENT状态
  2. 第二步,一旦包含TCP SYN报文段的IP数据报到达了服务器,服务器就会从数据报中提取出TCP SYN报文段,为该TCP连接分配缓存和变量,并向客户端发送允许连接的报文段(ACK(J + 1)),该报文段也不包含应用层数据。与此同时服务器也随机选择一个初始序号(K)并将此序号放置于TCP报文段首部的序号字段中,即(SYN(K)),随确认字段ACK一并发送给客户端。此报文字段通常被称为SYNACK报文段,此时服务器进入SYN-RECEIVED状态
  3. 第三步,客户端接收到SYNACK报文段,为该连接分配缓存和变量,此时客户端进入ESTABLISHED状态。客户端向服务器发送另外一个报文段,这个报文段是对服务器的允许连接的报文段的确认(ACK(K + 1)),与前两次不同,这次报文段中可携带应用层数据,由于连接已经建立,SYN比特被置为0。服务器收到这个报文段后,也进入ESTABISHED状态
    完成这三个步骤后,客户端和服务器的每一个报文段中都可以携带应用层的数据,且SYN比特都将被置为0。建立TCP连接的三个步骤也被称为TCP三次握手
    三次握手的目的不仅在于让通信双方了解一个连接正在建立, 还在于利用数据包的选项来承载特殊的信息,交换初始序列号。——-《TCP/IP 详解 卷一:协议》
为什么是三次握手?为什么不是两次握手?

三次握手:
A—->B: [A: I’m A.]
B—->A: [B: Hello A, I’m B.]
A—->B: [A: Hello B, nice to meet you.]
然后愉快的py

二次握手:
A—->B: [I’m A.]
B—->A: [Hello A, I’m B.]
如果A收到了“Hello A,I’m B”,愉快的py
如果A没有收到“Hello A,I’m B”,那么while(true){ A—->B: [I’m A.] }, 疯狂握手
三次握手的目的是确认A—>B, B—>A这两个信道都是可靠的;如果只有两次握手就无法确认B—>A的信道是否可靠。如果只需要A—>B信道可靠,那就类似于UDP的需求了。

四次挥手

概览

连接的任何一方都可以发起关闭连接,在传统情况下往往由客户端来发起关闭连接,如下图所示。
four-wave.png

  1. 第一步:客户端发送FIN包(包含了客户端的序列号M),此外FIN包还包含了一个ACK段用于确认对方最后一次发送的数据。
  2. 第二步:服务器收到客户端发送的FIN包后,将M值+1作为响应的ACK值,表明它已经成功接收到来自客户端发送的FIN。此时,处于上层的应用程序会被告知连接的另一方发起了关闭连接的请求,这将导致应用程序发动自己的关闭操作。服务器从被动关闭者变为主动关闭者,向客户端发送FIN(包含了服务器的序列号N)。
  3. 第三步:为了完成连接的关闭,客户端发往服务器最后的报文段中包含了一个确认服务器发送的FIN的ACK字段。
为什么是四次挥手?

四次挥手:
A —> B, [A: ‘我想关闭连接’]
B —> A, [B: ‘好的,我知道了,我不要收到你的东西了’]
B —> A, [B: ‘我想关闭连接’]
A —> B, [A: ‘好的,我知道了,我也不要收到你的东西了’]
主动方发送关闭请求,被动方接受并确认请求后,被动方就无法收到主动方发送的数据了。在前两次挥手后,服务器就无法收到客户端发送的数据了(服务器释放了关于客户端的资源),但是服务器可以给客户端发送数据,所以有了第三次、第四次挥手。

页面周期概览

页面构建

DOM Tree

Style Rules

Rendering Tree

事件处理

参考文献:

  1. 彻底理解浏览器的缓存机制
  2. 图解TCP三次握手与四次分手
  3. 《HTTP权威指南》
  4. 《JavaScript忍者秘籍》
  5. 《计算机网络(自顶向下方法)》