浏览器原理系列(一):关于架构、TCP/HTTP、缓存以及渲染流程的原理


我们都知道前端工程师每天都需要和浏览器打交道,浏览器对于我们的重要程度不言而喻。但是不知道各位在日常开发(摸鱼 bushi)中有没有和笔者有一些共同的疑惑,比如为什么我只是打开了一个标签页却开了好几个进程(可通过浏览器的任务管理器查看)、为什么大家都倾向于使用 chrome而不是其他浏览器、为什么同一个资源第二次、第三次访问往往会比第一次快很多、为什么资源可以完整安全地送达到浏览器、从输入一个 URL到看到渲染好地页面这中间到底发生了什么?如果你对于上面的问题都能回答上来,那你对于浏览器的知识储备应该比我强不少哈哈哈,如果你对于这些问题也有疑问,那我相信这篇文章或许可以解答你的疑惑!!!接下来我们就按照问题一个一个来解决大家的疑惑并学习背后的知识原理。(ps:后看,这是我创作的动力!!!

你将学到:

  • 浏览器的工作原理
  • chrome的进程、线程相关知识和架构
  • 浏览器缓存
  • TCP/HTTP 相关知识
  • 浏览器的导航过程(从输入 URL到渲染出完整的 页面

话不多说,让我们进入今天的学习吧!

image.png

chrome 的进程、线程以及架构

如果你现在打开浏览器的任务管理器,就会看到下面这样的图片

image-20220928205430775

你可能会产生疑惑,明明我只是打开了掘金,明明 js是单线程的,为什么会开了这么多的进程?为了解开这个疑惑,我们需要先看下 线程进程的基本概念

进程:系统进行资源调度和分配的基本单位,实现了操作系统的并发

线程:进程的子任务,是CPU调度和分派的基本单位,用于保证程序的实时性,实现 进程内部的并发

看完 进程线程的概念,接下来我们来看下 chrome的多进程架构图

image-20220928210943153

可以看到,chrome包含以下进程

  • 插件进程:负责插件的运行(隔离插件进程和其他进程可以防止因为插件进程的奔溃或错误影响浏览器和其他页面,因为你不能保证你的插件是绝对安全稳定的)
  • 渲染进程:将 HTML/CSS/JS转换为交互的页面(排版引擎 Blink和 JavaScript 引擎 V8都是运行在这里),默认情况下 chrome会为每个 Tab开启一个渲染进程(渲染进程通过在沙箱中运行来保证安全)
  • 网络进程:负责网络资源的加载,比如请求和响应,浏览器缓存等功能
  • 浏览器主进程:负责显式、交互、子进程管理以及存储等功能
  • GPU 进程:实现 3DCSS等效果以及 UI界面的绘制

看完上面对各进程的知识讲解,你现在应该可以知道为啥我们打开一个网页会开启这么多进程了吧,关于这些进程一些实际应用场景将在后面的几个小节中穿插详解。

Q:为什么 JS 是单线程的,为什么不适用多线程的并行能力?

A:由于 JS 经常要操作 DOM以及和用户互动,如果是多线程的话,可能会出现 A 线程在修改 DOMA结果 B 线程删除了 DOMA这样的情况,这个时候就会出现线程冲突(HTML5提出了 Web Worker允许 js 创建多个子线程在后台进行并行操作,但是这些子线程并不能操作 DOM 所以这个标准并不能说让 js 变成多线程),而为了使 js 也能像其他语言那样拥有异步和并行的能力,浏览器通过 事件队列、事件循环来解决这个问题,推荐看这篇文章 一文带你读懂浏览器的事件循环机制 - 掘金 (juejin.cn)

Chorme团队正在设计新的 Chrome架构,目的是构建一个 更内聚、松耦合、易于维护和扩展的系统,感兴趣的朋友可以去网上查找相关资料

浏览器缓存

你在日常生活中是否有留意过这样一个现象,当我们第一次打开一个网站时,往往需要等待数秒的加载,而之后我们再打开这个网站时,就会比之前快很多,这其中其实是 浏览器缓存的功劳。

浏览器是指浏览器根据 url第一次访问资源之后,根据缓存策略将 html、css、js、图片等资源保存在浏览器本地,之后你再访问这些资源时,便可以根据缓存策略决定是使用本地的缓存还是重新请求。

浏览器缓存是前端性能优化必不可少的重要一环!!!

那如何查看自己的请求是使用缓存还是重新访问呢?我们直接打开掘金的首页并打开控制台

image.png

(红色框里的就是用了memory cachedisk cache 的 像这些就是使用了缓存的)

缓存分类:

1.Memory Cache:在浏览器内存中的缓存,访问优先级最高,访问速度也是最快的(和渲染进程共存,进程关闭就清除缓存,所以有效期比较短)

2.service worker Cache:独立的 js 线程,可实现离线缓存、消息推送等功能

3.Disk Cache:磁盘中的缓存,容量较大,但是相对的访问速度较慢

4.push CacheHTTP2的新特新,优先级最低(前面三种缓存无法命中后才会命中这个)

缓存策略:

浏览器的缓存策略可以分为强缓存协商缓存两种

  • 强缓存: 不用向服务器请求。可以通过设置 HTTP Headerexpriescache-control实现
    • expries: 服务器第一次响应时,会将过期的时间戳写在 expries的请求头中返回,当我们再次请求时,如果本地时间小于 expries的时间戳,就直接去缓存中读取资源
      • 缺点:如果更改了本地时间,可能会导致缓存失效
    • cache-control: 通过 max-age控制本地资源的有效期,s-maxage控制代理服务器(CDN缓存)的有效期(s-maxage仅在代理服务器中生效,max-age仅在客户端生效),设置成no-store 则表示拒绝一切形式的缓存
      • 缺点:如果更改了本地时间,同样会导致缓存失效,但是解决了 expires会因为客户端和服务器直接时差不一导致的缓存时间不一致的问题
1
`cache-control`优先级高于 `expries
  • 协商缓存:浏览器需要先向服务器发起请求,在判断是否从本地获取缓存资源。可以通过设置 HTTP HeaderLast-ModifiedETag实现
    • Last-Modified:首次响应时会返回 Last-Modified字段,之后客户端每次请求都会带上 If-Modified-Since的字段(上一次response中的 Last-Modified的值),服务器根据该时间戳和资源在服务器上最后的修改时间是否一致来判断是否使用缓存(返回 304表示使用本地缓存,返回新的 Last-Modified表示不使用缓存)
      • 缺点:如果服务器没有及时感知到文件的变化(比如在毫秒级时间内完成了改动,但是因为 Last-Modified是秒级导致无法感知)
    • ETag:既然上面的 Last-Modified存在着缺点,那 ETag就是他的解决方案(服务器根据文件内容编码生成唯一标识符,可以精确感知文件的变化),首次响应会返回 ETag标识,下一次请求就带上 If-None-Match与服务端进行比较来判断是否使用缓存
      • 缺点:服务端需要额外的开销来生成 ETag标识

Etag 在感知文件变化上比 Last-Modified 更加准确,优先级也更高。当 Etag 和 Last-Modified 同时存在时,以 Etag 为准。

HTTP 决策

关于 HTTP 请求的决策,这里我们通过Chrome给出的官方流程图来给大家讲解

image.png

接下来我给大家解释下上面的步骤

  1. 考虑资源是否可复用
    1. 如果不可以复用,则直接设置 Cache-Controlno-store表示拒绝一切形式的缓存
    2. 如果可以复用,考虑是否设置成每次都需要向浏览器进行缓存的有效确认
      1. 如果需要,则设置 Cache-Controlno-cache
      2. 如果不需要,则考虑是否可以被代理服务器缓存
        1. 如果可以,设置成 public
        2. 如果不行,设置成 private
    3. 考虑资源有效时长
      1. 设置对应的 max-ages-maxage等值
      2. 最后再设置协商缓存需要用到的 ETagLast-Modified

以上便是对于一个HTTP 请求是否要用缓存以及如何使用缓存的决策过程,我们在考虑是否使用缓存的时候也可以按着这个思路来考虑。

网页刷新对缓存的影响

接下来我们看看网页刷新对各类缓存的影响,帮助大家更好地使用缓存

  • 强制刷新: 直接从服务器请求,跳过强缓存和协商缓存(比如 Ctrl + F5
  • 地址栏回车或正常刷新:跳出强缓存,检查协商缓存
  • 新窗口的 URL 访问: 按照正常的缓存策略,即先检查强缓存

上面就是对于浏览器缓存的一些讲解了,相信看到这里你一定对于浏览器缓存有一些新的或者更深入的了解。

TCP/HTTP 在浏览器中的使用

首先我们先学一些前置知识

  • IP:计算机的地址(互联网协议地址)
  • UDP:支持一个无连接的传输协议,为程序提供了一种无需建立连接就可以发送封装的 IP数据包的方法。不能保证数据的可靠性,但是传输速度非常快(可用于一些对完整性要求不高的场景,比如视频、互动游戏等)
  • TCP:一种面向连接的、可靠的、基于字节流的传输层通信协议(连接到不同但互相连接的计算机通信网络的著计算机中的成对进程之间依靠 TCP提供的可靠的通信服务)
  • HTTP:超文本传输协议,通常运行在 TCP之上,通过浏览器和服务器进行数据交互、进行超文本(文本、图片、视频等)传输的规定

接下来我们讲一下 TCP连接的过程(包含 建立连接 - 传输数据 - 断开连接

  1. 建立连接:通过三次握手来建立连接
    1. 客户端向服务端发送 SYN包并等待服务器确认(客户端进入 SYN-SENT阶段)
    2. 服务端收到客户端的 SYN包后,对该包进行确认后结束 LISTEN阶段,并返回一段 TCP报文 随后进入 SYN-RECV阶段
    3. 客户端接受到服务端的 SYN + ACK包之后,确认连接是正常的 从而结束 SYN-SENT阶段并发送最后一段报文
    4. 服务端接收到客户端确认收到服务器数据的报文后,明确连接是正常的,三次握手的过程完成
  2. 传输数据:客户端需要对每个数据包进行确认操作(接收到数据之后要发送确认数据包给服务端)所以当服务端在规定事件内没有接受到客户端的确认消息,就判断丢包并重发 (一个大的文件在传输的过程中会被拆封成多个小的数据包并在客户端按照 TCP 头部的序号排序重组成完整的数据)
  3. 断开连接:通过四次挥手来保证双方都断开连接

image-20220928221859252

总结: TCP牺牲了数据包的传输数量(三次握手和数据包校验机制大大提高了数据包的数量)来保证了数据传输的可靠性

浏览器中 HTTP 的请求流程

先用一张图来看一下浏览器中的 HTTP 请求所经历的各个阶段

img

接下来我们按照图中的流程来解释下里面的各个过程

  1. 构建请求(根据搜索框的信息构建请求 url)

    1. 如果是符合 url的形式则根据对应的网络协议拼接成请求url(比如我们输入 juejin.cn会被自动拼接为 https://juejin.cn/
    2. 如果是一些其他搜索内容,则根据对应的搜索引擎去拼接请求的 url(比如输入 掘金则会自动拼接为 https://www.google.com/search?q=%E6%8E%98%E9%87%91&oq=%E6%8E%98%E9%87%91&aqs=chrome..69i57j0i13j69i59j0i13j0i30j0i13i30l4j0i10i13i15i30.1257j0j15&sourceid=chrome&ie=UTF-8
  2. 查找缓存:根据缓存策略去判断是使用缓存还是重新请求资源

    1. 使用缓存:直接使用缓存,整个请求过程结束
    2. 重新请求:进入网络请求过程
  3. 查找 ip 地址和端口: 根据 DNS(域名系统)查找对应的ip,拿到ip后就需要获取对应的端口(url没有特别指明默认使用 80端口

  4. 等待 tcp 队列:由于 chrome对于每个域名的 TCP连接数量有限制 (同个域名下最多只能建立 6 个连接)

    1. 同一个域名下有大于 6 个请求同时发生,则需要等待
    2. 少于 6 个。则直接建立 TCP 连接
  5. 建立 TCP 连接:进行三次握手进行 TCP 连接,连接成功则进入下一阶段

  6. 发送 HTTP 请求:

    1. 浏览器向服务器发送 请求行 ,包括 请求方法、请求 URI、HTTP 版本协议(如果是像 post这些方法,还需要在 请求体中将对应的数据发送过去)
    2. 发送请求头,包括所使用的操作系统、浏览器内核、域名信息、cookie 等信息
  7. 服务器处理 HTTP 请求: 根据浏览器的请求信息返回相应的内容

    1. 服务器返回的数据格式包括响应行(包括协议版本和 1 状态码等)、响应头(包括生成数据的时间戳、数据类型以及 cookie 等信息)、响应体(客户端实际想要的大部分内容)
    2. 断开连接
      1. 正常情况下服务器返回了响应数据后就会请求端来 TCP连接
      2. 如果浏览器/服务器在头部信息加入了 Connection:Keep-Alive 则不会断开连接(可以省去下次请求时建立连接的时间从而加快资源加载速度)
    3. 重定向:如果服务器返回了 3xx一般就是重定向 比如 301 这个时候会在响应头的 location中带上重定向的地址,浏览器接收到后就会自动再请求重定向的地址 (比如你输入 baidu.com会被重定向到 https://www.baidu.com )

    总结:以上便是浏览器中 HTTP请求的全部过程了,经过了 :构建请求、查找缓存、准备 IP 和端口、等待 TCP 队列、建立 TCP 连接、发起 HTTP 请求、服务器处理请求、服务器返回请求和断开连接这八个阶段

    从输入 URL 到渲染完页面发生了什么

    面试官: 说一说从输入 url 到页面渲染完成这个过程发生了什么?

    我:。。。。。。

    相信大家多多少少都看过上面这个问题,因为这是一道非常高频的经典面试题,面试官往往会从这道来考察你的基础知识(包括浏览器的进程架构、请求过程、渲染原理等等),接下来我们就一起来学习下这个过程,避免下次遇到的时候翻车!!

    我们先回顾下第一小节的知识,浏览器中有 浏览器主进程、网络进程、渲染进程,这三个进程便是这个过程的三大主力军,我们再来回顾下第二小节的内容 浏览器缓存,这会决定是否跳过请求资源的阶段,我们再回顾下上一小节的 HTTP在浏览器中的应用,这将决定哪个进程负责请求中的那个阶段,这么一看是不是发现这道题可以把前面的知识都连接起来!!!

    接下来进入我们的学习吧!

    按照管理,我们先看一下这个过程的大致流程图

    image-20220928232858665

    (作图使用的是https://www.iodraw.com/)

我们先带大家回顾下三个进程的各自职责是干嘛的

  • 浏览器主进程:用户交互、主进程管理以及文件存储等功能
  • 网络进程:面向渲染进程和浏览器主进程提供网络相关功能(包括浏览器缓存)
  • 渲染进程:将 HTML/CSS/JS/图片等资源解析成页面(运行在沙箱内保证系统的安全)

接下来我们按照图来讲解整个过程

  1. 用户输入,浏览器根据用户输入生成请求的 url 并发起请求

  2. 判断是否使用缓存:

    1. 如果有缓存资源,则直接将缓存资源返回给浏览器主进程

    2. 如果不使用缓存,则进入网络请求过程

      1. 进行 DNS 解析 获取对应的 ip地址 (如果是 HTPS 还要建立 TSL连接)
      2. 建立 TCP 连接
      3. 构建请求头、请求行、请求体信息发起请求
      4. 服务器开始处理响应信息
      5. 浏览器接收到响应信息并分析请求头
        1. 如果带有重定向等信息 则重新请求重定向 url 指向的资源
        2. 否则接受响应并提示浏览器主进程准备渲染进程
      6. 判断响应信息的类型(Content-Type
        1. 如果是下载类型 则被提交到下载管理器 URL 请求的导航阶段结束
        2. 如果是 HTML类型 则继续导航流程
      7. 接下来准备渲染流程
    3. 准备渲染流程

      1. 浏览器默认会为每个 tab 页创建一个渲染进程
      2. 如果是属于同一个站点(比如从一个页面打开另一个新的页面且两个页面是同一个站点)则新页面会复用父页面的渲染进程
      3. 准备好渲染进程后将进入提交文档阶段
    4. 提交文档阶段:将网络进程接收到的 HTML数据提交给渲染进程

      1. 浏览器进程接受到网络进程的响应头数据后 ,向浏览器进程发起 提交文档的信息
      2. 渲染进程接收到 提交文档的信息后 就会和网络进程建立起传输数据的 管道
      3. 当文档数据传输完成后 渲染进程会返回 确认提交的信息给浏览器进程
      4. 浏览器进程收到 确认提交的信息后 会更新浏览器页面状态(包括安全状态、地址栏 url、历史状态以及 web 页面) 这就是为什么我们输入一个网址时浏览器并没有立马跳转而是过一会跳转的原因
      5. 之后就进入了渲染阶段

      5.渲染阶段:渲染阶段即将 HTML/CSS/JS/图片等资源渲染成我们看到的页面,这一部分的内容有点多,受限于篇幅,推荐大家直接看我的另一篇文章 浏览器是如何将 HTML、CSS、JavaScript 变成可视的页面的 - 掘金 (juejin.cn)

      总结: 好的,讲到这里我们已经将从输入 URL到页面渲染的内容讲完了,相信大家都收获了一些东西

      结束语:

      不知不觉已经写了好多,无奈涉及的知识太多无法一篇讲完,后续将继续更新浏览器原理知识系列,感兴趣的小伙伴可以点个关注一起学习!!!

      往期推荐:

      谈谈前端性能优化的常见手段及知识原理 - 掘金 (juejin.cn)

      一文带你读懂 webpack 的知识和原理,附带常见面试题! - 掘金 (juejin.cn)

      谈谈前端中的设计模式和使用场景 - 掘金 (juejin.cn)


文章作者: olddog
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 olddog !
  目录