这个问题可以分两个方面回答:网络通信和浏览器渲染。
网络通信
DNS解析
用户输入url点击回车。先会进行DNS解析。
由于用户输入的url浏览器不能识别,因此需要通过DNS域名系统(域名和IP地址映射)对url进行解析,得到想要得到网站的主机名对应的ip地址。
- 浏览器首先会搜索自身DNS缓存,浏览器缓存大概1000条
- 如果本机没找到,就会在操作系统中寻找。
- 如果还未找到,就对DNS服务器发起域名解析请求,最后在根域名中的DNS中找到ip地址。
TCP三次握手
拿到对应的ip之后,浏览器会以随机端口向服务器的http80端口发起TCP连接请求,这个请求通过4层模型的封包,根据IP地址,通过各种路由、网关(ARP,MAC),到达服务器端,进入内核的TCO/IP协议栈,层层解包,最终到达应用层,建立TCP/IP连接。
因为TCP是面向连接的,所以需要先通过三次握手建立连接,三次握手的目的:同步连接双方的序列号和确认号,交换TCP窗口信息。
- 第一次握手:建立连接。客户端发送连接请求报文段,将SYN位 置为1,Sequence Number(seq)为x;然后,客户端进入SYN_SEND状态,等待服务器的确认;
- 服务器收到SYN报文段。对报文进行确认,将ACK设置为x+1(seq+1) ,服务器端将所有信息(即SYN+ACK+seq报文段)放到一个报文段中,一并发送给客户端,此时服务器进入SYN_RECV状态;
- 客户端收到SYN+ACK+Seq报文,将ACK设置为y+1(seq+1),向服务器发送ACK报文段,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。
为什么要三次握手
防止已失效的连接请求报文段传送给服务器。client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。这样,server的很多资源就白白浪费掉了
在这里提前普及一下,在数据传输完毕后,断开TCP连接,就是四次挥手。
- 主机1(挥手既可以是客户端也可以是服务器端),设置sequence number ,向主机发送一个FIN报文段,主机1进入FIN_WAIT_1状态,表示主机1没有数据发给主机2了
- 主机2收到主机1发送的FIN报文段,向主机1回一个ACK报文段,主机1进入FIN_WAIT_2状态
- 主机2向主机1发送FIN报文段,请求关闭连接,主机2进入LAST_ACK状态
- 主机1收到主机2的报文段,向主机2发送ACK报文,主机2接到后关闭连接,主机1等待MSL后,关闭连接(MSL报文最大生存时间,TCP的TIME_WAIT状态也称为2MSL等待状态)
为什么要四次分手
TCP面向连接,且全双工,主机1没有数据发送的时候,主机2可能还在发送数据,所以主机2告诉主机1收到数据,还要告诉主机1发送完数据。
为什么要等待2MSL
它是任何报文段被丢弃前在网络内的最长时间,超过这个时间报文会被丢弃。如果主机1直接close,然后再向相同服务器相同端口发送请求,可能会混淆数据包。此外主机2没有收到ACK,那么就会继续发送一次FIN
发起http请求
建立TCP/IP连接后,客户端向服务器端发起http请求,
浏览器渲染流程
浏览器主要组成
渲染引擎流程
-
解析html构建DOM树:
浏览器接收到服务器响应来的html文档开始,遍历文档节点,字节转化为字符,字符转化为标记,生成dom树。
html解析分为标记化和树构建。词法分析时将输入内容解析成多个标记,HTML标记包括起始标记、结束标记、属性名称和属性值。标记生成器识别标记,传递给树构造器,然后接受下一个字符以识别下一个标记;如此反复直到输入的结束。 标记生成器发送的每个节点都会由树构建器进行处理。
初始状态是数据状态。遇到字符 < 时,状态更改为“标记打开状态”。接收一个 a-z字符会创建“起始标记”,状态更改为“标记名称状态”。这个状态会一直保持到接收> 字符。在此期间接收的每个字符都会附加到新的标记名称上。
构建dom树
2.解析css 构建cssom树:
浏览器解析css文件并城市css树。css从右向左解析,嵌套越多越增加浏览器工作量
- 渲染阻塞:
浏览器遇到script标签,dom构建将停止,直到脚本完成执行,然后继续构建dom。所以在渲染时遵循两个原则:css引入在前,js放在页面底部,尽量少影响dom的构建,延长首绘时间,可以用async和defer实现异步加载。js解析由js解析器执行。
浏览器碰到script标签,会立刻执行脚本
async加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步),加载完立刻执行
defer加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。
defer和async区别在于下载完后何时执行,defer性能更优
- 构建渲染树:
渲染引擎渲染。浏览器先从dom树中遍历可见节点,并找到其适配的css样式。dom树 css树 渲染树交叉进行,边加载、边解析、边渲染
渲染树构建完成,渲染树和dom树并非一一对应,每个节点都是可见节点并且都含有其内容和对应规则的样式。display:none不会被显示在树中,visibility:hidden会显示在树中。 - 渲染树布局
布局阶段会从渲染树的根节点开始遍历,然后确定每个节点对象在页面上的确切大小与位置,布局阶段的输出是一个盒子模型,它会精确地捕获每个元素在屏幕内的确切位置与大小。
布局过程为:
父节点确定自己的宽度
父节点完成子节点放置,确定其相对坐标
节点确定自己的宽度和高度
父节点根据所有的子节点高度计算自己的高度
- 渲染树绘制
在绘制阶段,遍历渲染树,调用渲染器的paint方法在屏幕上显示其内容。由浏览器的ui负责
网友评论