从输入 URL 到页面展示,这中间发生了什么?
# 从输入 URL 到页面展示,这中间发生了什么?
在浏览器里,从输入 URL 到页面展示,这中间发生了什么,这是一道很常见的面试题,这里面涉及的知识面很广,以 chrome 举例,会涉及到进程、线程、网络、TCP/IP、HTTP协议 等等
我们现在来分析一下具体发生了什么
# chrome 中的进程
首先 什么是进程
进程
一个进程就是一个程序的运行实例。详细解释就是,启动一个程序的时候,操作系统会为该程序创建一块内存,用来存放代码、运行中的数据和一个执行任务的主线程,我们把这样的一个运行环境叫进程。 一个进程可以有多个线程
线程是依附于进程的,而进程中使用多线程并行处理能提升运算效率
进程和线程的关系如下
进程中的任意一线程出错,会导致整个进程的崩溃
线程之间共享进程中的数据
当一个进程关闭之后,操作系统会回收进程所占用的内存
进程之间相互隔离
接着来了解一下 浏览器的进程 ,在 2007 年之前,市面上的浏览器都是单进程的,在 2008 年,chrome 发布了多进程架构
# 单进程浏览器
单进程浏览器是指浏览器的所有功能模块都是运行在同一个进程里,这些模块包括了 网络、插件、JavaScript运行环境、渲染引擎和页面等。这么多模块运行在一个进程中,如果这个进程中有任何一个线程出现了问题,整个浏览器都会崩溃,包括一些插件等都运行在一个单一进程中,如果存在恶意插件,也会造成不安全,有可能这个恶意插件会释放病毒等。所以单进程浏览器,不稳定、不流畅、不安全
# 多进程浏览器
在 chrome 早期推出多进程架构时,是下面这种样子
插件是单独的进程,页面的渲染也是单独的进程,多个插件会创建多个进程,进程之间相互隔离,也就是说当一个页面或者一个插件崩溃,不会影响整个浏览器。JavaScript 运行在渲染进程中,就算阻塞也只是针对当前页面,并不会影响其他页面。插件是单独的进程,这样也可以做安全沙箱,解决了安全性的问题
这是目前的 chrome 架构图,包含了 1个浏览器主进程、1个 GPU 进程、1个网络进程、多个渲染进程和多个插件进程
浏览器主进程: 主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
渲染进程: 核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程
GPU 进程: 网页、Chrome 的 UI 界面都选择采用 GPU 来绘制
网络进程: 主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
插件进程: 主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。
多进程提升了浏览器的稳定性、流畅性和安全性,但是同样带来了问题,更高的资源占用、更复杂的体系架构
也正是因为多进程的存在,在打开一个 chrome页面时,至少会有4个进程,1个浏览器主进程、1个网络进程、1个GPU进程以及1个渲染进程
# 数据传输 TCP
TCP
TCP (传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,具有下面两个特点
- 对于数据包丢失的情况,TCP 提供重传机制
- TCP 引入了数据包排序机制,用来保证把乱序的数据包合成一个完整的文件
在文件传输时,是这样的过程
上层将数据包交给传输层
传输层会在数据包前面附加上 TCP 头,组成新的 TCP 数据包,再将新的包交给网络层
网络层再将 IP 头附加到数据包上,组成新的 IP 数据包,并交给底层
数据包传到另一主机的网络层,在这里主机拆开 IP 头信息,并将拆开的数据部分交给传输层
在传输层数据包中 TCP 头会被拆开,并根据 TCP 中的所提供的端口号,把数据交给上层的的应用程序
完成的数据传输就完成了
完成的 TCP 连接过程
首先 ,建立连接阶段。这个阶段是通过三次握手来建立客户端和服务器之间的连接。TCP 提供面向连接的通信传输。面向连接是指在数据通信开始之前先做好两端之间的准备工作。所谓三次握手,是指在建立一个 TCP 连接时,客户端和服务器总共要发送三个数据包以确认连接的建立。
其次,传输数据阶段。在该阶段,接收端需要对每个数据包进行确认操作,也就是接收端在接收到数据包之后,需要发送确认数据包给发送端。所以当发送端发送了一个数据包之后,在规定时间内没有接收到接收端反馈的确认消息,则判断为数据包丢失,并触发发送端的重发机制。同样,一个大的文件在传输过程中会被拆分成很多小的数据包,这些数据包到达接收端后,接收端会按照 TCP 头中的序号为其排序,从而保证组成完整的数据。
最后,断开连接阶段。数据传输完毕之后,就要终止连接了,涉及到最后一个阶段 “四次挥手” 来保证双方都能断开连接。
TCP 为了保证数据传输的可靠性,牺牲了数据包的传输速度,因为 “三次握手” 和 “数据包校验机制” 等把传输过程中的数据包的数量提高了一倍
# HTTP 协议
HTTP
HTTP 协议,正是建立在 TCP 连接基础之上的。HTTP 是一种允许浏览器向服务器获取资源的协议,是 Web 的基础,通常由浏览器发起请求,用来获取不同类型的文件,例如 HTML 文件、CSS 文件、JavaScript 文件、图片、视频等。此外,HTTP 也是浏览器使用最广的协议
# 一般来说,浏览器发起 HTTP 请求,会经过以下流程
构建请求,首先浏览器构建请求行信息,构建好后,浏览器准备发起网络请求
GET /index.html HTTP1.1
1查找缓存,在真正发起网络请求之前,浏览器会先在浏览器缓存中查询是否有要请求的文件。当浏览器发现请求的资源已经在浏览器缓存中存有副本,它会拦截请求,返回该资源的副本,并直接结束请求,而不会再去源服务器重新下载。
准备 IP 地址和端口
- 首先需要和服务器建立 TCP 连接,这个过程中需要 IP 地址和 端口
- 通过 DNS (域名系统),也就是 域名映射为 IP ,来获取到真正的 IP
- 端口号如果未指定,http 默认为 80, https 默认为 443
等待 TCP 队列 Chrome 有个机制,同一个域名同时最多只能建立 6 个 TCP 连接,如果在同一个域名下同时有 10 个请求发生,那么其中 4 个请求会进入排队等待状态,直至进行中的请求完成。
如果当前请求数量小于6,会直接建立 TCP 连接
建立 TCP 连接
发送 HTTP 请求,在这一步里面 首先浏览器会向服务器发送 请求行,请求行 包括 请求方法,请求 URI 和 HTTP 版本协议,如 POST 方法,会将数据通过 请求体 来发送,除此之外还有 请求头,它包含了一些浏览器的信息,如操作系统,内核等,已经当前请求的域名信息,Cookie 等
# 而浏览器处理 HTTP 请求的过程如下
返回请求
- 首先服务器会返回 响应行,包括协议版本和状态码
- 接着返回 响应头,包含了一些服务器的信息,返回的数据类型,Cookie 等
- 然后返回 响应体数据
断开连接 通常情况下,一旦服务器向客户端返回了请求数据,它就要关闭 TCP 连接。不过如果浏览器或者服务器在其头信息中加入了
Connection:Keep-Alive
1那么 TCP 连接在发送后将仍然保持打开状态,这样浏览器就可以继续通过同一个 TCP 连接发送请求。保持 TCP 连接可以省去下次请求时需要建立连接的时间,提升资源加载速度。
重定向
- 如果响应行返回的状态码是 301,那么就需要重定向
- 重定向的域名在响应头的 Location 字段中
- 浏览器拿到 Location 字段中的地址,并使用该地址重新导航
# 浏览器的数据缓存
DNS缓存,主要就是在浏览器本地把对应的 IP 和域名关联起来
页面资源缓存
当服务器返回 HTTP 响应头给 浏览器时,浏览器通过 响应头中的 Cache-Control 字段来设置是否缓存该资源,通过 Cache-Control 中的 Max-age 来设置缓存时长,单位为 秒
Cache-Control:Max-age=2000
1如果缓存过期了,浏览器则会继续发起网络请求,并且在 HTTP 请求头中带上
If-None-Match:"xxxxx"
1服务器会根据 If-None-Math 的值来判断请求的资源是否有更新
- 如果没有更新,返回 304 状态码,表示当前缓存可以继续使用,不需要更新
- 如果资源有更新,服务器就返回最新的资源给浏览器
Cache-Control
Cache-Control、Expires 用于设置缓存过期时间。 Cache-Control 响应头中常用字段的具体含义:
max-age:用来设置资源(representations)可以被缓存多长时间,单位为秒;
s-maxage:和 max-age 是一样的,不过它只针对代理服务器缓存而言;
public:指示响应可被任何缓存区缓存;
private:只能针对个人用户,而不能被代理服务器缓存;
no-cache:强制客户端直接向服务器发送请求,也就是说每次请求都必须向服务器发送。服务器接收到请求,然后判断资源是否变更,是则返回新内容,否则返回 304,未变更。这个很容易让人产生误解,使人误以为是响应不被缓存。实际上 Cache-Control: no-cache 是会被缓存的,只不过每次在向客户端(浏览器)提供响应数据时,缓存都要向服务器评估缓存响应的有效性。
no-store:禁止一切缓存(这个才是响应不被缓存的意思)。 Cache-Control 的优先级高于 Expires。 Etag 是属于 HTTP 1.1 属性,它是由服务器生成返回给前端。ETag 实体标签:一般为资源实体的哈希值。 当你第一次发起 HTTP 请求时,服务器会返回一个 Etag,并在你第二次发起同一个请求时,客户端会同时发送一个 If-None-Match,而它的值就是 Etag 的值(此处由发起请求的客户端来设置)。然后,服务器会比对这个客服端发送过来的 Etag 是否与服务器的相同,如果相同,就将 If-None-Match 的值设为 false,返回状态为 304,客户端继续使用本地缓存,服务器不返回数据。如果不相同,就将 If-None-Match 的值设为 true,返回状态为 200,客户端重新解析服务器返回的数据。
Last-Modified 表示响应资源在服务器最后修改时间。
- Last-Modified 标注的最后修改只能精确到秒级,如果某些文件在 1 秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间;
- 如果某些文件会被定期生成,当有时内容并没有任何变化,但 Last-Modified 却改变了,导致文件没法使用缓存;
- 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形。 Etag 的优先级高于 Last-Modified。
HTTP请求从发起到结束
构建请求
查找缓存
准备 IP 和端口
等待 TCP 队列
建立 TCP 连接
发起 HTTP 请求
服务器处理请求
服务器返回请求和断开连接
# 从输入 URL 到页面展示
用户输入
当用户在地址栏输入内容时,浏览器会判断输入的内容是 搜索内容还是请求的 URL
- 如果是搜索内容,地址栏会使用浏览器默认的搜索引擎,来合成新的带搜索关键字的 URL
- 如果输入的内容符合 URL 规则,则会根据规则,加上 http 或 https 等协议,合成完成的 URL
在输入完关键字并键入回车之后,意外着当前页面即将要被替换,在进行这个流程之前,会触发
beforeunload
事件,beforeunload
允许页面在退出之前进行一些数据清理的操作,也可以用来取消导航,让浏览器不再执行后续的工作如果当前页面没有监听
beforeunload
或者同意继续后,那么浏览器标签页上的favicon
会改变,变为加载中状态,此时页面还是当前页面的内容,在提交文档阶段,页面内容才会被替换URL 请求过程
接下来进入了页面资源请求过程,浏览器会通过进程间的通信(IPC)把 URL 请求发送至 网络进程,网络进程收到 URL 请求后,首先会判断本地是否缓存了该资源,如果有缓存资源,则直接返回资源给浏览器,如果本地没有,会进入网络请求流程。
首先进行 DNS 解析,获取请求域名的 IP 地址,如果请求协议是 HTTPS 还要建立 TLS 连接
然后建立 TCP 连接,连接建立后,浏览器会构建请求行、请求头、请求体等信息,然后向服务器发送请求信息
服务器收到请求信息后,会根据请求信息生成 响应行、响应头、响应体等信息,发送给网络进程,网络进程收到响应头和响应行之后,开始解析对应的内容
重定向
如果返回的响应行的状态码为 301 或者 302 ,那么此时就需要进行重定向,会获取 Location 字段的地址,进行重定向(在导航过程中,如果服务器响应行的状态码包含了 301、302 一类的跳转信息,浏览器会跳转到新的地址继续导航;如果响应行是 200,那么表示浏览器可以继续处理该请求。)
处理数据类型
浏览器会根据 Content-Type 来获取响应体的内容类型,Content-Type 一般为 MIME 类型 (opens new window) ,在请求网页时,一般 Content-Type 为 text/html 类型,表示是 HTML 格式
如果 Content-Type 为 application/octet-stream 则表示为 下载类型的文件,那么该请求会被提交给浏览器的下载管理器,同时该 URL 请求的导航流程就此结束
准备渲染进程
默认情况下,chrome 会为每一个页面分配一个渲染进程,也就是每打开一个新页面就会创建一个新的渲染进程,但是如果从一个页面打开了另一个新页面,而新页面和当前页面属于同一站点的话,那么新页面会复用父页面的渲染进程(process-per-site-instance)
同一站点
具体地讲,我们将 “同一站点” 定义为根域名(例如,google.com)加上协议(例如,https:// 或者 http://),还包含了该根域名下的所有子域名和不同的端口
提交文档
首先当浏览器进程接收到网络进程的响应头数据之后,便向渲染进程发起 “提交文档” 的消息
渲染进程接收到 “提交文档” 的消息后,会和网络进程建立传输数据的 “管道”
等文档数据传输完成之后,渲染进程会返回 “确认提交” 的消息给浏览器进程
浏览器进程在收到 “确认提交” 的消息后,会更新浏览器界面状态,包括了安全状态、地址栏的 URL、前进后退的历史状态,并更新 Web 页面
渲染阶段
渲染进程将 HTML 内容转为 DOM树结构
渲染引擎将 CSS 样式表转为 styleSheets,计算出 DOM 节点的样式
创建布局树,并计算元素的布局信息,剔除不可见元素(head、display:none 等)
根据图层生成绘制列表,碧昂精气提交到合成线程
合成线程将图层分为图块,并在光栅化进程池中奖图块转为位图
合成线程发送绘制图块命令 DrawQuad 给浏览器进程
浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上
从输入 URL 到页面展示,这中间发生了什么?
用户输入 URL ,浏览器会根据用户输入的信息判断是搜索还是网址,如果是搜索内容,就将搜索内容和默认搜索引擎合成新的 URL;如果用户输入的内容符合 URL 规则,浏览器就会根据 URL 协议,在这段内容上加上协议合成合法的 URL
用户输入完内容,按下回车键,浏览器导航栏显示 loading 状态,但是页面还是呈现前一个页面,这是因为新页面的响应数据还没有获得
浏览器进程浏览器构建请求行信息,会通过进程间通信(IPC)将 URL 请求发送给网络进程
网络进程获取到 URL,先去 本地缓存 中查找是否有缓存文件,如果有,拦截请求,直接 200 返回;否则,进入网络请求过程
网络进程 进行 DNS 解析 返回域名对应的 IP 和端口号,如果之前 DNS 数据缓存服务缓存过当前域名信息,就会直接返回缓存信息;否则,发起请求获取根据域名解析出来的 IP 和端口号,如果没有端口号,http 默认 80,https 默认 443。如果是 https 请求,还需要建立 TLS 连接。
Chrome 有个机制,同一个域名同时最多只能建立 6 个 TCP 连接,如果在同一个域名下同时有 10 个请求发生,那么其中 4 个请求会进入排队等待状态,直至进行中的请求完成。如果当前请求数量少于 6 个,会直接建立 TCP 连接。
TCP 三次握手建立连接,http 请求加上 TCP 头部 —— 包括源端口号、目的程序端口号和用于校验数据完整性的序号,向下传输
网络层在数据包上加上 IP 头部 —— 包括源 IP 地址和目的 IP 地址,继续向下传输到底层
底层通过物理网络传输给目的服务器主机
目的服务器主机网络层接收到数据包,解析出 IP 头部,识别出数据部分,将解开的数据包向上传输到传输层
目的服务器主机传输层获取到数据包,解析出 TCP 头部,识别端口,将解开的数据包向上传输到应用层
应用层 HTTP 解析请求头和请求体,如果需要重定向,HTTP 直接返回 HTTP 响应数据的状态码 301 或者 302,同时在请求头的 Location 字段中附上重定向地址,浏览器会根据 状态码 和 Location 进行重定向操作;如果不是重定向,首先服务器会根据 请求头中的 If-None-Match 的值来判断请求的资源是否被更新,如果没有更新,就返回 304 状态码,相当于告诉浏览器之前的缓存还可以使用,就不返回新数据了;否则,返回新数据,200 的状态码,并且如果想要浏览器缓存数据的话,就在相应头中加入字段: 响应数据又顺着应用层 —— 传输层 —— 网络层 —— 网络层 —— 传输层 —— 应用层的顺序返回到网络进程
数据传输完成,TCP 四次挥手断开连接。如果,浏览器或者服务器在 HTTP 头部加上如下信息,TCP 就一直保持连接。保持 TCP 连接可以省下下次需要建立连接的时间,提示资源加载速度
Connection:Keep-Alive
1网络进程将获取到的数据包进行解析,根据响应头中的 Content-type 来判断响应数据的类型,如果是字节流类型,就将该请求交给下载管理器,该导航流程结束,不再进行;如果是 text/html 类型,就通知浏览器进程获取到文档准备渲染
浏览器进程获取到通知,根据当前页面 B 是否是从页面 A 打开的并且和页面 A 是否是同一个站点(根域名和协议一样就被认为是同一个站点),如果满足上述条件,就复用之前网页的进程,否则,新创建一个单独的渲染进程
浏览器会发出 “提交文档” 的消息给渲染进程,渲染进程收到消息后,会和网络进程建立传输数据的 “管道”,文档数据传输完成后,渲染进程会返回 “确认提交” 的消息给浏览器进程
浏览器收到 “确认提交” 的消息后,会更新浏览器的页面状态,包括了安全状态、地址栏的 URL、前进后退的历史状态,并更新 web 页面,此时的 web 页面是空白页
渲染引擎首先将 HTML 和 CSS 转换为 DOM 树和 styleSheets,计算出对应的 DOM 节点样式,然后创建布局树,剔除不显示的元素,对布局树分层后交给合成线程分成图块,在光栅化进程池中将图块转为位图,最后浏览器根据 DrawQuad 生成页面,并显示到显示器上