浏览器Http请求,响应,渲染机制

1,268 阅读11分钟

🎸🎸🎸 大家好,我是Cola_吉他,一名前端小白,刚才看了点浏览器方面的东西,来这里和大家聊一下 浏览器的原理,以下是我认为比较重要的,因此做了笔记分享给大家,那好喽,废话不多说,直接来看哈🤭🤭🤭。

❓❓❓ 举个例子,当我们打开浏览器,在输入框中输入网址,敲回车键运行时,都发生了什么?

image.png

⭐ 讲之前,我们先来了解一下网络,发送端和接收端之间通过什么来传输数据

『协议』是支撑这么一个庞大而复杂的系统有条不紊运作的核心,所谓『协议』就是通讯双方所必须遵守的规则,在这种规则下,不同的数据报可能被解析为不同的响应动作。

计算机网络分为五层:

    应用层
    运输层
    网络层
    数据链路层
    物理层
    

我们这里分为三个阶段来讲解:

  • HTTP 请求阶段
  • HTTP 响应阶段
  • 浏览器渲染阶段

HTTP 请求阶段=>响应阶段

应用层

『应用层』距离用户最近,主机上的一个个的进程构成了『应用层』。

⭐ 首先 浏览器 会使用 DNS 协议返回 域名所对应的 IP 地址,接着,应用层创建一个 『TCP 套接字』 ,然后将这个请求动作 封装成一个 Http 数据报 并推入套接字中。应用层同时可能会有几十个数据报的发出,而 套接字就是用于区分各个应用层应用的 ,往往由 端口号IP 地址 进行标识。

套接字分为两种类型: 『TCP 套接字』『UDP 套接字』

『TCP 套接字』: 保证数据报可靠地到达目的地,但是耗时

『UDP 套接字』: 不保证数据报一定能到达目的地,但是速度快

DNS 原理

下面我们来看 DNS 的原理 ⬇ ⬇ ⬇

DNS 服务器分为:根 DNS 服务器,顶级域 DNS 服务器和权威 DNS 服务器。

顶级域 DNS 服务器 主要负责 com、org、net、edu、gov 等顶级域名,根 DNS 服务器 存储了所有顶级域 DNS 服务器的 IP 地址,也就是说可以通过根服务器找到顶级域服务(根服务器会返回所有维护顶级域名的 IP 地址)。然后你任意选择其中一个 顶级域服务器 ,请求该 顶级域服务器 拿到域名后应当能够做出判断并给出负责当前域的 权威服务器地址(以百度为例,顶级域服务器将返回所有负责 baidu 这个域的权威服务器地址)。最后,选择其中一个 权威服务器地址 ,向它继续查询 www.baidu.com 的具体 IP 地址,最终 权威服务器 会返回给你具体的 IP 地址

之后,还有个重点:

『本地 DNS 服务器』: 它就等于主机的『助理』一样,帮助主机查询域名的 IP 地址

其实 路由器 不仅给你返回了 IP 地址 ,还会告诉你一个 DNS 服务器地址 ,这个就是你的 本地 DNS 服务器地址 。(也就是说,你的所有域名解析请求只要告诉它就行了,它会帮你查并返回结果给你的)。

- - - 除此之外,本地 DNS 服务器 往往是 具有缓存功能 的,通常两天内的记录都会被缓存,所以大部分时候你是感觉不到域名解析过程的,因为往往就是从 缓存 里拿的,非常快。

下面我们来看这个(这张图是别人的,但能很明确的描述问题):

image.png

♥ 主机负责向自己的 本地 DNS 发送 查询报文 ,如果 本地服务器 缓存中有,将直接从 缓存 中拿取。如果本地服务器发现缓存中没有,会从内置在内部的 根服务器 列表中选一个发送查询报文,根服务器 解析一下 后缀名 ,告诉 本地服务器 负责 .com 的所有 顶级服务器列表 ,本地服务器选择一个 顶级域服务器 继续查询,.com 域服务器拿到域名后继续解析,返回负责 .xx 域的所有 权威服务器 列表,本地服务器从返回的权威服务器之一再次 发送查询报文 ,最终会从某一个权威服务器上得到具体的 IP 地址 ,之后向主机返回结果。

这里只是在应用层 DNS 运作的过程。

接着我们来看 运输层 怎么走的 ⬇ ⬇ ⬇

运输层

运输层 的任务就是将应用层推出 套接字 的所有数据报收集起来,并且按照应用层指定的 运输层协议TCP 或 UDP ,重新封装 应用层数据报 ,并推给 网络层 等待发送。

TCP:基于连接的可靠传输协议(适合于一些对数据完整性要求高的场合)。

UDP: 无连接的不可靠传输协议(适合于那种可以允许数据丢失但对传输速率要求特别高的场景)。

UDP

UDP 不同于 TCP 那样复杂,它既不保证数据可靠的传输到目的地,也不保证数据按序到达目的地,仅仅提供了简单的差错检验。

image.png

在图中,数据应用层 推出来的数据,源端口号 用于 响应报文的交付,目的端口号 用于向目的 进程 交付数据,校验和用于检查传输过程中数据是否受损,如果受损,UDP 将直接丢弃该报文。

TCP

TCP 要稍微复杂些,它是面向连接的,并且基于连接提供了可靠的数据传输服务。 在这里就涉及到了 TCP『三次握手』『四次挥手』

三次握手

image.png

  • 简单来说就是 :

    1. 客户端向服务端发送SYN
    2. 服务端返回SYN,ACK
    3. 客户端发送ACK
  • 从理解角度来说:

三次握手的目的是建立可靠的通信信道,主要的目的就是双方确认自己与对方的发送与接收机能正常。

  1. 第一次握手:客户什么都不能确认;服务器确认了对方发送正常
  2. 第二次握手:客户确认了:自己发送、接收正常,对方发送、接收正常;服务器确认 了:自己接收正常,对方发送正常
  3. 第三次握手:客户确认了:自己发送、接收正常,对方发送、接收正常;服务器确认 了:自己发送、接收正常,对方发送接收正常 所以三次握手就能确认双发收发功能都正常,缺一不可。
四次挥手

在网络数据传输中,传输层协议断开连接的过程我们称为四次挥手。

image.png

  • 第一次挥手:Client将FIN置为1,发送一个序列号seq给Server;进入FIN_WAIT_1状态;
  • 第二次挥手:Server收到FIN之后,发送一个ACK=1,acknowledge number=收到的序列号+1;进入CLOSE_WAIT状态。此时客户端已经没有要发送的数据了,但仍可以接受服务器发来的数据。
  • 第三次挥手:Server将FIN置1,发送一个序列号给Client;进入LAST_ACK状态;
  • 第四次挥手:Client收到服务器的FIN后,进入TIME_WAIT状态;接着将ACK置1,发送一个acknowledge number=序列号+1给服务器;服务器收到后,确认acknowledge number后,变为CLOSED状态,不再向客户端发送数据。客户端等待2*MSL(报文段最长寿命)时间后,也进入CLOSED状态。完成四次挥手。

从理解角度分析:

四次挥手断开连接是因为要确定数据全部传书完了

  1. 客户与服务器交谈结束之后,客户要结束此次会话,就会对服务器说:我要关闭连接了(第一 次挥手)。
  2. 服务器收到客户的消息后说:好的,你要关闭连接了。(第二次挥手)。
  3. 然后服务器确定了没有话要和客户说了,服务器就会对客户说,我要关闭连接了。(第三次挥手)。
  4. 客户收到服务器要结束连接的消息后说:已收到你要关闭连接的消息。(第四次挥手),才关闭。

运输层 的任务就是从应用层的各个进程的 套接字 那取回来所有需要发送的数据,然后选择 TCP 或者 UDP 将数据封装并推给下面的 网络层 待发送,之后就会进入我们的网络层。

这里 网络层数据链路层物理层 相对比较偏,有疑问的可以自己搜下看一看,这里只说下做了什么吧。

网络层

网络层 其实解决的就是一个 转发 的问题,通过 IP 协议 划分了网络范围,即我没有直接用网线和你连在一起,我也能通过你的 IP 分析出该怎么样找到负责你的网关 路由器 ,并通过你的网关路由给你传输 数据报

数据链路层

网络层 解决的是,分组转发的目的网络,也就是转发给目的网络的网关路由 ,而 链路层 解决的是,将 分组广播给个人 ,也即目的主机。

物理层

物理层 就是一些0 1 0 1之类的电信号传输

🏃‍🏃‍🏃‍ 到最后一个阶段了,也是知识点最多的一个阶段,大家好好看,可以轻松的理解很多优化方法的原因。

浏览器渲染阶段

举例解析

首先我们先来理解这三个之间的关系: 进程(Process)线程(Thread)栈(Stack)

进程是一个工厂,工厂有它的独立资源;
工厂之间相互独立(进程之间独立);
线程是工厂中的工人,多个工人协作完成任务; 
工厂内有一个或多个工人(可以有多个线程) ;
工人之间共享空间(线程之间可以相互传递任务);

渲染过程

下面我们来看访问地址内部怎么运行的,先来看个图片:

⬇ ⬇ ⬇

image.png

⭐ 首先在 客户端 输入地址,会发送 Request请求 ,中间会经过DNS解析TCP协议的三次握手和四次挥手 ,进入 服务器端,拿到代码后,浏览器会在 内存条 中开辟出一块 栈内存 ,用来给代码提供环境,同时分配一个 主线程 一行一行解析代码,解析代码过程中会进行 进栈出栈 操作,当浏览器遇到 link/script/img 等请求时,都会开辟出 全新的线程等待任务对列(Task Queue) 中加载资源文件。当从上到下走完后,会生成 DOM树 ,然后线程会去 等待任务对列(Task Queue) 中将加载好的资源文件一个一个搬出来,当全部处理完后,会生成CSSOM树 ,然后 DOM树 会和 CSSOM树 结合成 Render Tree渲染树 ,渲染树会通过 回流 计算 视口 大小,进行重绘 ,最后通过 GPU 展示在页面上。

代码加载顺序: DOM树渲染(html)==> 生成CSSOM(css),两者结合生成Render Tree(渲染树)== >回流(计算视口大小)==> 重绘 ==> Gpu展示在页面上

  • 重绘 :元素样式的改变(宽高位置等不变)。
  • 回流 :元素大小或位置发生改变,触发了重新布局,呆滞渲染树重新计算布局和渲染。

性能优化

1.减少http请求的次数和大小。

  • 资源合并压缩
  • 图片懒加载
  • 所有的音视频走流文件

2.避免dom重绘和回流。

  • 避免操作DOM(基于Vue,React);
  • 分离读写:(将样式设置放在一起(浏览器有等待_机制,读和写操作分开会避免触发回流);
  • 尽量批量处理;
  • 缓存处理;
  • 元素批量修改;
  • 动画效果应用到position和fixed元素上(脱离文档流);
  • 牺牲平滑度换取速度。
  • Css 硬件加速(GPU加速)(transform/opacity等会触发发GPU加速,不会引发回流和重绘);

⭐ 在Js Jq中,offsetTop,offsetWidth,offsetLeft,offsetHeight,clientTop,clientWidth,clientLeft,clientHeight,scrollTop,scrollWidth,scrollLeft,scrollHeight,getComputedStyle,currentStyle 等都会刷新渲染对列。

分离读写:

在最新浏览器中有等待阶段,会自动检测下一行,如果下一行和上一行都是读操作或者都是写操作,浏览器会只回流一次,否则会多次回流。

    <script>
        let box = document.getElementById('box');
        box.style.width = '200px';
        //会触发两次回流
        console.1og(box.clientWidth); 
        box.style.height ='200px';
        box.style.margin = '10px';
    </script>
    
    <script>
        let box = document.getElementById('box');
        //只会触发一次回流
        box.style.width = '200px';
        box.style.height ='200px';
        box.style.margin = '10px';
        console.1og(box.clientWidth);
    </script>

🌙🌙🌙 好啦,大概就这些了哈,好好理解这些,可是面试中的重点哦!加油哈,🤭 🤭 🤭,晚安喽( ̄o ̄) . z Z!!!

image.png