资源加载和网络栈
webkit资源加载机制
资源
HTML支持的资源主要包括以下类型:
-
- HTML
- JavaScript文件
- css样式表
- 图片
- SVG
- css shader文件
- 音频、视频和字幕
- 字体文件
- XSL样式表
以上这些资源在webkit中均有不同的类表示,他们的公共基类是CachedResource。图4-1表示的是CachedResource类和子类。(注,在webkit中,HTML文本的类型叫MainResource类,对应的资源类是CachedRawResource类。)
资源缓存
资源缓存机制是提高资源使用效率的有效方法。
基本思想是,建议一个资源缓存池,当webkit需要请求资源时,先从资源池中查找是否有相应资源。
-
- 有。webkit取出资源使用。
- 没有。webkit会先创建一个CachedResource子类的对象,并发送真正的网络请求给服务器;webkit收到资源后将其放入该资源类的对象中去,以便缓存后下次使用。(注:这里的缓存指的是内存缓存)
图4-2展示了这一机制的原理。
webkit从资源池查找资源的关键字是URL。内容完全一样的两个资源拥有不同的URL,也会被认为是两个资源。
资源加载器
webkit一共有三种类型的资源加载器。
-
- 针对每种资源类型的特定加载器。
-
-
- 仅加载某一种资源。如image元素,需要图片资源,对应的特定加载器是imageLoader。
- 特定加载器没有公共基类。
- 加载器属于它的调用者。如图4-3是图片加载器。
-
-
- 资源缓存机制的资源加载器。
-
-
- 所有特定加载器都共享它查找并插入缓存资源-CachedResourceLoader类。
- 特定加载器通过缓存机制的资源加载器查找是否有缓存资源,它属于HTML的文档对象。如图4-4。
-
-
- 通用资源加载器-ResourceLoader类。
-
-
- 在webkit需要从网络或者文件系统获取资源时,使用该类获取资源的数据。
- 被所有特定加载器共享。
- 属于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子类。
-
- BufferResourceHandle类:该类缓冲网络或文件传过来的数据,直到数据满足需求转给设置的另一个ResourceHandle对象。
- ThrottlingResourceHandle类:面对很多个资源请求时仅使用一个URLRequest对象获取资源,有效减少网络开销(因为不需要重新建立网络连接)。
- layeredResourceHandle类:上面两个类的父类,不直接参与资源的处理,只转发,无实际意义。
此外,在Chromium中还有很多ResourceHandle的子类,他们作用各异。
-
- RedirectToFileResourceHandler:继承自layeredResourceHandle类。接收到的数据转给另一个ResourceHandler类,同时转存到文件。
- StreamResourceHandler:继承自layeredResourceHandle类。接受的数据转给另一个ResourceHandler类,同时转存到数据流。
- CertificateResourceHandler:主要处理证书类的资源请求。
资源统一由Browser进程管理,这使得不同网页间的资源共享变得很容易。同时面临一个问题,每个Renderer进程某段时间内有多个资源请求,同时有多个Renderer进程,Browser进程需要处理大量的资源请求,这就需要一个处理请求的调度器,就是Chromium的ResourcScheduler。
ResourcScheduler类管理的对象就是图4-11最顶层的net::URLRequest对象。
ResourcScheduler类根据URLRequest的标记和优先级来调度URLRequest对象。
每个URLRequest对象包含ChildId和RouteId来标记属于哪个Renderer进程。
ResourcScheduler有一个哈希表,该表按照进程来组织URLRequest对象。
对于以下类型的网络请求,将会立即被Chromium发出。
-
- 高优先级的请求
- 同步请求
- 服务器具有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和图片。常见的是大量使用的小图片,可以合并成一张大的图片以共使用。
资源的数据量
对每个资源来说,可以通过减少数据量来提高加载速度。
- 使用浏览器本地磁盘缓存机制。
- 启用资源的压缩技术。
其他的技巧,如减少无用的空格、启用异步资源加载等。