资源加载和网络栈

65 阅读13分钟

资源加载和网络栈

webkit资源加载机制

资源

HTML支持的资源主要包括以下类型:

    1. HTML
    2. JavaScript文件
    3. css样式表
    4. 图片
    5. SVG
    6. css shader文件
    7. 音频、视频和字幕
    8. 字体文件
    9. XSL样式表

以上这些资源在webkit中均有不同的类表示,他们的公共基类是CachedResource。图4-1表示的是CachedResource类和子类。(注,在webkit中,HTML文本的类型叫MainResource类,对应的资源类是CachedRawResource类。)

资源缓存

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

基本思想是,建议一个资源缓存池,当webkit需要请求资源时,先从资源池中查找是否有相应资源。

    • 有。webkit取出资源使用。
    • 没有。webkit会先创建一个CachedResource子类的对象,并发送真正的网络请求给服务器;webkit收到资源后将其放入该资源类的对象中去,以便缓存后下次使用。(注:这里的缓存指的是内存缓存

图4-2展示了这一机制的原理。

webkit从资源池查找资源的关键字是URL。内容完全一样的两个资源拥有不同的URL,也会被认为是两个资源。

资源加载器

webkit一共有三种类型的资源加载器。

    1. 针对每种资源类型的特定加载器
      1. 仅加载某一种资源。如image元素,需要图片资源,对应的特定加载器是imageLoader。
      2. 特定加载器没有公共基类。
      3. 加载器属于它的调用者。如图4-3是图片加载器。

    1. 资源缓存机制的资源加载器
      1. 所有特定加载器都共享它查找并插入缓存资源-CachedResourceLoader类。
      2. 特定加载器通过缓存机制的资源加载器查找是否有缓存资源,它属于HTML的文档对象。如图4-4。

    1. 通用资源加载器-ResourceLoader类。
      1. 在webkit需要从网络或者文件系统获取资源时,使用该类获取资源的数据。
      2. 被所有特定加载器共享。
      3. 属于CachedResource类,但与CachedResourceLoader无继承关系 (易混淆)

过程

图4-6描述的是一个带有资源缓存机制的加载资源的全过程,包括资源已经在缓存中和不在缓存中的两种情况。

资源的生命周期

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

另一方面,当一个资源加载后,通常会被放入资源池,以后之后使用。

问题来了,webkit如何判断缓存的资源是否需要更新从而发送请求给服务器呢?

webkit的做法是,首先判断资源是否在资源池中,

      • 如果是,发送http请求给服务器,说明资源在本地的信息(如资源什么时候修改的),服务器根据信息判断,资源是否更新了
        • 如果没有更新,服务器返回304无需更新,直接使用资源池中的资源。
        • 否则webkit下载最新的资源。
      • 否则webkit下载最新的资源。

实践:资源的缓存

Chromium多进程资源加载

多进程

资源的加载在不同的webkit移植中有不同的实现,Chromium采取的是多进程的资源加载机制。

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

Renderer进程在网页的加载过程中需要获取资源,但是由于安全性(当沙盒模成型打开时,Renderer进程是没有权限获取资源)和效率上(资源共享等)的考虑,Renderer进程获取资源实际上是,通过进程间的通信将任务交给Browser进程,Browser进程有权限从网络或文件系统获取资源。

Chromium架构的Renderer进程中,ResourceHandleInternal通过IPCResourceLoaderBridge类和Browser进程进行通信。IPCResourceLoaderBridge继承自ResourceLoaderBridge类,负责发起请求的对象和回复结果的解释工作,消息的接收和派发则交给ResourceDispatcher类处理。

Browser进程中,首先由ResourceMessageFilter类过滤Renderer进程发来的消息,如果是请求资源类消息,则转发给ResourceDispatcherHostImpl类,ResourceDispatcherHostImpl类创建Browser中的ResourceLoader对象来处理。

ResourceLoader对象实际上是Browser进程的资源加载类,负责管理 向网络发起的请求、从网络接收过来的认证请求、请求的回复管理等工作。

URLRequest类从网络或者本地文件系统读取信息,承担了建立网络连接、发送请求数据、接收回复数据的工作,在网络栈中会有详细讲解。

工作方式和资源共享

资源请求有同步和异步两种方式。对于这两种方式,ResourceLoader类使用类和类向Renderer进程发送状态消息,接收Renderer进程的反馈等,如图4-12描述了这些类之间的关系。

从图4-12可以发现还有两个ResourceHandle子类。

    1. BufferResourceHandle类:该类缓冲网络或文件传过来的数据,直到数据满足需求转给设置的另一个ResourceHandle对象。
    2. ThrottlingResourceHandle类:面对很多个资源请求时仅使用一个URLRequest对象获取资源,有效减少网络开销(因为不需要重新建立网络连接)。
    3. layeredResourceHandle类:上面两个类的父类,不直接参与资源的处理,只转发,无实际意义。

此外,在Chromium中还有很多ResourceHandle的子类,他们作用各异。

    1. RedirectToFileResourceHandler:继承自layeredResourceHandle类。接收到的数据转给另一个ResourceHandler类,同时转存到文件。
    2. StreamResourceHandler:继承自layeredResourceHandle类。接受的数据转给另一个ResourceHandler类,同时转存到数据流。
    3. CertificateResourceHandler:主要处理证书类的资源请求。

资源统一由Browser进程管理,这使得不同网页间的资源共享变得很容易。同时面临一个问题,每个Renderer进程某段时间内有多个资源请求,同时有多个Renderer进程,Browser进程需要处理大量的资源请求,这就需要一个处理请求的调度器,就是ChromiumResourcScheduler

ResourcScheduler类管理的对象就是图4-11最顶层的net::URLRequest对象。

ResourcScheduler类根据URLRequest的标记和优先级来调度URLRequest对象。

每个URLRequest对象包含ChildIdRouteId来标记属于哪个Renderer进程。

ResourcScheduler有一个哈希表,该表按照进程来组织URLRequest对象。

对于以下类型的网络请求,将会立即被Chromium发出。

    1. 高优先级的请求
    2. 同步请求
    3. 服务器具有SPDY能力

以上讨论的代码均在Chromium的"content/browser/loader"中。



网络栈

webkit的网络部分代码比较少,主要是一些消息头、MIME消息、状态码等信息的描述和处理,没有实质的网络连接和针对网络的优化。

Chromium网络栈

前面提到的URLRequest之类之下是网络栈内容,本节重点描述这部分。

网络栈基本组成

如图4-13展示了Chromium网络栈的主要模块。

网络栈结构

如图4-14描述了从URLRequest类到Soket类之间的调用过程。

    • 首先是URLRquest类悲伤层调用并启动的话死后,会根据URL的scheme决定创建什么样的请求。
      • scheme是URL的协议类型,如http://、ftp://;
      • 也可以是自定义的,如Android的"file://android_asset/"。
    • 其次,当URLRequestJob对象被创建后,该对象首先从cookie管理器中获取URL相关信息。
    • 再次,HttpNetworkTransaction类使用HttpNetworkSession类来管理连接对话。
    • 最后是,套接字的建立。

代理

当用户设置代理后,网络栈结构是如何组织的?

  • ProxyService:对于一个URL,HttpStreamFactory类使用ProxyService获取代理信息。
    • ProxyService类检查当前代理设置是不是最新的,如果不是,则依赖ProxyConfigService获取代理信息。
    • 该类不处理实际任务,使用ProxyResolver做实际代理工作。
  • ProxyConfigService:获取代理信息的类。
  • ProxyScriptFetcher:chromium支持代理的JavaScript脚本,负责从代理的URL中获取该脚本。
  • ProxyResolver:负责代理的解释和执行,通常启用新的线程来处理。
  • ProxyResolverV8:ProxyResolver的子类,使用V8引擎来解释执行脚本。

图4-17描述以上类,以及chromium获取网络代理的过程。其中分支3.1和4.1分别代表简单的代理设置和代理脚本设置的处理过程。

域名解析(DNS)

chromium使用HostResolverImpl类解析域名,具体调用的是“getaddrinfo()”,是一个阻塞式函数,使用单独的线程处理。

为了考虑效率,使用HostCache类保存解析后的域名,还有DNS预解析机制。

磁盘本地缓存

绝大多数浏览器都有磁盘缓存机制,因为缓存机制确实能够提高网页的加载速度。

为了适应网络资源的本地缓存需求,chromium的本地磁盘缓存有几个特性:

    • 有相应的机制来移除合适的缓存资源。
    • 确保浏览器崩溃时不破坏磁盘文件。
    • 可以通过同步和异步方式高效快速地访问。
    • 能够避免存储相同的资源。
    • 操作一个项时不影响其他请求。
    • 不支持多线程访问,磁盘缓存操作放入单独的线程。
    • 支持老版本结构。

实现上有两个类,BackEnd(整个磁盘缓存)和Entry(表项)。

至少需要一个索引文件和四个数据文件。

      • 索引文件用来检索存放在数据文件中的众多索引项。
      • 数据文件又称为块文件,它的结构为一个文件头加上后面的块文件,每个块的大小固定。当超过块大小时会为其分配多个块。但最多不超过四个,当超过时用单独的文件存储。

索引文件包括一个索引头部和索引地址表。

    • 头部表示索引文件的信息(索引文件版本号,索引项数量、文件大小等信息)
    • 索引地址表保存各个表项对应的索引地址,直接将文件映射到内存地址,从内存地址可以快速找到数据文件。
    • 如果一个表项需要分配四个块,那么和块在文件中的索引位置是对齐的(起始块的位置是4的倍数)

表象结构也分为两部分

    • 第一部分标记自己,包括各种元数据信息和自身内容,通常变动较少。
    • 另一部分经常发生变动,大小固定,主要为表项的回收算法服务(LRU算法)。

总结以上可以描绘出磁盘缓存的存储结构,如图4-22。

Cookie机制

基于安全性考虑,一个网页的cookie只能被该网页(或者说,该域的网页)访问。

根据cookie的时效性可以分为两种:

    • 会话型cookie
      • 只保存在内存中,浏览器退出即清除。
      • 如果cookie没有设置失效时间,就是会话型cookie
    • 持续型cookie
      • 当浏览器退出时,仍然保留cookie的内容
      • 该类型的cookie有一个有效期
      • 有效期内访问cookie所属域,都需要把cookie发送给服务器,以便服务器能够跟踪用户行为。

高性能网络栈

DNS预取和TCP预连接

一次DNS查询大概60-120ms,TCP的三次握手大概几十毫秒或更长。

DNS预取技术:主要思想是利用现有的DNS机制,提前解析网页中可能的网络连接。

    • 该技术不使用chromium的网络栈,而是利用系统的域名解析机制,不会阻碍当前网络栈的工作。
    • 针对多个域名采取并行处理,每个域名的解析须有新开启的一个线程处理,结束后退出。
    • 网页开发者可以显示指定预取哪些域名,具体做法是:<link rel="dns-prefetch" href="http://example.com">。用户地址栏同理。

HTTP管线化

HTTP1.1增加了管线化技术,可以将多个http请求一次性提交给服务器,无需等待服务器的回复,因为它可能将填充在一个TCP数据包内。

能在高延迟的连接环境下有明显的性能提升。

局限性:需要通过永久连接完成,并且只有get和head等请求可以管线化。

SPDY

为了解决网络延迟和安全性问题,chromium引入SPDY。根据Google官方数据,使用SPDY的服务器和客户端可以将网络加载的时间减少64%。在HTTP2.0草案中将引入SPDY,将其作为基础编写。

SPDY协议是一种新的会话层协议,是一种栈式结构,被定义在HTTP协议和TCP协议之间。层次关系如4-25所示。

SPDY的核心思想是多路复用。仅使用一个链接来传输一个网页的众多资源。

本质上并没有改变HTTP协议,只是将HTTP协议头通过SPDY协议来封装和传输。

数据传输方式也没有变化,还是使用TCP/IP协议。

所以,SPDY相对容易部署,服务器只需要插入SPDY的解释层从SPDY的消息头中获取各个资源的HTTP头即可。

SPDY必须建立在SSL层之上。

SPDY的特征:

    • 利用一个TCP连接来传输不限个数的资源请求的读写数据流。提高TCP连接的利用率,减少TCP连接的维护成本。
    • 可以调整资源请求的优先级。
    • 对请求使用压缩技术,大大减少需要传送的字节数。
    • 可以尝试发送信息给浏览器,高速后帘可能需要哪些资源。更极端情况,服务器可以主动发送资源。

QUIC

QUIC是一种新的网络传输协议,主要是改进UDP数据协议的能力,解决传输层的传输效率,提供数据加密。

SPDY可以在QUIC之上工作。

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

DNS和TCP连接

DNS解析和TCP连接占用大量时间,网页开发者可以从以下方面着手改变:

    • 减少重定向链接。
    • 利用DNS预取机制。
    • 搭建支持SPDY协议的服务器。
    • 避免错误的连接请求。

资源的数量

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

  • html网页内嵌小型资源,即当资源比较小时,可以直接放在网页中。如图片较小时,可以转换为base64字符串。
  • 合并一些资源,如css、JavaScript和图片。常见的是大量使用的小图片,可以合并成一张大的图片以共使用。

资源的数据量

对每个资源来说,可以通过减少数据量来提高加载速度。

  • 使用浏览器本地磁盘缓存机制。
  • 启用资源的压缩技术。

其他的技巧,如减少无用的空格、启用异步资源加载等。