一次完整的HTTP事务流程
- 域名解析,浏览器根据 DNS 服务器得到域名的 IP 地址
- 发起TCP三次握手
- 建立TCP连接后向这个 IP 的机器发送 HTTP 请求
- 服务器收到、处理并返回 HTTP 请求,浏览器得到HTML代码
- 浏览器解析HTML代码,渲染页面
DNS解析域名
DNS
的作用就是通过域名查询到具体的 IP
。
因为 IP
存在数字和英文的组合(IPv6),很不利于人类记忆,所以就出现了域名。你可以把域名看成是某个 IP 的别名,DNS
就是去查询这个别名的真正名称是什么。
在 TCP
握手之前就已经进行了 DNS
查询,DNS
解析是一个递归查询的过程,具体步骤如下(以www.google.com
为例):
- 首先在本地域名服务器(最近的一台DNS服务器)中查询
IP
地址 - 如果没有找到,本地域名服务器会向根域名服务器发送一个请求
- 如果根域名服务器中也不存在该域名,但判定这个域名属于“
com
”的一级域,则本地域名服务器会向负责 “com
” 这个一级域名的服务器发送一个请求 - 如果
com
一级域名服务器没有找到该域名,但判定这个域名属于“google.com
”的二级域
,则本地域名服务器会向google.com
二级域名服务器发送一个请求,以此类推 - 直到本地域名服务器得到域名对应的
IP
地址,并将其缓存到本地,供下次查询使用 - 综上,网址的解析过程为
.->.com->google.com->www.google.com
。
DNS 是基于 UDP 做的查询,为什么之前不考虑使用 TCP 去实现?
之前的文章中提到过TCP
传输是需要连接的,以及校验数据的准确性,还有三次握手四次挥手,而UDP
相对而言无需连接,不会对数据进行处理只是搬用过去,虽然这样不稳定,但确是高效的。
- TCP - - - DNS域名解析时间 = TCP连接时间 + DNS交易时间
- UDP - - - DNS域名解析时间 = DNS交易时间
很显然,采用UDP
传输,DNS
域名解析时间更小。
在很多冷门域名解析的时候,DNS
多次迭代查询,如果在此使用的是TCP
的话,TCP
需要建立多个连接,这多出来的时间不容小觑
TCP三次握手
在之前的文章中已经讲过TCP和UDP的特点和区别了,这里就文章标题而言,简单说下三次握手。
第一次握手
客户端向服务端发送连接请求报文段。该报文段中包含自身的数据通讯初始序号。请求发送后,客户端便进入 SYN-SENT 状态。
第二次握手
服务端收到连接请求报文段后,如果同意连接,则会发送一个应答,该应答中也会包含自身的数据通讯初始序号,发送完成后便进入 SYN-RECEIVED 状态。
第三次握手
当客户端收到连接同意的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入 ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED 状态,此时连接建立成功
。
浏览器解析渲染过程
详细过程在之前的文章已经讲过 ,这里简单叙述下过程
- 根据
HTML
结构生成DOM
树 - 根据
CSS
生成CSSOM
- 将
DOM
和CSSOM
整合形成RenderTree
- 根据
RenderTree
开始渲染和展示 - 遇到
<script>
时,会执行并阻塞渲染
上文中,浏览器已经拿到了 server
端返回的 HTML
内容,开始解析并渲染。最初拿到的内容就是一堆字符串,必须先结构化成计算机擅长处理的基本数据结构,因此要把 HTML
字符串转化成 DOM
树 —— 树是最基本的数据结构之一。
解析过程中,如果遇到<link href="...">
和<script src="...">
这种外链加载 CSS
和 JS
的标签,浏览器会异步下载,下载过程和上文中下载 HTML
的流程一样。只不过,这里下载下来的字符串是 CSS
或者 JS
格式的。
浏览器将 CSS
生成 CSSOM
,再将 DOM
和 CSSOM
整合成 RenderTree
,然后针对 RenderTree
即可进行渲染了。大家可以想一下,有 DOM
结构、有样式,此时就能满足渲染的条件了。另外,这里也可以解释一个问题 —— 为何要将 CSS 放在 HTML 头部?—— 这样会让浏览器尽早拿到 CSS
尽早生成 CSSOM
,然后在解析 HTML
之后可一次性生成最终的 RenderTree
,渲染一次即可。如果 CSS
放在 HTML
底部,会出现渲染卡顿的情况,影响性能和体验。
最后,渲染过程中,如果遇到<script>
就停止渲染,执行 JS
代码。因为浏览器渲染和 JS
执行共用一个线程,而且这里必须是单线程操作,多线程会产生渲染 DOM
冲突。待<script>
内容执行完之后,浏览器继续渲染。最后再思考一个问题 —— 为何要将 JS 放在 HTML 底部?—— JS
放在底部可以保证让浏览器优先渲染完现有的 HTML
内容,让用户先看到内容,体验好。另外,JS
执行如果涉及 DOM
操作,得等待 DOM
解析完成才行,JS
放在底部执行时,HTML
肯定都解析成了 DOM
结构。JS
如果放在 HTML
顶部,JS
执行的时候 HTML
还没来得及转换为 DOM
结构,可能会报错。
<script>
的标签也不是非要放到底部,因为你可以给 script
标签添加async
或者 defer
属性,前者会并行进行下载并执行 JS
,后者会先下载文件,然后等待 HTML
解析完成后顺序执行。