URL输入到页面展示是前端面试或者补充知识都挺全面的一道题。
稍微梳理一下知识点,如有不对,请各位大佬指出
进程和线程
- 进程,每当一个程序运行,系统会分配一定的内存用于保存变量,运行主线程等,这样的一个环境叫做进程。
- 线程,操作系统能够进行运算调度的最小单位,是依附于进程的。
浏览器多进程
- 浏览器主进程,提供界面交互,管理子进程,文件存储等功能。
- 网络进程,提供下载网络资源的能力
- 插件进程,运行插件的进程
- GPU进程,原是用来渲染CSS 3D的,后用来渲染UI界面
- 渲染进程,把HTML,CSS,JavaScript转为用户可与之交互的页面,JavaScript引擎便运行在渲染进程内。
从输入URL到页面展示
1. 用户输入
首先浏览器先检查用户输入的是符合规则的URL还是关键字,分别有以下步骤
- 关键字,浏览器会用默认的搜索引擎拼接上关键字查询参数,组成一个新的URL,发起一个请求。
- 如果用户输入符合URL规则,例如www.a.com ,那么就会拼接上协议等,组成一个完整的URL。同时页面进入加载状态,也就是转圈圈。等待提交文档阶段,才会替换内容。
2.URL请求
确定了用户输入的是某个URL,然后进入
- 浏览器主进程通过进程间通信(IPC),把URL请求发送至网络进程。
- 网络进程首先检查缓存,也就是强缓存,如果有该缓存资源,那么直接返还给浏览器进程,没有则进入网络请求过程。
- 第一步是获取IP地址,也就是DNS解析,获取到服务器的IP地址,没有说明端口号,默认是80端口
DNS解析
-
首先检查浏览器缓存,有则返回,无则继续
-
检查操作系统缓存,也就是host文件
-
检查路由器缓存
-
向ISP(互联网服务提供商)的Local DNS查询
-
如果还没有,继续往下
- 域名其实还有根域名,例如
www.a.com.root,首先会像根域名服务器发起请求,以获得com顶级域名的服务器地址 - 接着向顶级域名服务器发起请求查询次级域名服务器地址
- 再向次级域名服务器发起请求查询下一级域名服务器地址
- Local DNS,缓存域名和IP地址,返回给浏览器,并缓存在本地系统中
- 域名其实还有根域名,例如
TCP三次握手
查询到IP地址和端口后,就进行TCP的三次握手了
- 服务端首先要在某个端口进入
Listen状态 - 客户端发送SYN同步报文,把
SYN标志位置1,序列号(sequence number) 生成一个跟时间有关的随机数,目的是为了告诉服务端客户端的初始序列号,序列号的作用是解决包乱序、重复,以及对端回复确认收到的数据,同时客户端进入SYN_SENT状态 - 服务端收到客户端的
SYN包后,回复SYN+ACK,序列号也是生成一个服务端的序列号,确认号为客户端传来的序列号+1,告诉客户端,这个序列号之前的数据都已经接收到,其实在这里就是回复收到了客户端的请求报文。服务端进入SYN_RCVD状态。 - 客户端收到服务端的
SYN+ACK包后,回复ACK,也代表客户端收到了服务端的同步报文,同时,客户端进入ESTABLISHED状态。服务端收到ACK包后,进入ESTABLISHED状态。
两次握手可以吗
不可以哦,三次握手为了确认客户端、服务端的接收,发送能力,两次握手不能确认客户端的接收功能。
至于 四次、五次可以吗,只要你喜欢,100次都可以。但是浪费时间。
HTTPS
如果请求的URL地址是HTTPS协议,那么还要进行TLS握手。
-
客户端发送
Client Hello包,里面主要包含密码套件、客户端随机数Client_Random、TLS版本,密码套件包括HTTPS中使用的算法对称加密、非对称加密、摘要算法,例如Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f),分别是非对称加密算法,非对称加密算法(可能是用于身份验证,私钥加密)、对称加密算法、摘要算法 -
服务端发送
Server Hello包,包含选择的密码套件、服务器随机数Server_Random,版本号 -
服务端发送证书
-
如果是
ECDHE算法,服务端发送Server Key Exchange,主要是为了发送Server_Param,用于生成第三个数,也就是配合Client_Param生成合成会话密钥的最后一个数. -
服务端发送
Server Hello Done,打招呼结束了 -
客户端收到服务端证书,进行校验
- 证书有服务器公钥,服务器其它信息,还有一个数字签名,客户端用系统内置的CA公钥来解密数字签名,获得一个消息摘要。
- 根据服务器公钥,以及其它内容,通过相同的
HASH算法,生成一个消息摘要,生成的消息摘要跟解密获得的消息摘要比较是否相同。相同则该公钥是服务器的公钥,也就是完成服务器的身份验证
-
校验证书通过后,用获得的服务器公钥加密发送
Client Key Exchange,给服务器发送Client_Param。同时,自己根据Client_Param、Server_Param,生成一个pre-master,再根据Client_Random、Server_Random、pre-master生成最终的会话密钥---对称加密的钥匙 -
客户端发送
Change Cipher Spec,代表下面的通信就将使用加密通信,也为了校验会话密钥两者是否相同。 -
服务端也用同样的规则,生成一个会话密钥,也发送
Change Cipher Spec,TLS握手结束。
HTTP请求
握手完成之后,就要构建请求行,例如包含GET /index.html http/1.1.
- 首先发送请求行
- 发送请求头
- 如果是
POST请求等,再发送请求体
HTTP响应
等待服务器返回响应。
响应也是状态行、响应头、响应体
根据状态码,如果是301,那么就要根据Location进行重定向了,
如果是200,代表浏览器可以继续处理请求。
根据Content-Type,如果是文件,就交给浏览器的下载管理器去下载。
如果是HTML,就提供给渲染进程,进行页面的渲染了。
开始渲染
-
默认情况下,Chrome会为每一个页面分配一个渲染进程,但是同一站点,(根域名+协议)例如a.com的情况下,复用同一个渲染进程。
-
提交文档 这里的文档是指响应体的数据 。
- "提交文档"由浏览器进程发出给渲染进程,渲染进程收到后,和网络进程建立传输数据的通道。
- "确认提交",等待文档数据传输完成之后,渲染进程发出"确认提交"消息给浏览器进程
- 浏览器进程收到"确认提交"的消息后,会更新浏览器界面状态。包括
安全状态、地址栏的URL、前进后退的历史记录,更新页面 - 然后就进入了渲染阶段,一旦页面完成渲染,渲染进程会发送一个消息给浏览器进程,浏览器收到通知后,停止转圈圈
渲染流水线
通过输入一个HTML文件,通过渲染模块,最后输出为屏幕的像素。
中间的渲染模块可分为:
- 构建DOM树
- 样式计算
- 布局阶段
- 分层
- 绘制
- 分块
- 光栅化
- 合成
构建DOM树
为什么要构建DOM树呢?因为浏览器无法直接理解和使用HTML,所以要将HTML转为浏览器认识的DOM树。
构建DOM树的输⼊内容是⼀个HTML⽂件,然后经由HTML解析器 解析,最终输出树状结构的DOM
- 通过分词器把HTML字节流转换为
Token,例如startTag Token和endTag Token,并且HTML解析器维护了一个栈结构,用来确定DOM树中的父子关系,每压入一个startTag Token,就会挂载到原栈顶下面去,遇到entTag Token就会弹出一个startTag Token,表示该元素没有子元素了 - 通过HTML解析器会创建一个默认有
document的DOM结构,每有一个startTag Token或其它token就会挂载上去,这样就形成了一个DOM结构
样式计算
样式计算的⽬的是为了计算出DOM节点中每个元素的具体样式
CSS样式来源主要有3种:
- link外部样式表
- style标签内的样式
- 标签的内嵌样式
第一步、浏览器一样无法认识CSS,需要转为浏览器认识的--styleSheets
第二步、样式属性值标准化,需要将所有值转换为渲染引擎容易理解的、标准化的计算值,
例如2em -> font-size: 32px; 颜色转RGB
第三步、计算出DOM树中每个节点的具体样式
涉及CSS的继承规则和层叠规则
- CSS继承就是每个DOM节点都包含有⽗节点的样式
- CSS层叠,也就是权重
这个阶段最终输出的内容是每个DOM节点的样式,并被保存 在ComputedStyle的结构内
布局计算
那么接下来就需要计算出DOM树中可见元素的几何位置,我们把这个计 算过程叫做布局。
Chrome在布局阶段需要完成两个任务:创建布局树和布局计算
-
创建布局树 有些元素是隐藏的,所以要额外构建一棵只包含可见元素的布局树
遍历DOM树中的所有可⻅节点,并把这些节点加到布局中 -
布局计算 计算布局树中节点的坐标位置,把计算好的布局节点,写回到布局树中。
分层
因为页面中有一些复杂的效果,或者是z-index来做Z轴排序。
渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树。
浏览器的页面实际上被分成了很多层,这些图层叠加后合成了最终的页面。
通常情况下,并不是每一个节点都包含一个图层,如果一个节点没有对应的层,默认归属于父节点的图层
什么情况下会创建一个单独的图层
-
拥有层叠上下文属性的元素会被提升为一层 层叠上下文
例如position不为static,z-index不为auto,就会创建一个图层
flex布局的子元素,z-index不为none,则会创建一个图层 -
需要裁剪(clip)的地方也会被创建为图层 例如超级多文字在一个
200 * 200的div中,超出的部分会被裁剪掉,这个div会被提升为一个层
图层绘制
渲染引擎会把⼀个图层的绘制拆分成很多⼩的绘制指令,然后再把这些指令按照顺序组成⼀个待绘制列表。
在控制台的layer(打开控制台,右上角3个点,more tool,layer)中可以看到
栅格化操作
绘制列表只是⽤来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的
在有些情况下,有的图层可以很大,比如有的页⾯你使⽤滚动条要滚动好久才能滚动到底部,但是通过视口,用户只能看到页⾯的很小⼀部分,所以在这种情况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要。
合成线程会将图层划分为图块, 然后合成线程会按照视口附近的图块来优先⽣成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。而图块是栅格化执⾏的最小单位。渲染进程维护了⼀个栅格化的线程池,所有的图块栅格化都是在线程池内执⾏的
渲染进程把成图块的指令发送给GPU,然后在GPU中执行生成图块的位图,并保存在GPU的内存中
合成和显示
一旦所有图块都被栅格化,合成线程会发出一个绘制图块的命令。把该命令提交给浏览器进程。
浏览器进程中有一个viz的组件,用来接收绘制图块的命令。然后将内容页面绘制到内存中,最后将内存显示在屏幕上。
四次挥手
假如客户端没有数据发送,要关闭TCP连接了。
1、 客户端发送FIN包,把FIN标志位置1,客户端进入FIN-WAIT1状态。
2、 服务端回复ACK包,服务端进入CLOST-WAIT状态。
3、 客户端接收到ACK包后,进入FIN-WAIT2状态。
等待服务器的主动关闭
4、 服务器发送FIN报文。服务器进入LAST-ACK状态
5、 客户端回复ACK报文,客户端进入TIME_WAIT状态,等待2MSL后,进入CLOSE状态。
6、 服务器收到回复后,进入CLOSE状态。
三次挥手可以吗
可以但不好,由于TCP有延迟确认,在服务端回复客户端ACK时,合并FIN报文,也就是合并3、4步骤,但可能因为处理时间太久,客户端进入超时重传阶段,甚至太久的话,客户端会因为收不到回复,直接关闭连接了,服务端还处于LAST-ACK阶段。无法用于下一个TCP连接。
等待2MLS
IP网络层有TTL也就是time to live,每经过一个路由,该值减1, 到0时就会被丢弃这个包。
在传输层,根据TTL,假设这个包最大存活时间为一个MSL(MAX SEGMENT LIFETIME),也就是多少秒后,这个包就不见了。
2MSL = 回复的ACK能到达服务端 + 如果该ACK没到达,保证服务端重传的FIN报文能够收到.
如果重传的FIN也丢了,那就进入超时重传,直到关闭连接吧
keep-alive
由于connection: keep-alive允许复用TCP连接,那么到底什么时候关闭TCP连接呢?
- timeout,配置超时时间,连接建立多久之后就关闭TCP连接
- 请求次数,请求多少次之后就关闭