摘要:极客时间《浏览器工作原理与实践》宏观视角下的浏览器学习笔记,Chrome 架构、TCP 协议、HTTP 请求流程、导航流程、渲染流程
Chrome 架构
进程与线程
一个程序启动时,操作系统会为该程序创建一块内存,用来存放代码、运行中的数据和一个执行任务的主线程,这样一个运行环境叫进程。线程是不能单独存在的,它依附于进程,并且由进程来启动和管理,进程中使用多线程并行处理能提升运算效率。
进程和线程之间的关系:
1、线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位
2、进程中的任意线程出错,都会导致整个进程崩溃
3、线程之间共享进程中的数据
4、进程之间的内容相互隔离
5、进程关闭后,它所占用的内存会被操作系统回收
多进程架构
浏览器最早是单进程的架构,所有功能模块都是运行在同一个进程里,这样会出现很多问题。到现在最新的 Chrome 浏览器:1 个浏览器进程、1 个 GPU 进程、1 个网络进程、多个渲染进程和多个插件进程。
各个进程的功能:
1、浏览器进程。主要负责界面显示、用户交互、子进程管理、数据存储等功能。
2、渲染进程。渲染进程都是运行在沙箱模式下,其核心任务是将 HTML、CSS、JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中。默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。
3、GPU 进程。主要负责绘制网页和 Chrome 的 UI 界面。
4、网络进程。主要负责页面的网络资源加载。
5、插件进程。主要负责插件的运行。
多进程模型提升了浏览器的稳定性、流畅性和安全性,但也带来了新的问题:更高的资源占用和更复杂的体系架构
TCP 协议
互联网,实际上是一套理念和协议组成的体系架构。网络传输的参考模型有两种,一种是 OSI(Open System InetConnection)参考模型,其有七层,另一种是 TCP/IP 参考模型,被减压成四层。OSI 模型在当下已经基本被 TCP/IP 模型所取代。
IP:把数据包送达目的主机
数据包要在互联网上进行传输,就要符合网际协议(Internet Protocol,简称 IP)标准。计算机的地址称为 IP 地址,访问任何网站实际上只是计算机向另外一台计算机请求信息。IP 属于网络层协议。
数据包从主机 A 到主机 B 的旅程:
1)主机 A 的上层将数据包交给网络层;
2)网络层将 IP 首部添加到数据包上,组成新的 IP 数据包,然后交给底层;
3)底层通过物理网络将数据包传输给主机 B;
4)数据包被主机 B 的底层传输到网络层;
5)网络层将数据包的 IP 首部取出,然后将剩下的数据部分交给上层;
6)主机 B 的上层即可接收到主机 A 发过来的数据
UDP:把数据包送达应用程序
用户数据包协议(User Datagram Protocol)简称 UDP,UDP 中一个最重要的信息是端口号。IP 通过 IP 地址信息把数据包发送给指定的电脑,而 UDP 通过端口号把数据包分发给正确的程序。UDP 是传输层的协议,它是面向无连接的,不需要在正式传递数据之前先连接起双方,因而不能保证数据可靠性,但是 UDP 的头部开销小,传输速度非常快。UDP 不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,即 UDP 提供了单播,多播,广播的功能。
传输过程:数据先由上层传到传输层,传输层的 UDP 协议将含有目的端口号和源端口号等信息的 UDP 首部与数据包合并成新的 UDP 数据包,然后将 UDP 数据包交给网络层。同样,主机得到网络层的数据包后要取出其中的 UDP 首部,并按照 UDP 首部中提供的端口号将剩下的数据部分交给上层对应的应用程序
TCP:把数据完整地送达应用程序
传输控制协议(Transmission Control Protocol),简称 TCP,它是一种面向连接的、可靠的、基于字节流的传输层通信协议。它与 UDP 同属于传输层协议,但 TCP 是一个全双工的协议。它对比 UDP 有两个显著特点:1、TCP 提供重传机制;2、引入数据包排序机制,保证把乱序的数据包组合成一个完整的文件。则在拼装数据时,TCP 首部要比 UDP 首部多了用于排序的序列号信息。
完整的 TCP 连接的生命周期包括建立连接、传输数据和断开连接三个阶段
1、建立连接,这个阶段通过“三次握手”来建立客户端和服务器之间的连接。TCP 提供面向连接的通信传输,面向连接是指在数据通信开始之前先做好两端之间的准备工作。
2、传输数据,该阶段主要是接收端需要对每个数据包进行确认操作,接收端在接受到数据包之后要给发送端发送确认数据包。如果当发送端发送一个数据包后,没有在规定时间内接收到接收端反馈的确认消息,就判断为数据对视,并触发发送端的重发机制。一个大文件会被拆分成很多小数据包,接收端在接收到所有数据包后,会按照 TCP 首部中的序号进行排序,从而保证组成完整的数据。
3、断开连接,该阶段“四次挥手”来保证双方都能断开连接。
TCP 牺牲了数据包的传输速度来保证数据传输的可靠性,“三次握手”和“数据包校验机制”等把传输过程中的数据包的数量翻了一倍。
三次握手
如果想确定双通道通畅,至少要使用三个包的发送接收,也就是三次握手才能确认双方的收和发功能是否正常
第一次握手,客户端 A 发送消息给服务端 B。B 如果收到请求,B 知道 A 发送功能和自身的接受功能正常,但不知道 A 的接收功能和自身的发送功能是否正常,而 A 是双方的收发功能都不清楚是不是正常。
第二次握手,服务端 B 返回确认消息和请求信息给客户端 A 。A 如果接收到消息,A 知道自身和 B 的的收发功能都正常,但此时的 B 仍并不知道 A 的接收功能和自身的发送功能是否正常
第三次握手,客户端 A 返回确认消息给服务端 B。B 如果接收到确认消息,B 就知道双方的收发功能都正常。
四次挥手
TCP 连接是双向的,因此在四次挥手中,前两次挥手用于断开一个方向(A 到 B)的连接,后两次挥手用于断开另一方向(B 到 A)的连接。
第一次挥手,客户端 A 发送连接释放请求给服务端 B。A 认为数据已经发送完成,可以断开连接
第二次挥手,服务端 B 发送连接释放应答给客户端 A。完成之后,A 到 B 方向的连接释放,A 不会再发送数据,B 也不会再接收数据,但仍可以发送和接收报文。
第三次挥手,服务端 B 发送完所有数据后,向客户端 A 发送连接释放请求。
第四次挥手,客户端 A 发送连接释放应答给服务端 B。B 接收到消息就会关闭接收消息的通道。而 A 发送消息后等待 2MSL 时间后,如果没有接收到 B 的重发请求才会关闭。如果 B 没有收到 A 反馈的消息,就会超时重传第三次挥手的报文,因为 B 不知道 A 有没有收到连接释放请求。A 在 2MSL 时间内收到了第三次挥手的报文就会再次发送第四次挥手的报文给 B,确保 B 能收到 A 的应答报文而正确的关闭连接,否则 B 会因为没有接收到应答消息而一直开着。
MSL:报文最大生存时间。它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。
HTTP 请求流程
HTTP 协议是建立在 TCP 连接基础之上的,是一种允许浏览器向服务器获取资源的协议。
完整流程
1、构建请求
浏览器构建请求行信息,之后准备发起网络请求
1 | // 请求方法 请求 URI HTTP 版本 |
2、查找缓存
再发起真正的网络请求之前,浏览器会现在浏览器缓存中查询是否有要请求的文件。浏览器缓存是一种在本地保存资源副本,以供下次请求时直接使用的技术。使用缓存可以实现快速加载资源,又可以缓解服务器端压力。如果缓存查询失败,则会发起真正的网络请求。
3、准备 IP 和端口
HTTP 的内容是通过 TCP 的传输数据阶段来实现的:浏览器使用 HTTP 协议作为应用层协议将请求的文本信息封装,再用 TCP/IP 作为传输层协议将封装后的数据发到网络上。
一般 IP 地址难以记忆,可以利用 URL 地址通过域名系统 DNS 获得域名所对应的 IP。同时浏览器还提供了 DNS 数据缓存服务,如果域名之前解析过,那么浏览器会缓存解析的结果以供下次查询时直接使用。除了 IP,还有端口,如果没有指明端口号,一般 HTTP 协议默认使用 80 端口
4、等待 TCP 队列
Chrome 有个机制:同一个域名下同时最多只能建立 6 个 TCP 连接,多出的请求会进入排队等待状态。
5、建立 TCP 连接
浏览器通过 TCP 经过三次握手与服务器建立连接
6、发起 HTTP 请求
HTTP 的内容正是在 TCP 的传输数据阶段来实现的。浏览器会向服务器发送请求行(请求方法、请求 URI、HTTP 版本),请求头(浏览器的一些基本信息),如果是 POST、PUT、PATCH 请求,还会有请求体(包含准备的数据)。
7、服务器处理请求
8、服务器返回请求
服务器处理结束,会返回数据给浏览器。返回的数据有响应行(HTTP 版本、状态码、状态描述)、响应头(服务器的一些信息和要保存在客户端的 Cookie 等信息,有 Set-Cookie 字段可以设置浏览器本地 Cookie 数据,可以保持登录状态)和响应体(通常是包含了 HTML 的实际内容)。
9、断开连接
一般服务器返回了请求数据,就会关闭 TCP 连接,但如果浏览器或服务器在其头信息中加入了Connection:Keep-Alive
,那么 TCP 连接将一直保持打开状态,浏览器可以继续通过同一个 TCP 连接发送请求。这样可以省下建立连接的时间,提升资源加载速度。
10、重定向
还有一种情况会发生重定向操作:当响应行返回的状态码是 301 或 302 时,浏览器就会重定向到响应头 Location 字段指定的地址。
导航流程
从输入 URL 到页面展示:
1、用户输入关键字,浏览器会根据用户输入的信息判断是搜索内容还是请求网址,如果是搜索内容,就将搜索内容与默认的搜索引擎合成新的 URL;如果用户输入的内容符合 URL 规则,浏览器就会根据规则,给这段内容加上协议合成合法的 URL
2、当前页面没有监听 beforeunload 事件或者同意了继续后续流程,页面进入加载状态,但页面内容还没有变化
3、浏览器进程通过进程间通信(IPC)把 URL 请求发送至网络进程,网络进程根据 URL 构建请求
4、发起请求前,网络进程会先查找本地缓存,如果有对应的缓存资源,那么直接返回资源给浏览器进程;如果在缓存中没有查找到资源,那么直接进入网络请求流程
5、使用 DNS 解析获取域名的 IP 地址,HTTP 协议默认端口是 80。如果是协议是 HTTPS,那么请求需要建立 TLS 连接,HTTPS 协议默认端口是 443。有 IP 和 端口通过三次握手建立 TCP 连接
6、发送由请求行请求头请求体构成的 HTTP 请求报文,服务器接收到请求信息后,会根据请求信息返回由响应行、响应头和响应体构成的响应数据,数据发送完成后四次挥手断开链接
7、网络进程开始解析响应行和响应头,如果发现返回的状态码是 301 或者 302,那么浏览器重定向到响应头中 Location 字段指定的 URL。如果是 200,浏览器根据 HTTP 头中 Content-Type 的值来决定如何显示响应体的内容。如果是字节流类型的,通常情况下,浏览器会按照下载类型来处理该请求,导航流程就此结束。如果是 HTML,那么浏览器开始准备渲染进程。通常情况下是一个页面使用一个进程,但如果页面都在在同一个浏览上下文组,那么这几个页面会共用一个渲染进程,共享 JS 的执行环境。
8、渲染进程准备好后,浏览器进程向渲染进程发起“提交文档”的消息。渲染进程接收到“提交文档”的消息后,会和网络进程建立“管道”传输数据。文档数据传输完成后,渲染进程会返回“确认提交”的消息给浏览器进程。
9、浏览器进程在收到“确认提交”的消息后,会更新浏览器界面状态,包括了安全状态、地址栏的 URL、前进后退的历史状态,并更新 Web 页面。至此导航流程结束,开始渲染流程。
渲染流程
1、渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构
2、渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式
3、结合 DOM 树和 styleSheets 创建布局树,并计算元素的布局信息
4、对布局树进行分层,并生成分层树
5、为每个图层生成绘制列表,并将其提交到合成线程
6、合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。图块是栅格化执行的最小单位,视口附近的图块会优先生成位图。而且光栅化线程池一般会使用 GPU 来加速(GPU 进程),完成后将结果返回给渲染进程的合成线程,合成线程将位图合成图层
7、合成线程发送绘制图块命令 DrawQuad 给浏览器进程
8、浏览器进程根据 DrawQuad 消息将所有图层合成为一个图层,也就是一张页面图片,并显示到显示器上
重排
修改元素的几何位置属性,浏览器会触发重新布局,需要从第 2 步开始渲染流水线,所以开销也是最大的
重绘
修改元素不引起几何位置变换的属性,不涉及布局、构建层树,直接生成绘制指令列表。比如背景色,会跳过 3、4 步,要比重排高效
合成
用 CSS 的 transform 来实现动画效果,修改的是既不要布局也不要绘制的属性,不涉及渲染线程,只由合成线程执行后续的合成操作,会跳过 3、4、5 步,合成是最高效的
计算渲染进程个数
Chrome 浏览器会将浏览上下文组中属于同一站点的页面分配到同一个渲染进程中。页面通过<a>
标签或者window.open
方法打开新页面,页面之间会建立连接,这些标签页被称为浏览上下文组 ( browsing context group)。而协议和根域名相同的页面会被归为同一站点。那么计算多个页面渲染进程的个数,就需要判断页面是否满足“同一站点”和有连接关系这两点要求。是否是同一站点,直接查看协议和根域名即可,但是要注意:同一站点依然会受到同源策略的限制。连接关系则有两种方法建立:
1、通过<a>
标签来和新标签建立连接,比如<a href="https://blog.niuxiaokui.top/" target="_blank">牛小葵</a>
2、通过 JavaScript 中的window.open
方法来和新页面建立连接,比如new_window = window.open("https://blog.niuxiaokui.top")
新页面 window.opener 可以查看源页面的 window 信息,但如果在<a>
标签使用了 rel=”noopener”属性,新页面就不能获取到源页面的 window 对象,此时 window.opener 为 null。如果<a>
标签使用了 rel=”noreferrer” 属性,同样可以限制获取原页面的 window 信息,同时还无法获取 document.referrer 信息。考虑到兼容性,这两个属性一般会同时设置 rel=”noopener noreferrer”。