输入 URL 到按下 return 发生了什么

前言

  1. DNS 解析
  2. TCP 连接
  3. HTTP 请求
  4. 浏览器渲染

这是一道非常常见的面试题,网上早有许多答案,上面写的也就是简单的答案,但是我想看看如果我尽力每一个点去发掘可以写成什么样,能学到什么。

DNS 解析

DNS 全称是 Domain Name System(域名系统),全靠它我们才不需要上网记住 IP 而是写域名,但是电脑并不知道域名对应的 IP,所以需要它来帮忙解析。

浏览器缓存

首先会去找浏览器缓存,如果缓存内有就不会找下一步了,Chrome 的话是 60s。

系统缓存

如果没找到首先检查 Hosts 文件的 DNS 和 IP 的关系,然后再找系统缓存,系统缓存的时间是按照 DNS TTL(Time to Live) 的时间来缓存。

小知识点

因为很多会设置比较短的 TTL,比如一些是 30s,那么 30s 之后就重新请求浪费了时间,Apple 在 WWDC 2018 提示的 Optimistic DNS 方案,如果本地 DNS 缓存过期依然使用缓存的 IP 去连接,但是同时进行新的 DNS 查询去更新缓存,如果原来 IP 连接失败,则用新的结果进行重试。
Apple 已经在部分产品上开始使用该方案,如 CloudKit

LDNS(本地服务器查询)

这里就是设置系统系统本身的 DNS 服务器,一般如果没有设置就是 ISP(网络提供商)的缓存服务器。

路由器缓存

路由器也有一个自己的缓存,应该是按照 TTL 时间来记录。

根域名服务器

以上都没有完成找到的话,就寻找根域名服务器,因为网络协议的限制,导致根域名服务器地址被限制在 13 个,但是现在实际有 800+ 个服务器在运行,他们采用任播技术假设镜像服务器。
他将会返回不同顶级域名的服务器,比如 .com .org 等等,但是如果找不到的话,还是会返回 .com 的顶级域名服务器。

顶级域名服务器

顶级域名会返回注册的权威域名服务器,比如域名是 Google 所管理,那么就返回 Google 的权威域名服务器。

权威域名服务器

 到这里基本就是解决了,可能他们内部还有别的服务器,如果该域名有 CDN 加速的话,就会根据你的 IP 来返回地理位置相对靠近的 IP 列表。

知识点

现代的网络服务大部分都是多个服务器进行负载均衡处理的,如果返回了多个 IP,那么主流的系统或者浏览器会优先使用第一个 IP 去请求,所以为了达到负载均衡的效果,在返回你的结果上随机排列,也就是说,如果第一个 IP 不可用,那么就会尝试第二个,如果查询时间较慢,就会浪费很多时间,所以可以利用并发查询来优化这个过程,比如使用 dnsmasq 或者 Surge For Mac。

TCP 连接

浏览器发送 HTTP 请求之前,需要在浏览器和服务器之间建立一条 TCP/IP 连接,连接需要三次握手,释放需要四次挥手,详细的请参考TCP 的特性

为什么是三次握手

这个问题比较多,其实思考一下就是互相确定可以通信的步骤就是三次,比如

浏览器:在吗?
服务器:在,你说。
浏览器:ok,我找你其实是为了跟你说我发现一个不错的 blog,YeungKC 写的……

在这个过程中如果多了或者少了,都不是确保互相通信。
实际信号简单表明

client: syn
server: syn + ack
client: ack

为什么是四次挥手

主要为了保证数据互相完成发送,举个 🌰

浏览器:我要下线了,下次再网上冲浪
服务器:好我知道了
服务器:我也觉得这个 blog 十分不错诶 bababababla
服务器:我说完了,我也下线了
浏览器:那我走了 88

这里不是有五次么?实际上问题就在这里,当客户端想断开的时候,就形成了一种半关闭连接,不会再发送数据,但是服务端依然可以处理发送,然后再确认断开。
所以实际上服务端中间的依然可以发送,保证两端都处理完成。

client: fin
server: ack
server: …
server: fin
client: ack

HTTP 请求

HTTPS

这里要首先讲起 HTTPS,HTTPS 连接需要先建立一次 TLS/SSL 握手阶段,服务端返回公钥,然后再通过公钥加密信息,再给服务端,那么服务端有使用私钥就可以解开信息,这里主要是非对称加密问题,还有更深入就是数字证书,因为实际上不是返回公钥给客户端而是数字证书。

非对称加密

实际上就是可以用多个公钥加密的内容只有私钥才能解开,常见算法有 RSA DSA 等。

数字证书

如果只给公钥会导致一个问题就是中间人替换公钥,收到内容之后再重新加密,所以需要数字证书,而浏览器会检查证书链等方法去检查是否正确,从而避免这个问题。

HTTP 1.1

  • 复用 TCP,避免 TCP 建立和断开的开销,但是问题在于是串行排队一个一个资源加载
  • 只发 header 信息,比如 head 请求就可以通过状态码 100 还是 401(没有权限)来判断
  • 获取部分文件内容,实现多线程下载和断点续传
  • 更多状态码
  • 缓存策略,Cache-Control 控制可以更为精细,比如
    • Cache-Control 是强制缓存,比如用得最多的是max-age多少秒之后失效
      • Expires 是 HTTP 1 的产物,使用绝对到期时间,由于客户端时间可变,所以如果两者都设置之下取max-age
    • 304 状态码 etag 判断是否需要获取服务端数据

HTTP 2.0

  • 多路复用,这是最牛逼的功能之一,当时我在公司同事得知淘宝已经开始使用,马上打开一看,同时加载对于多资源网站太有用了
  • 头部压缩,通讯双方都缓存一份对应属性表,减少了传输也减少了大小
  • 服务端推送,这之前只能通过 socket 来实现
  • 流量控制
  • 流量优先级,解决 TCP 抢占问题

浏览器渲染

浏览器获取到 HTML 之后会被一句一句的解析,大概过程如是 HTML 就直接渲染,如是linkimgscript等就获取外部资源并渲染执行。

详细请看

script 标签的 defer 和 async

简单来讲就是defer等于放到body底部,async就是适合不依赖其他资源的情况下,比如 Google 分析之类的。但是可以看看这个文章,实际上使用defer还不如还是放到body底部调整顺序。

defer

这个属性的用途是表明脚本在执行时不会影响页面的构造。也就是说,脚本会被延迟到整个页面都解析完毕后再运行。因此,在<script>元素中设置defer属性,相当于告诉浏览器立即下载,但延迟执行。

HTML5 规范要求脚本按照它们出现的先后顺序执行,因此第一个延迟脚本会先于第二个延迟脚本执行,而这两个脚本会先于DOMContentLoaded事件执行。在现实当中,延迟脚本并不一定会按照顺序执行,也不一定会在DOMContentLoad时间触发前执行,因此最好只包含一个延迟脚本。

async

这个属性与defer类似,都用于改变处理脚本的行为。同样与defer类似,async只适用于外部脚本文件,并告诉浏览器立即下载文件。但与defer不同的是,标记为async的脚本并不保证按照它们的先后顺序执行。

第二个脚本文件可能会在第一个脚本文件之前执行。因此确保两者之间互不依赖非常重要。指定async属性的目的是不让页面等待两个脚本下载和执行,从而异步加载页面其他内容。

DOMContentLoaded 和 Load

DOMContentLoaded

简单完就是 HTML 全部解析加载完毕,并和内联和引用非 async 的 JavaScript 执行完毕,就触发

Load

就是所有所有东西加载完,比如 async JavaScript 和图片

videoaudioflash等是不会影响 Load 事件

知识点

不同浏览器对于同一个域名的资源并发 TCP 连接数同,Chrome 是 6 个,所以这里也能利用上面所讲过的知识点进行优化。其实就是使用不同的域名去加载

优化

上面提到基本就是整个过程,我们那从中优化一下加载流程

  • 减少 DNS 查询,尽量资源都在同一个域(Domain)下
  • 减少 TCP 连接,同上,但是如果 HTTP 2 甚至可以考虑全部放在同一个下
  • 使用 CDN 加速
  • 减少 HTTP 跳转
  • 减少体积,header 等
  • GZip 压缩,这里之前没提过…但是我知道
  • 选择好的缓存策略
  • 绕过浏览器并发请求问题,资源放在不同请求下

优化是一个大学问,问题还有很多,不过举例这些算是银弹,很有效提升

总结

知识点应该还有很多很多别的,应该说说不完的,我也在写文章的过程中温故而知新一些东西,也搜索的时候知道其他,还是得继续努力啊~