浏览器内核之资料加载与网络栈

img

微信公众号:爱写bugger的阿拉斯加
如有问题或建议,请后台留言,我会尽力解决你的问题。

前言

此文章是我最近在看的【WebKit 技术内幕】一书的一些理解和做的笔记。
而【WebKit 技术内幕】是基于 WebKit 的 Chromium 项目的讲解。
书接上文 浏览器内核之WebKit 架构与模块

1. Webkit 资源加载机制

网络和资源加载是网页的加载和渲染过程中的第一步,加载的资源包括以下内容:

image.png

image.png

在资源类的前面加上 “Cached” 字样,是因为效率问题而引入的缓存机制,所有对资源的请求都会先获取缓存中的信息, 以决定是否向服务器提出资源请求。

2. 资源缓存

资源的缓存机制是提高资源使用效率的有效方法。

它的基本思想是建立一个资源的缓存池。

当 WebKit 需要请求资源的时候,先从资源池中查找是否存在相应的资源。如果有,WebKit 则取出以便使用;如果没有,WebKit 创建一个新的 CachedResource 子类的对象,并发送真正的请求给服务器,WebKit 收到资源后将其设置到该资源类的对象中去,以便于缓存后下次使用。这里缓存指的是内存缓存,而不同于后面在网络栈部份的磁盘缓存。

image.png

WebKit 从资源池中查找资源的关键字是 URL, 因为标记资源唯一的特征就是资源的 URL 。这也意味着,假如两个资源有不同的 URL ,但是它们的内容完全一样,也被认为是两个不同的资源。其实,上面是个简单的示意图,真实的过程比这里要复杂,这其中涉及到了资源的生命周期和失效机制。

3. 资源加载器

按照加载器的类型来分,WebKit 总共有三种类型的加载器。

image.png

image.png

image.png

image.png

image.png

由于从网络获取资源是一个非常耗时的过程,通常一些资源的加载是异步执行的,也就是说网络资源的获取和加载不会阻碍当前 WebKit 的渲染过程,例如图片、CSS 文件。

当然,网页也存在某些特别的资源会阻碍主线程的渲染过程,例如 Javascript 代码文件。这会严重影响 WebKit 下载资源的效率。因为主线程被阻碍了,后面的解析工作没有办法继续往下进行,所以对于 HTML 网页中后面使用的资源也没有办法知道并发送下载请求。

这时候,WebKit 会这样:当前的主线程被阻碍时,WebKit 会启动另外一个线程去遍历后面的 HTMl 网页,收集需要的资源 URL,然后发送请求,这样就可以避免被阻碍。与此同时,WebKit 能够并发下载这些资源,甚至并发下载 JavaScript 代码资源。这种机制对于网页的加载提速很是明显。

4. 资源的生命周期

资源池中的生命周期是什么呢?资源池不能无限大,必须要用相应的机制来替换其中的资源,从而加入新的资源。资源池使用的机制其实很简单,就是采用 LRU(Lease Recent Used 最近最少使用)算法。

另外一方面,当一个资源加载后,通常它会被放入资源池,以便之后使用。问题是,WebKit 如何判断下次使用的时候是否需要更新该资源从而对服务器重新请求?因为服务器可能在某段时间之后更新了该资源。

考虑这样的场景,当用户打开网页后,他想刷新当前的页面。这种情况下,资源池会出现怎样的情况呢?是清除所有的资源,重新获得?还是直接利用当前的资源?都不是。对于某些资源,WebKit 需要直接重新发送请求,要求服务器将内容重新发送过来。但对于很多资源,WebKit 则可以利用 HTTP 协议减少网络负载。在 HTTP 协议的规范中对此有规定,浏览器可以发送消息确认是否需要更新,如果有,浏览器则重新获取该资源;否则就需要利用该资源。

WebKit 的做法是,首先判断资源是否在资源池中,如果是,那么发送一个 HTTP 请求给服务器,说明该资源在本地的一些信息,例如该资源什么时间修改的,服务器则根据该信息作判断,如果没有更新,服务器则发送回状态码 304 ,表明无需更新,那么直接利用资源池中原来的资源;否则。WebKit 申请下载最新的资源内容。

5. Chromium 多进程资源加载

资源的实际加载在各个 WebKit 移植中有不同的实现。Chromium 采用的是多进程的资源加载机制。

image.png

图4-11 描述了关于 Chromium 如何利用多进程架构来完成资源的加载,主要是多个 Render 进程和 Browser 进程之间的调用栈涉及的主要类。

Render 进程在网页的加载过程中需要获取资源,但是由于安全性(实际上,当沙箱模型打开的时候,Render 进程是没有权限去获取资源的)和效率上(资源共享等问题)的考虑,Render 进程的资源获取实际上是通过进程间通信将任务交给 Browser 进程来完成,Browser 进程有权限从网络或者本地获取资源。

在 Chromium 架构的 Renderer 进程中,ResourceHandleInternal 类通过 IPCResource-LoaderBridge 类同 Browser 进程通信。IPCResourceLoaderBridge 类继承自 ResourceLoaderBridge 类,其作用是负责发起请求的对象和回复结果的解释工作,实际消息的接收和派发交给 ResourceDispatcher 类来处理。

资源统一交由 Browser 进程来处理,这使得资源在不同网页间的共享变得很容易。因为每个 Renderer 进程某段时间内可能有多个请求,同时还有多个 Renderer 进程,Browser 进程需要处理大量的资源请求,这就需要一个处理这些请求的调度器,这就是 Chromium 中的 ResourceScheduler。

6. 网络栈

6.1 WebKit 的网络栈

image.png

上图4-13 是 “ent” 所包括的主要子目录,也是 Chromium 网络栈的主要模块。这里面除了一些基础的部分,例如 HTTP 协议。NDS 解析等模块,还包含了 Chromium 为了减少网络时间 而引入的新技术,例如 SPDY 、QUIC 等

image.png

图4-14 描述了从 URLRequest 类到 Socket 类之间的调用过程。以 HTTP 协议为例,图中列出了建立 TCP 的 socket 连接过程中涉及的类。

首先是 URLRequest 类被上层调用并启动请求的时候,它会根据 URL 的 “scheme” 来决定需要创建什么类型的请求。“scheme” 也就是 URL 的协议类型,例如 “http://”、“file://” ,也可以是自定义的 scheme ,例如 Android 系统的 “file://android_asset/”。 URLRequest 对创建的是一个 URLRequestJob 子类的一个对象,例如图中的 URLRequestJob 类。为了支持自定义的 scheme 处理方式, Chromium 使用工厂模式。

URLRequestJob 类和它的工厂类 URLRequestJobFactory 的管理工作都由 URLRequestJobManager 类负责。基本思路是,用户可以在该类中注册多个工厂,当有 URLRquest 请求时,先由工厂检查它是否需要处理该 “scheme” ,如果没有,工厂管理类继续交给下一个工厂类来处理。最后,如果没有任何工厂能够处理,Chromium 则交给内置的工厂来检查和处理是否为 “http://”、“ftp://”、或者 “file://” 等。

图 4-15 就是描述这些类的关系。
image.png

7. 代理

当用户设置代理时,用户代理依赖以下类来处理。

image.png

图 4-17 不仅描述上面这些类,同时也描述了 Chromium 中获取网络代理的过程。图中数据表示获取网络代理的次序,其中的分支 3.1 和 4.1 分别表示简单的代理设置和代理脚本设置的处理过程。

image.png

8. 磁盘本地缓存

如果没有磁盘缓存,当用户访问网页的时候,每次浏览器都要需要从网站下载网页,图片、js 等资源,这其实费力又不讨好。解决这一问题的方法就是将之前浏览器下载的资源保存下来,存到磁盘中,以备今后使用。当然,资源是有时效性的,也会变得不再有效,所以需要有相应的退出机制来解决这一问题。目前大多数浏览器都有磁盘缓存机制,因为缓存机制确实能够提高网页的加载速度。

8.1 特性

为了适应网络资源的本地缓存需求, Chromium 的本地磁盘缓存有几个特性或者要求。

  • 虽然需要缓存的资源可能很多,但磁盘空间不是无限大的,所以必须要有相应的机制来移除合适的缓存资源,以便加入新的资源。

  • 能够确保在浏览器崩溃时不破坏文件,至少能够保护原先在磁盘中的数据。

  • 能够高效和快速地访问磁盘中现有的数据结构,支持同步和异步两种访问方式。

  • 能够避免同时存储两个相同的资源。

  • 能够很方便地从磁盘中删除一个项,同时可以在操作一个项的时候不受其他请求的影响。

  • 磁盘不支持多线程访问,所以需要把所有磁盘缓存的操作放入单独的一个线程。

  • 升级版本时,如果磁盘缓存的内部存储结构发生改变, Chromium 仍然能够支持老版本的结构。

8.2 结构

内部结构主要有个类:Backend 和 Entry 。 Backend 类表示整个磁盘缓存,是所有针对磁盘缓存操作的主入口,表示的是一个缓存表。Entry 类指的是表中的表项。缓存通常是一个表,对于整个表的操作作用在 Backend 类中,包括创建表中的一个个项,每个项由关键字来唯一确定,这个关键字注是资源的 URL。而对项目内的操作包括读写等都是由 Entry 类来处理。

9. Cookie 机制

Cookie 格式就是一系列的 “关键字+值” 对,一个简单的例子如下:

test1 = webkit; test2 = chromium, Expires = Sun, 30 Oct 2016 21:35:00 GMT; Domain = .myweb.cm;

例子中包括两个自定义的关键字,分别是 “test1” 和 “test2” ,它们的值分别为 “webkit” 和 “chromium” 。后面的则是预定义的关键字 “Expires” 和 “Domain”,表示的是该 Cookie 的失败时间和该 Cookie 对应的域。基于安全性考虑,一个网页的 Cookie 只能被该网页(或者说是该域的网页)访问。

根据 Cookie 的时效性可以将 Cookie 分成两种类型,第一种是会话型 Cookie (Session Cookie)。如果 Cookie 没有设置失效时间,就是会话型 Cookie。第二种是持续型 Cookie (Persistent Cookie),也就是当浏览器退出的时候,仍然保留 Cookie 的内容。该类型的 Cookie 有一个有效期,在有效期内,每次访问该 Cookie 所属域的时候,都需要将该 Cookie 发送给服务器,这样服务器能够有效追踪用户的行为。

Chromium 中支持 Cookie 的机制也较为简单和清晰,如图 4-23 所示的是 Chromium 所设计和使用的主要类及其关系。CookieMonster 是Cookie 机制中最重要的类,实际上相当于 Cookie 管理器。

它包括几个作用:

第一是实现 CookieStore 的接口,它是对外的接口,调用者可以设置和获得 Cookie;

第二是报告各种 Cookie 的事件,例如更新信息等,主要使用 Delegate 类,

第三是 Cookie 对象的集合,也就是 CanonicalCookie 的集合,每个 CanonicalCookie 对象都是保存在内存中的,当需要存储到磁盘的时候使用 PersistentCookieStore 类,具体由 SQLitePersistentCookieStore 类负责实际的存储动作。

image.png

10. 安全机制

HTTP 是一种使用明文来传输数据的应用层协议。构建在 SSL 之上的 HTTPS 提供了安全的网络传输机制,现已被广泛应用于网络上。典型的是电子商务、银行支付方面的应用。基本上所有的浏览器都支持该协议, Chromium 当然也不例外。

不仅如此,Chromium 也支持一种新的标准,这就是 HSTS (HTTP Strict TransportSecurity)。该协议能够让网络服务器声明它只支持 HTTPS 协议,所以浏览器能够理解服务器的声明,发送基于 HTTPS 的连接和请求。通常情况下,浏览器用户不会输入 “scheme(http://)”,浏览器的补充功能通常会加入该 “scheme” ,但是,服务器可能需要输入 ”https://“ 。在这样子的情况下,该协议就显得非常有用。一般情况下,服务在返回的消息头中加入以下信息表明它支持该标准:

Strict-Transport-Security: max-age=16070400; includeSubDomains

11. 高性能网络栈

Chromium 的网络模块有两个重要目标,其一是安全,其二是速度。为此,该项目引入了很多 WebKit 所没有的新技术。

11.1 DNS 预取和 TCP 预连接(Preconnect)

一次 DNS 查询的平均时间大概是 60 ~ 120ms 之间或者更长,而 TCP 的三次握手时间大概也是几十毫秒或者更长。为了有效减少这段时间,Chromium 引入了 DNS 预取和 TCP 预连接,它们都是由 Chromium 的 ”Predictor“ 机制来实现的。

首先是 DNS 预取技术。它的主要思想是利用现有的 DNS 机制,提前解析网页中可能的网络连接。具体来讲,当用户正在浏览当前网页的时候,Chromium 提取网页中的超链接,将域名抽取出来,利用比较少的 CPU 和网络带宽来解析这些域名或者 IP 地址,这样一来,用户根本感觉不到这一过程。当用户单击这些链接的时候,可以节省不少时间,特别在域名解析比较慢的时候,效果特别明显。

DNS 预取技术是利用系统的域名解析机制,好处是它不会阻碍当前网络栈的工作。DNS 预取技术针对多个域名采取并行处理的方式,每个域名的解析须由新开启的一个线程来处理,结束后此线程即退出。

当然, DNS 预取技术不仅应用于网页中的超链接,当用户在地址栏中输入地址后,候选项同输入的地址很匹配的时候,在用户敲下回车键获取网页之前, Chromium 已经开始使用 DNS 预取技术解析该域名了。

Chromium 使用追踪技术来获取用户从什么网页跳转到另外一个网页。可以利用这些数据,一些启发式规则和其他一些暗示来预测用户下面会单击什么超链接,当有足够的把握时,它便先 DNS 预取,更进一步,还可以预先建立 TCP 连接。听起来够智能的吧,是的。但是这对用户的隐私是一个极大的挑战,它甚至能预测你单击什么超链接。

同 DSN 预取技术一样,追踪技术不会应用于网页中的超链接,当用户在地址栏中输入地址,如候选项同输入的地址很匹配,则在用户敲击下回车键获取该网页之前,Chromium 就已经开始尝试建立 TCP 连接了。

11.2 HTTP 管线化(PipeLining)

很多时候,服务器和浏览器通话是按顺序来的,浏览器发送一个请求给服务器,等到服务器的回复后,才会发送另外一个请求。弊端是效率极差。

HTTP 1.1 开始增加了管线化技术,Chromium 也支持这一技术,但它需要服务器的支持两者配合才能实现 HTTP 管线化。HTTP 管线化技术是一项同时将多个 HTTP 请求一次性提交给服务器的技术,因此无需等待服务器的回复,因为它可能将多个 HTTP 请求填充在一个 TCP 数据包内。HTTP 管线化需要在网络上传输较少的 TCP 数据包,因此减少了网络负载。

image.png

图4-24 描述了 HTTP 管线化技术是如何传送请求和回复的。

请求结果的管线化使得 HTML 网页加载时间动态提升,特别是在具体有高延迟的连接环境下。在速度较快的网络连接环境下,提速可能不是很明显。因为这些请求还是有明显的先后顺序的。管线化机制需要通过永久连接(Persistent Connection)完成,并且只有 GET 和 HEAD 等请求可以进行管线化,使用场景有很大的限制。

11.3 SPDY

SPDY 就是为了解决网络延迟和安全性问题。根据 Google 的官方数据,使用 SPDY 协议的服务器和客户端可以将网络加载的时间减少 64。

SPDY 协议是一种新的会话层协议,因为网络协议 是一种栈式结构,它被定义在 HTTP 协议和 TCP 协议之间,SPDY 协议的核心思想 是多数复用,仅使用一个连接来传输一个网页中的众多资源。

image.png

11.4 QUIC

QUIC 是一种新的网络传输协议,主要目标是改进 UDP 数据协议的能力。同 SPDY 建立在传输层之上不同,QUIC 所要解决的问题就是传输层的传输效率,并提供了数据的加密,所以,SPDY 可以在 QUIC 之上工作。

12. 实践:高效的资源使用策略

WebKit 和 Chromium 为了高效率地下载资源,设计出了各种各样的策略和新技术。
## 12.1 DNS 和 TCP 连接

DNS 和 TCP 连接占用大量的时间,所以为了高效地加载网页,网页开发者可以从以下方面着手改变以减少这一部分的时间。

  • 减少链接的重定向。有些网页中使用了大量重定向,可能还会有很多次重定向,还不仅要求浏览器建立多次链接,同时还需要多次 DNS 解析,这会阻碍 DNS 预取技术的应用,应该尽量避免。

  • 利用DNS预取机制。网页的开发者当然知道需要链接的 URL,为了让浏览器也知道这些链接,开发者可以指定需要预取的 URL。

  • 搭建支持 SPDY 协议的服务器,当然指的是那些需要使用 HTTPS 协议的网站。

  • 避免错误的链接请求。有些网页中包含了一些失效的链接,当浏览器试图获取该链接对应的资源的时候,就会占用网络资源。

12.2 资源的数量

我们也可以通过减少网页中所需的资源数量来改善网页的加载:

  • 在 HTML 网页中内嵌小型的资源,也就是当资源比较小的时候,可以将它们直接放在网页中,可能的资源如 CSS、JavaScript 和图片等。比如图片 用 webpack 直接打包成 base64。

  • 合并一些资源,例如 CSS、JavaScript 和图片。常见的就是一些网页中大量使用的小图片,可以将它们合并成一张大的图片以供使用。

12.3 资源的数据量

对于每个资源而言,通过减少它的数据量来提高网页的加载速度:

  • 使用浏览器本地磁盘缓存机制。因为 HTTP 协议支持资源的失效机制,通过对资源设置适当的失效期来减少浏览器对资源的重复获取。

  • 启用资源的压缩技术。例如,对于图片而言,可以使用 zip 压缩技术,然后在 HTTP 消息头中说明该资源经过压缩,这样可以有效减少网络传输的数据量。

最后

希望本文对你有点帮助。

下期分享 第五章 HTML解释器与模型 敬请期待。

对 全栈开发 有兴趣的朋友可以扫下方二维码关注我的公众号 —— 爱写bugger的阿拉斯加

分享 web 开发相关的技术文章,热点资源,全栈程序员的成长之路。

陛下…看完奏折,点个赞再走吧!

爱写bugger的阿拉斯加

猜你喜欢

转载自blog.csdn.net/ch834301/article/details/82228294
今日推荐