本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!
🌻 前言
书接上回~
系列文章目录:
本文是系列文章的最后一篇。看标题就知道这是一道常考面试题,但是你不能仅仅把他当作是个面试题,因为它能大大提升你对浏览器的认知。
本文将从进程、网络、引擎等方面从底层深入剖析网页加载的全过程,主打的就是细致入微。文中提到的一些知识,如果一带而过,可以查看本系列前几篇中了解更多。
🎏 超详细流程
(一)键入地址
我们键入地址是在浏览器的地址栏输入,这部分是由浏览器的浏览器进程
管理的,浏览器的用户界面和用户交互都是由浏览器进程管理的。
当我们输入时,浏览器进程会从历史记录、书签等地方模糊搜索,给出智能补全
提示。最终输入后,浏览器会判断输入的是搜索内容,还是URL,如果输入内容符合URL规则就把内容加上协议,合成完整的URL链接。否则就将内容和默认的搜索引擎合成新的URL
浏览器进程不仅要处理浏览器的用户界面,还负责协调管理浏览器的所有其他进程。
当浏览器接收到输入的 URL 后,会把URL发送给网络进程
,由网络进程进行后续的URL解析、DNS查询等操作;
(二)URL解析、DNS查询
网络进程
拿到 URL 后,会先解析URL
的各个组成部分,包括协议(如http或https)、主机名(或IP地址)、端口号(如果有)、路径和查询参数等。
解析到域名后,需要将域名转换为对应的IP,才能对目标地址发送请求。所以需要先进行DNS查询
。
以下是DNS查询的过程:
-
本地缓存查询:首先,这个本地是先查询浏览器缓存 > 系统缓存hosts文件 > 路由器缓存;
-
递归查询:如果在本地缓存中没有找到,计算机会向本地域名DNS服务器发送一个查询请求。这个服务器一般就是网络服务提供商的服务器,也可能是一个公共的DNS服务器;
注意是计算机发起请求,DNS查询是由操作系统中的网络堆栈发起的,而不是由浏览器直接发起的。这样设计的好处是,所有的网络应用程序,不仅仅是浏览器,都可以利用操作系统的DNS查询功能,无需自己实现DNS查询的逻辑。
- 迭代查询:如果DNS服务器没有这个域名的记录,它会执行一个迭代查询,询问其他的DNS服务器。这个过程可能会涉及多个服务器,从根服务器开始,然后是顶级域服务器(如.com),然后是二级域服务器(如.google.com)。
域名的结构通常从右到左,从最高级别到最低级别。例如,在 www.baidu.com 这个域名中,.com 是顶级域,baidu 是二级域,www 是三级域,也就是子域。
- 返回结果:最后找到了IP地址后,DNS服务器会将结果返回给你的计算机,你的计算机则会将这个结果缓存起来,以备将来使用。
(三)建立 TCP 连接
网络进程
拿到目标服务器IP地址后,为了保证数据的可靠传输和顺序传输,以及进行流量和拥塞控制,发送请求之前需要先建立TCP连接,而不能直接向服务器请求数据。
TCP 连接的基本原理:
建立 TCP 连接的过程是通过客户端和服务端相互发送TCP报文
确认连接的。TCP报文
中有个6个控制维,相当于控制 TCP 状态的标识字段。如下6个控制位:
- URG:Urgent紧急的(用于优先发送紧急数据)
- ACK:acknowledgement确认;
- SYN:synchronize同步;
- FIN:finish结束;
- PSH:push推送
- RST:reset复位重置(tcp连接发生错误,用于释放连接)
TCP 连接的流程:
建立 TCP 链接的过程就是我们常说的三次握手,简单描述下这个过程:
- 第一次:浏览器向服务器发送一个
SYN
包,表示希望建立连接; - 第二次:服务器收到 SYN 包后,返回一个
SYN-ACK
包,表示同意建立连接; - 第三次:浏览器收到 SYN-ACK 包后,再发送一个 ACK包,确认已经收到了服务器的
SYN-ACK
包;·
ACK:acknowledgement确认;SYN:synchronize同步;
至于为什么是3次,不是2次,4次?
可以看本系列文章之网络协议篇。
建立 TCP 连接之后,客户端就可以向服务端发送请求获取数据了。
(四)TLS 握手
如果网页地址的请求协议是HTTPS,在TCP连接之后还需要建立TLS链接,以保证传输的数据不被窃听、篡改或伪造。
TLS握手的过程就是在交换密钥,验证身份并生成加密连接所需的安全参数。
(五)Http 请求获取网页数据
我觉得上学时很多电脑老师都提到过:“访问网页其实就是查看别人电脑上的文件”
所以建立连接之后,浏览器就可以根据输入的 URL
就可以定位到资源,发送 HTTP 请求获取网页内容了。
浏览器网络进程会完成请求响应的过程拿到网页数据,大部分浏览器都是在网络进程中完成请求的,像渲染进程中的 http异步请求线程
只是负责发起请求和接收响应,实际请求还是在网络进程中完成的。
http 请求过程本质上其实就是基于 HTTP 协议的客户端和服务器之间交换请求和响应报文。报文是信息交流的基本单位。一个HTTP请求报文通常包括请求行(包含HTTP方法、URI和HTTP版本)、请求头部(包含各种头部字段,如User-Agent、Accept等)以及请求主体(可选,比如POST请求中的表单数据)。
为了提高协议的传输效率和性能,HTTP协议多次迭代优化,其中有几次重要的优化版本,例如http1.1、http2。
(六)判断 URL 类型
浏览器请求用户输入的 URL 拿到数据后,会先根据请求头的Content-Type字段判断数据类型是下载类型还是正常的HTML页面,如果是下载类型请求会被提交到浏览器的下载管理器,导航流程结束,但如果是HTML类型浏览器的渲染进程就会准备进行页面渲染。
网络进程
获取到HTML数据后,一般会先传递给主进程
(也就是浏览器进程),由主进程和渲染进程进行交互。
(七)提交文档
确定用户输入是访问网页后,主进程就会通知渲染进程可以准备开始渲染了。
渲染进程准备好后,浏览器进程会向它发送 “提交文档” 的消息。
渲染进程接收到消息后会与网络进程建立传输数据的管道,等文档数据传输完成后,渲染进程会返回 “确认提交” 的消息给浏览器进程。
浏览器进程在收到消息后,会更新浏览器界面状态,包括安全状态,地址栏的URL,前进后退的历史状态,更新web页面。
(八)浏览器渲染网页
最后渲染进程
拿到HTML数据,开始网页的渲染过程。
浏览器的渲染引擎和js引擎都是在渲染进程中工作的,渲染进程中的GUI渲染线程
解析自上而下解析 HTML 代码。当然了,有一些设置了预解析的资源,可以在HTML解析过程中提前发现并加载它们。
渲染引擎在GUI渲染线程
中工作,JS 引擎在JS引擎线程
中工作。这两个线程是互斥的,因为GUI 渲染线程和 JavaScript 引擎线程都需要访问和操作 DOM,所以为了避免线程安全
问题,它们俩其中一个线程工作时,另一个线程就会被挂起。这也就是为什么JS文件会阻塞页面加载,一般最好放在页面底部引入的原因。
那CSS文件会阻塞渲染吗?答案是也会,因为虽然Dom树和css规则是并行解析的,但是css加载会阻塞渲染树的合成,所以同样也会阻塞渲染。
好了,那GUI渲染线程
是如何渲染网页的呢?(详细还是去看往期文章)
-
HTML解析: 浏览器内核的 HTML Parse 将 HTML 转化为DOM树(DOM Tree);
-
css解析: 同时CSS Parse 将css转化为 Cssom Tree(Style Rules),可以用 document.styleSheets 查看除了内联和默认样式之外的所有内部和外部样式表;
-
生成渲染树: 然后 DOM树(DOM Tree)和CSS规则(Style Rules)通过附加(Attachment)生成渲染树(Render Tree),设置了display: none的元素会在渲染树中去除。
-
布局: 然后计算DOM树中可见元素的几何位置(例如节点的宽高、相对包含块的位置),生成布局树;
-
分层: 渲染主线程会对整个布局树中进行分层。一般滚动条、a标签、transform、will-change等样式都会影响分层效果,另外,像opacity、filter等属性虽说也能影响分层,但直接设置一般不会有效,需要设置动画animation才会独立分层。所以如果你想查看哪些元素单独分层了,可以在控制台的图层面板查看(设置这些属性的元素分层渲染可以触发GPU硬件“加速”):
-
绘制: 渲染引擎将将页面内容绘制到帧缓冲区(Framebuffer)中,帧缓冲区是一个内存区域,用于存储图像数据,这些图像数据最终会被 GPU 渲染到屏幕上。至此,渲染主线程的工作已经完成了,接下来由其他线程处理显示图像;
-
分块: 合成器线程首先对每个图层进行分块,将其划分为更多的小区域;
-
光栅化: 上面我们已经获得了文档结构、元素的样式、元素的几何关系、绘画顺序,接下来把这些信息转化为显示器中的像素才能显示,这个转化的过程,就叫做光栅化。此过程是合成器的光栅工作线程把每个块变成位图,位图可以理解成内存里的一个二维数组,这个二维数组记录了每个像素点信息;
-
合成: 合成器线程再将以上的像素信息生成合成帧,合成帧就是页面一个帧的内容的绘制四边形集合(绘制四边形是包含图块在内存的位置以及图层合成后图块在页面的位置之类的信息);
-
显示: 最后合成器线程会把出现在视口区的合成帧提交给浏览器进程,即渲染帧,最后浏览器进程将渲染帧发送给GPU从而展示在屏幕上,如果有页面滚动,则合成器线程会构建另外一个合成帧发送给GPU来更新页面;
(九)断开 TCP 连接
一般当网页数据传输完成后,就会断开 TCP 连接了,TCP 断开采用的是四次挥手策略。当然了,tcp连接的双方都可以主动断开tcp连接,例如连接的一方网络断开、网络阻塞、主动释放资源等情况。
四次挥手
的大致步骤如下:
- 第一次:主动关闭方发送 FIN 报文,表示没有数据要发送了,请求关闭连接;
- 第二次:被动关闭方收到 FIN,发送 ACK,表示知道了对方要关闭连接,但是暂时还不关闭,因为被动方可能还有数据要发送;
- 第三次:被动关闭方发送完数据后,发送 FIN,表示可以关闭连接了;
- 第四次:主动关闭方收到 FIN,发送 ACK,表示好的,马上断开连接,等待一段时间(2MSL)后关闭连接,被动关闭方收到报文后关闭;
FIN:fninish结束;ACK:acknowledgement确认;
为什么要等待2msl呢? 就是为了保证主动关闭方最后发送的ACK报文段能够到达被动关闭方,以防ACK丢失,导致被动关闭方无法正常关闭连接。
为什么挥手需要四次?而不是三次?
简单来说就是前 2 次挥手用于关闭一个方向的数据通道,后两次挥手用于关闭另外一个方向的数据通道。
注意: 在特定情况下,四次挥手是可以变成三次挥手的,例如短连接或频繁创建销毁连接的场景:
- 主动关闭方发送 FIN + ACK。
- 被动关闭方收到 FIN + ACK,发送 ACK。
- 主动关闭方收到 ACK,连接关闭。
🎁 最后
学如逆水行舟,不进则退~👊👊👊
先看后赞,养成习惯👍
收藏吃灰,不如学会🍗
点个关注,不要迷路🪤