前言
回炉计划第三篇,这是一个经典的前端面试题,考察范围非常的广,可深入的角度也非常的多。本篇文章的目的主要是回顾,所以内容和网络上流传的文章会有一定的重叠性。
过程
过程大致分为以下几个步骤
- DNS 解析
- TCP 连接
- 发送 HTTP 请求
- 服务器处理请求并返回 HTTP 报文
- 浏览器解析渲染页面
- 连接结束
DNS 解析
DNS 服务和 HTTP 协议一样,都是位于应用层的协议,它提供域名到 ip 地址之间的解析服务。 以查询www.baidu.com
的 ip 地址为例
- 浏览器搜索自己的 DNS 缓存(维护一张域名与 IP 地址的对应表)
- 搜索操作系统中的 DNS 缓存(维护一张域名与 IP 地址的对应表)
- 搜索操作系统的 hosts 文件( Windows 环境下,维护一张域名与 IP 地址的对应表
- 操作系统将域名发送至 LDNS(本地区域名服务器,如果你在学校接入互联网,则 LDNS 服务器就在学校,如果通过电信接入互联网,则 LDNS 服务器就在你当地的电信那里。)LDNS 查询自己的 DNS 缓存(一般查找成功率在 80% 左右),查找成功则返回结果,失败则发起一个迭代 DNS 解析请求;
- LDNS 向 Root Name Server (根域名服务器,其虽然没有每个域名的的具体信息,但存储了负责每个域,如 com、net、org 等的解析的顶级域名服务器的地址)发起请求,此处,Root Name Server 返回 com 域的顶级域名服务器的地址;
- LDNS 向 com 域的顶级域名服务器发起请求,返回 baidu.com 域名服务器地址;
- LDNS 向 baidu.com 域名服务器发起请求,得到 www.baidu.com 的 IP 地址;
- LDNS 将得到的 IP 地址返回给操作系统,同时自己也将 IP 地址缓存起来
- 操作系统将 IP 地址返回给浏览器,同时自己也将 IP 地址缓存起来
- 至此,浏览器已经得到了域名对应的 IP 地址
补充:域名和 url 是两个概念,域名是用来确认服务器的地址的,而 url 是用来确认资源的地址;域名和 ip 地址不是一一对应的,一个域名同一时刻只能解析出一个 ip 地址,而一个 ip 地址可以绑定多个域名
TCP 连接
这就涉及到著名的 http 三次握手。简单来说与服务器建立连接需要经历以下三个过程。
- 第一次握手:建立连接时,客户端发送
syn
包(syn=j
)到服务器,并进入 SYN_SEND 状态,等待服务器确认; - 第二次握手:服务器收到
syn
包,必须确认客户的syn
(ack=j+1
),同时自己也发送一个SYN
包(syn=k
),即SYN+ACK
包,此时服务器进入SYN_RECV
状态; - 第三次握手:客户端收到服务器的
SYN+ACK
包,向服务器发送确认包ACK
(ack=k+1
),此包发送完毕,客户端和服务器进入ESTABLISHED
状态,完成三次握手。
这里可以延申拓展一下 HTTPS 的知识 HTTPS 详解--segmentfault
服务器处理请求并返回 HTTP 报文
这一步主要是后端从接口获取到 tcp 报文,处理之后返回HTTP Request
对象,也就是响应报文。
HTTP 响应报文也是由三部分组成: 状态码, 响应报头和响应报文。
状态码由三位数字组成,表示响应的类型。状态码有五大类 1xx,2xx,3xx,4xx,5xx
- 1xx:指示信息–表示请求已接收,继续处理。
- 2xx:成功–表示请求已被成功接收、理解、接受。
- 3xx:重定向–要完成请求必须进行更进一步的操作。
- 4xx:客户端错误–请求有语法错误或请求无法实现。
- 5xx:服务器端错误–服务器未能实现合法的请求。
响应报头 HTTP 请求头提供了关于请求,响应或者其他的发送实体的信息。 HTTP 响应头信息-菜鸟编程
响应报文:就是服务器返回给浏览器的文本信息了,包括 html,js,css 等资源
浏览器解析渲染页面
浏览器拿到响应报文之后,开始解析报文并呈现网页。由于不同的浏览器引擎实现的方法可能不一致,我们以webkit
内核为例进行说明。 WebKit 渲染的过程大致分为四步 浏览器的工作原理:新式网络浏览器幕后揭秘
构建 dom 树 -> 构建 render 树 -> 布局 render 树 -> 绘制 render 树
呈现引擎将开始解析 HTML 文档,并将各标记逐个转化成“内容树”上的 DOM 节点。同时也会解析外部 CSS 文件以及样式元素中的样式数据。HTML 中这些带有视觉指令的样式信息将用于创建另一个树结构:呈现树。
呈现树包含多个带有视觉属性(如颜色和尺寸)的矩形。这些矩形的排列顺序就是它们将在屏幕上显示的顺序。
呈现树构建完毕之后,进入“布局”处理阶段,也就是为每个节点分配一个应出现在屏幕上的确切坐标。下一个阶段是绘制 - 呈现引擎会遍历呈现树,由用户界面后端层将每个节点绘制出来。
需要着重指出的是,这是一个渐进的过程。为达到更好的用户体验,呈现引擎会力求尽快将内容显示在屏幕上。它不必等到整个 HTML 文档解析完毕之后,就会开始构建呈现树和设置布局。在不断接收和处理来自网络的其余内容的同时,呈现引擎会将部分内容解析并显示出来。
上面说到: 浏览器是一个边解析边渲染的过程。这个过程就涉及到两个比较重要的概念:重绘(repain)和回流(reflow)。
当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color
、background-color
、visibility
等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。
当Render Tree
中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。
会导致回流的操作
- 页面首次渲染
- 浏览器窗口大小发生改变
- 元素尺寸或位置发生改变
- 元素内容变化(文字数量或图片大小等等)
- 元素字体大小变化
- 添加或者删除可见的 DOM 元素
- 激活 CSS 伪类(例如::hover)
- 查询某些属性或调用某些方法
会导致回流的属性和方法
clientWidth
、clientHeight
、clientTop
、clientLeft
offsetWidth
、offsetHeight
、offsetTop
、offsetLeft
scrollWidth
、scrollHeight
、scrollTop
、scrollLeft
scrollIntoView()
、scrollIntoViewIfNeeded()
getComputedStyle()
getBoundingClientRect()
scrollTo()
页面在首次加载的时候必定会经历reflow
和repain
。reflow
和 repain
过程是非常消耗性能的,尤其是在移动设备上,它会破坏用户体验,有时会造成页面卡顿。所以我们应该尽可能少的减少 reflow
和 repain
。
连接结束(四次挥手)
TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake),也叫做改进的三次握手。客户端或服务器均可主动发起挥手动作,在 socket 编程中,任何一方执行 close() 操作即可产生挥手操作。
- 第一次挥手(FIN=1,seq=x)
假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为 1 的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。
发送完毕后,客户端进入 FIN_WAIT_1 状态。
- 第二次挥手(ACK=1,ACKnum=x+1)
服务器端确认客户端的 FIN 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。
发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。
- 第三次挥手(FIN=1,seq=y)
服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为 1。
发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个 ACK。
- 第四次挥手(ACK=1,ACKnum=y+1)
客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT 状态,等待可能出现的要求重传的 ACK 包。
服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。
客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。
【问题 1】为什么连接的时候是三次握手,关闭的时候却是四次握手? 答:因为当 Server 端收到 Client 端的 SYN 连接请求报文后,可以直接发送 SYN+ACK 报文。其中 ACK 报文是用来应答的,SYN 报文是用来同步的。但是关闭连接时,当 Server 端收到 FIN 报文时,很可能并不会立即关闭 SOCKET,所以只能先回复一个 ACK 报文,告诉 Client 端,"你发的 FIN 报文我收到了"。只有等到我 Server 端所有的报文都发送完了,我才能发送 FIN 报文,因此不能一起发送。故需要四步握手。
【问题 2】为什么 TIME_WAIT 状态需要经过 2MSL(最大报文段生存时间)才能返回到 CLOSE 状态?
答:虽然按道理,四个报文都发送完毕,我们可以直接进入 CLOSE 状态了,但是我们必须假象网络是不可靠的,有可以最后一个 ACK 丢失。所以 TIME_WAIT 状态就是用来重发可能丢失的 ACK 报文。