- (构建请求行)输入url后,浏览器会解析这个统一资源定位符,协议头,主机域名和ip地址,端口,目录路径,查询参数,hash值,做一个基本的筛查
- (查询浏览器缓存)浏览器查看缓存,如果请求资源在缓存中并且新鲜,跳转到转码步骤(强缓存 与 协商缓存),否则就对 URL 进行解析,确定 Web 服务器和文件名,根据这些信息来生成 HTTP 请求。
- (解析IP)解析IP地址,查询服务器域名 对应的 ip地址,因为 委托操作系统 发送消息时,必须提供通信对象的ip地址。
- (1)首先查找的是浏览器的 DNS 缓存;浏览器会缓存 DNS 记录一段时间。所以浏览器会先去查自己的缓存,要是域名在缓存中有记录则使用缓存中的IP进行下一步处理。要是没有则进行下一步查找。
- (2)查询系统缓存:如果浏览器缓存中没有,浏览器会去查找系统中记录的DNS信息。先检查域名是否在本地 hosts 里,再查系统本地缓存的其他 DNS 记录。
- (3)查路由器缓存:如果在系统缓存里面还是没找到对应的IP,那么接着会发送一个请求到路由器上,然后路由器在自己的路由器缓存上查找记录,路由器一般也存有DNS信息。
- (4)ISP DNS缓存(电信网,联通网和移动网):这里通过发送DNS查询报文给 ISP 也是很有深度的,首先你要知道ISP的IP地址。 如果 DNS 服务器和我们的主机在同一个子网内,需要对 DNS 服务器进行 ARP 查询;如果 DNS 服务器和我们的主机在不同的子网,则会对默认网关进行查询。
- (5)递归 DNS 查询:找到专门保存 web 服务器与 ip 对应关系的服务 DNS 服务器。客户端只要找到任意一台 DNS 服务器,就能通过它找到根域 DNS 服务器,然后顺藤摸瓜找到下层某台目标 DNS 服务器
-
- 客户端会先发送一个 DNS 请求,问输入的 url 的 ip 是啥,并且发送给本地的DNS服务器(就是客户端tcp/ip设置中填写的 DNS 地址)
-
- 本地域名服务器收到客户端的请求后,如果缓存里的表格能找到输入的 url ,则直接返回 IP 地址,如果没有 ?,本地的 DNS 回去问它的根域名服务器 询问输入的url的ip地址,根域名服务器是最高层次的,它不直接用于域名解析,但能指条明路。
-
- 根 DNS 收到本地 DNS 的请求后,发现后置是 .com, 就会返回 .com 的顶级域名服务器
-
- 本地的 DNS 收到顶级域名服务器的地址后,发起请求询问顶级域名服务器 输入的url的ip地址是什么
-
- 顶级域名服务器收到请求会返回输入的 url 区域的权威DNS服务器地址
-
- 本地 DNS 于是转头去问权威 DNS 服务器,输入地址的权威 DNS 服务器就是域名解析结果的原出处
-
- 权威 DNS 服务器查询后将对应的 ip 地址告诉本地的 DNS
-
- 本地 DNS 再将 ip 地址返回给客户端,客户端与目标建立连接
-
现在找到ip地址了,需要把数据包发出去,DNS 找到 IP 后,就可以把 http 的传输工作交给操作系统中的协议栈。浏览器通过调用 socket 库,来委托协议栈工作。 协议的上半部分负责收发数据的 tcp 和 udp 协议,他俩会接受应用层的委托执行收发数据的操作
-
(建立TCP连接)端口建立TCP连接,TCP 传输数据之前,要先三次握手 🤝 建立连接
-
- 一开始,客户端与服务器都属于 closed 状态。 先是服务端主动监听某个端口,处于 listen 状态。
-
- 客户端发起连接 SYN, 发送端首先发送一个带 SYN(synchronize)标志的数据包给接收方,第一次的 seq 序列号是随机产生的,这样是为了网络安全,如果不是随机产生初始序列号,黑客将会以很容易的方式获取到你
-
与其它主机之间的初始化 序列号,并且伪造序列号 进行攻击。之后处于 SYN- SENT 状态。
-
- 客户端向服务器发送一段 TCP 报文,其中标志位为 SYN,序号为 Seq:x,随后客户端进入 SYN-SENT 阶段
-
- 服务器收到来自客户端的 TCP 报文之后,结束 LISTEN 阶段,并返回一段 TCP 报文,其中标志位为 SYN 和 ACK,标签确认客户端发的 Seq 序号有效,服务器能正常接收客户端发的数据,并同意创建连接。确认号为 ACK=x+1, 表示收到了客户端的Seq序号并且将其值+1作为自己确认号Ack的值,随后服务器进入 SYN-RCVD阶段,序号Seq=y
-
- 客户端收到来自服务器端的确认 收到数据的 TCP 报文之后,明确了从客户端到服务器的传输是正常的,结束 SYN-SENT 阶段,并返回最后一段 TCP 报文
- 标志位ACK: 确认服务器端同意连接的信息
- 序号为Seq=x+1,表示收到服务器端的确认号ACK,并将其作为自己的序号值
- 确认号为Ack=y+1, 表示收到服务器端序号Seq,并将其值加一作为自己的确认号 Ack 的值
- 随后客户端进入 ESTABLISHED 阶段
- 若在握手某个过程中某个阶段莫名中断,TCP 协议会再次以相同的顺序发送 相同 的数据包
- (数据包完善头部信息)在双方建立了连接后,生成 tcp 报文, TCP 报文中的数据 部分就是存放HTTP头部 + 数据,组装好 TCP 报文之后,就需交给下面的网络层处理。
TCP 模块在执行连接、收发、断开等各阶段操作时,都需要委托 IP 模块将数据封装成网络包发送给通信对象。生成了 IP 头部之后,接下来网络包还需要在 IP 头部的前面加上 MAC 头部。 发送方的 MAC 地址是网卡生产时写入到 ROM 里的,只要将这个值读取出来写入到 MAC 头部就可以了。 接收方的 MAC 地址就有点复杂了,只要告诉以太网对方的 MAC 的地址,以太网就会帮我们把包发送过去,那么很显然这里应该填写对方的 MAC 地址。 在发包时,先查询 ARP 缓存,如果其中已经保存了对方的 MAC 地址,就不需要发送 ARP 查询,直接使用 ARP 缓存中的地址。 而当 ARP 缓存中不存在对方 MAC 地址时,此时就需要 ARP 协议帮我们找到路由器的 MAC 地址。 ARP 协议会在以太网中以 广播的形式,对以太网所有的设备喊出:“这个 IP 地址是谁的?请把你的 MAC 地址告诉我”。
-
(数据发送)网卡驱动以 IP 模块获取到包之后,会将其复制到网卡内的缓存区中,接着会其开头加上报文和起始帧分界符,在末尾加上用于检测错误的帧校验序列。 最后网卡会将包转为电信号,通过网线发送出去。首先,电信号到达网线接口,交换机里的模块进行接收,接下来交换机里的模块将电信号转换为数字信息, 数据包通过交换机转发抵达了路由器,网络包就到达了最终的目的地。
-
(抵达服务器)数据包抵达服务器后,服务器会先扒开数据包的 MAC 头部,查看是否和服务器自己的 MAC 地址符合,符合就将包收起来。 接着继续扒开数据包的IP头,发现IP地址符合,根据IP头中协议项,知道自己上层是 TCP 协议。于是,扒开TCP的头,里面有序列号,需要看一看这个序列号是不是我想要的, 如果是 就放入缓存中 然后返回一个 ACK ,如果不是就丢弃。 TCP头部里面还有端口号,HTTP 的服务器正在监听这个端口号。 于是,服务器自然就知道是 HTTP 进程想要这个包,于是就将包发给 HTTP 进程。服务器的HTTP进程看到,原来这个请求是要访问一个页面 于是就把这个网页封装在 HTTP 响应报文里。 HTTP响应报文也需要 穿上TCP、IP、MAC头部,目的地址是客户端IP地址。
-
(客户端收到响应数据包)客户端收到了服务器的响应数据包后
-
四次挥手双方连接断开
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。 这原则是当一方完成它的数据发送任务后就能发送一个 FIN 来终止这个方向的连接。收到一个 FIN 只意味着这一方向没有数据流动,一个 TCP 连接在收到一个 FIN 后仍能发送数据。
-
- 首先进行关闭的一方 将执行主动关闭,而另一方执行被动关闭。
-
- TCP 客户端发送一个 FIN,用来关闭客户到服务器的数据传送。
-
- 服务器收到这个 FIN,它发回一个 ACK,确认序号为收到的序号加1。和 SYN 一样, 一个 FIN 将占用一个序号。
-
- 服务器关闭客户端的连接,发送一个 FIN 给客户端。
-
- 客户端发回 ACK 报文确认,并将确认序号设置为收到序号加1。
关闭连接时,客户端向服务器发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。 服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文, 而服务器可能还有数据 需要处理 和 发送, 等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。
从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务器的 ACK 和 FIN 一般都会分开发送,从而比三次握手导致多了一次。
- (渲染html)返回html后,浏览器开始顺序加载并渲染 DOM, 当浏览器遇见 body 标签才是真正开始加载并起渲染 dom,此时会有以下几种情况, 遇见 dom 元素就正常顺序加载,边加载边渲染,当遇见内联 css 时,浏览器继续加载,但渲染会被阻塞,此时会生成新的 Css Rule Tree,生成后重新渲染页面, 在 head 标签下的 style 标签中,选择器 + 样式声明,当遇见外联 css 时(link),浏览器启 一个线程加载 css,DOM 继续加载但渲染被阻塞, 遇见内联 js,浏览器会执行这段脚本,dom的加载和渲染同时被阻塞(因为js有可能会更改dom Tree 和render Tree因此同时被阻塞), 遇到外联 js,浏览器会下载这一段脚本,下载成功后执行它,整个过程DOM的加载和渲染同时被阻塞。
如果 script 标签中包含 defer,那么这一块脚本将不会影响 HTML 文档的解析,而是等到 HTML 解析完成后才会执行。 而 DOMContentLoaded 只有在 defer 脚本结束后才会触发。
-
- HTML 还没有解析完成时,defer 脚本已经加载完毕,那么 defer 脚本将等待 HTML 解析完成后执行。 defer 脚本执行完毕后触发 DOMContentLoaded 事件。
-
- HTML 解析完成时,defer 脚本还没有加载完毕,那么 defer 脚本继续加载,加载完成后直接执行,执行完毕后 触发 DOMContentLoaded 事件。
在绘制的过程中,会遍历渲染树,调用由浏览器的UI组件的 paint() 方法在屏幕上显示对应的内容,并根据渲染树布局,计算CSS样式(既每个节点在页面中的大小和位置等几何信息)。 HTML默认是从上到下流式布局的,CSS 和 JS 的加入会打破这种布局,改变DOM的外观样式以及大小和位置。这就引出两个非常重要的概念:repaint重绘 和 reflow重排。
repaint重绘,屏幕的一部分重新绘制,不影响整体布局,比如某个CSS的背景色变了,但元素的几何尺寸和位置不变。 reflow重排,意味着元件的几何尺寸变了,我们需要重新验证并计算渲染树。是渲染树的一部分或全部发生了变化。 对浏览器而言都是一种「消耗」,所以我们应该尽量减少这两种状态的触发。