回答一个问题
从输入URL到页面展示到底发生了什么?
- 输入地址
- 浏览器查找域名的IP地址
- 浏览器向 web 服务器发送一个 HTTP 请求
- 服务器的永久重定向响应
- 服务器处理请求
- 服务器返回一个 HTTP 响应
- 浏览器显示 HTML
- 浏览器发送请求获取在 HTML 中的资源(如图片丶音频丶视频丶CSS丶JS等等)
1. 输入地址
当我们开始在浏览器中输入网址的时候,浏览器其实就已经在智能匹配可能得 URL 了,他会从历史记录丶书签等地方,找到已经输入的字符串可能对应的 URL ,然后给出智能提示,让你补全 URL 地址。
对于 Google 的 Chrome 浏览器,它甚至会直接从缓存中把网页展示出来,就是说你还没有按下 enter,页面就出来了。
2. 浏览器查找域名的 IP 地址
- 请求一旦发起,浏览器首先要做的事情就是解析这个域名,一般来说,浏览器会首先查看本地的硬盘的
hosts文件,看看其中有没有和这个域名对应的规则,如果有的话就直接使用host文件里面的IP地址。 - 如果在本地的
hosts文件没有找到对应的IP地址,浏览器会发出一个DNS请求到本地DNS服务器。本地DNS服务器一般都是你的网络接入服务商提供,比如中国电信丶中国移动等 - 查询你输入的网址的 DNS 请求到达本地 DNS 服务器之后,本地 DNS 服务器会首先查询它的缓存记录,如果缓存记录中有此条记录,就可以直接返回结果,此过程是
递归的方式进行查询。如果没有,本地 DNS 服务器还要向DNS根服务器进行查询。 DNS根服务器没有记录具体的域名和 IP 地址的对应关系,而是告诉本地 DNS 服务器,你可以到域服务器上去继续查询,并给出域服务器的地址。这种过程是迭代的过程。- 本地 DNS 服务器继续向域服务器发出请求,在这个例子中,请求的对象是
.com域服务器。.com域服务器收到请求之后,也不会直接返回域名和IP地址的对应关系,而是告诉本地DNS服务器,你的域名的解析服务器的地址。 - 最后,本地 DNS 服务器向域名的解析器发出请求,这时就能收到一个域名和 IP 地址对应关系,本地 DNS 服务器不仅要把 IP 地址返回给用户电脑,还要把这个对应关系保存在缓存中,以备下次别的用户查询,可以直接返回结果,加快网络访问。
3. 浏览器向 web 服务器发送一个 HTTP 请求
拿到域名对应的 IP 地址之后,浏览器会以一个随机端口向服务器的Web程序80端口(http协议)发起 TCP 的连接请求。这个连接请求到达服务端后(这中间通过各种路由设备,局域网内除外),进入TCP/IP 协议栈(用于识别该连接请求丶解封包,一层一层的剥开),最终达到 Web 程序,最终建立了 TCP/IP 的连接。
TCP连接如图所示:
- 第一次握手
-
- 主要传递传递两个信息,一个是请求连接(SYN=1),二是发出一个序列号(seq=n)。
- 这个时候客户端的状态
SYN-SENT状态 - 第一次握手让服务端知道客户端可以发送信息
- 第二次握手
-
- 这次回复三个信息,一是同意建立连接(SYN=1),二是确认收到刚才的信息(ask=刚才的seq+1),三是发出自己的序列号(seq=x)。
- 这个时候服务端的状态是
SEND-RCVD - 第二次握手让客户端知道服务端既能收到也能发出
- 第三次握手
-
- 这次回复三个信息,一是表示现在开始发送(SYN=0),二是成功收到了刚才的信息(ask=刚才的seq+1),三是发出自己的序列号(最开始发出序列号+1)。
- 这个时候客户端状态变成了
ESTABLISHED,服务端的状态变成了ESTABLISHED - 第三次握手让服务端知道了客户端既能收到也能发出
四次挥手流程
四次挥手核心就在于四个时间节点,分别是 发完了 , 知道发完了 , 收完了 , 知道收完了
- 第一次挥手,A 告诉 B 数据发送完了。
-
- 这个时候相当于 A 向 B 传输数据,数据传递完了,但是数据传递完了之后,不能直接断开,因为 B 并不知道数据传递完了。
- 第二次挥手,B 知道 A 发完了。
-
- 因为 B 还没接受完数据,所以虽然知道 A 把数据传完了,所以只能先回复 A 知道你传完了。
- 第三次挥手,B 直接告诉 A 接收完了
-
- 这时,虽然 A 已经发送完了数据,但是还不确定 B 接受完数据,所以 A 还要等着,因为一旦 B 没有接受成功,A 还要继续发送。所以 A 要等着 B 反馈接受完了,才能真正断开连接。所以当 B 接受完数据的时候,B 要告诉 A 接收完了。
- 第四次挥手,A 知道 B 接受完了
-
- 这时 A 知道 B 接收完数据了,就可以放心的断开连接了。
4. 服务器永久重定向响应
5. 服务器处理请求
6. 服务器返回一个 HTTP 响应
结合前面的步骤,服务器收到我们的请求,也处理我们的请求,到这一步,它会把它的处理结果返回,也就是返回一个 HTTP 响应。
HTTP响应 和 HTTP请求相似,HTTP 响应也由 3 个部分构成,分别是:
- 状态行
- 响应头(Request Header)
- 响应正文
7. 浏览器显示 HTML
在浏览器没有完全接受全部 HTML 文档时,它就已经开始显示这个页面了,浏览器是如何把页面呈现在屏幕上的呢?不同浏览器可能解析的过程不太一样,这里我们只介绍 webkit 的渲染过程,下图对应的就是WebKit渲染的过程,这个过程包括:
解析html以构建dom树 -> 构建render树 -> 布局render树 -> 绘制render树
浏览器在解析html文件时,会“自上而下”加载,并在加载过程中进行解析渲染。 在解析过程中,如果遇到请求外部资源时,如图片丶外部的CSS丶iconfont等,请求是异步的,不会影响html文档进行加载
解析过程中,浏览器会首先解析HTML文件构建DOM树,然后解析CSS文件渲染树,等到渲染树完成后,浏览器开始布局渲染树并将其绘制到屏幕上。 这个过程比较复杂,涉及到两个概念:reflow(回流) 和 repaint(重绘)。
DOM节点中的各个元素都是以盒模型的形式存在,这些都需要浏览器去计算其位置和大小等,这个过程称为reflow(重排) ;当盒模型的位置,大小以及其它属性,如颜色丶字体等确定下来之后,浏览器便开始绘制内容,这个过程称为repain(重绘) 。
页面在首次加载时必然会经历 reflow 和 repain。reflow 和 repain 过程是非常消耗性能的,尤其是在其移动设备上,它会破坏用户体验,有时会造成页面卡顿。所以我们应该尽可能少的减少 reflow 和 repain。
当文档加载过程中遇到 JS 文件,html 文档会挂起渲染(加载解析渲染同步)的线程,不仅要等待文档中的 JS 文件加载完毕,还要等解析执行完毕,才可以恢复html 文档的渲染线程。因为 JS 有可能会修改 DOM,最为经典的domcument.write,这意味着在 JS 执行完成前后续所有资源的下载可能是没有必要的,这是 JS 阻塞后续资源下载的根本原因。所以我平时的代码中,JS 是放在文档末尾。
JS 的解析器是由浏览器的 JS 解析器引擎完成的, 比如谷歌的是V8。JS 是单线程运行,也就是说在同一个时间内只能做一件事,所有的任务都需要排队,前一个任务结束,后一个任务才能开始。但是又存在某些任务比较耗时,比如 IO 读写等,所有需要一种机制可以先执行排在后面的任务,这就是:同步任务和异步任务。
JS的执行机制就可以看做是一个主线程加上一个任务队列。同步任务就是放在主线程上执行的任务,异步任务是放在任务队列中的任务。所有的同步任务在主线程上执行,形成一个执行栈;异步任务有了运行结果就会在任务队列中放置一个事件;脚本运行先依次运行执行栈,然后会从任务队列中提取事件,运行任务队列中的任务,这个过程是不断重复的,所以又叫做事件循环。