微信公衆號:愛寫bugger的阿拉斯加
若有問題或建議,請後臺留言,我會盡力解決你的問題。
此文章是我最近在看的【WebKit 技術內幕】一書的一些理解和作的筆記。
而【WebKit 技術內幕】是基於 WebKit 的 Chromium 項目的講解。
書接上文 瀏覽器內核之WebKit 架構與模塊vue
網絡和資源加載是網頁的加載和渲染過程當中的第一步,加載的資源包括如下內容:java
在資源類的前面加上 「Cached」 字樣,是由於效率問題而引入的緩存機制,全部對資源的請求都會先獲取緩存中的信息, 以決定是否向服務器提出資源請求。react
資源的緩存機制是提升資源使用效率的有效方法。android
它的基本思想是創建一個資源的緩存池。webpack
當 WebKit 須要請求資源的時候,先從資源池中查找是否存在相應的資源。若是有,WebKit 則取出以便使用;若是沒有,WebKit 建立一個新的 CachedResource 子類的對象,併發送真正的請求給服務器,WebKit 收到資源後將其設置到該資源類的對象中去,以便於緩存後下次使用。這裏緩存指的是內存緩存,而不一樣於後面在網絡棧部份的磁盤緩存。程序員
WebKit 從資源池中查找資源的關鍵字是 URL, 由於標記資源惟一的特徵就是資源的 URL 。這也意味着,假如兩個資源有不一樣的 URL ,可是它們的內容徹底同樣,也被認爲是兩個不一樣的資源。其實,上面是個簡單的示意圖,真實的過程比這裏要複雜,這其中涉及到了資源的生命週期和失效機制。web
按照加載器的類型來分,WebKit 總共有三種類型的加載器。算法
因爲從網絡獲取資源是一個很是耗時的過程,一般一些資源的加載是異步執行的,也就是說網絡資源的獲取和加載不會阻礙當前 WebKit 的渲染過程,例如圖片、CSS 文件。瀏覽器
固然,網頁也存在某些特別的資源會阻礙主線程的渲染過程,例如 Javascript 代碼文件。這會嚴重影響 WebKit 下載資源的效率。由於主線程被阻礙了,後面的解析工做沒有辦法繼續往下進行,因此對於 HTML 網頁中後面使用的資源也沒有辦法知道併發送下載請求。緩存
這時候,WebKit 會這樣:當前的主線程被阻礙時,WebKit 會啓動另一個線程去遍歷後面的 HTMl 網頁,收集須要的資源 URL,而後發送請求,這樣就能夠避免被阻礙。與此同時,WebKit 可以併發下載這些資源,甚至併發下載 JavaScript 代碼資源。這種機制對於網頁的加載提速非常明顯。
資源池中的生命週期是什麼呢?資源池不能無限大,必需要用相應的機制來替換其中的資源,從而加入新的資源。資源池使用的機制其實很簡單,就是採用 LRU(Lease Recent Used 最近最少使用)算法。
另一方面,當一個資源加載後,一般它會被放入資源池,以便以後使用。問題是,WebKit 如何判斷下次使用的時候是否須要更新該資源從而對服務器從新請求?由於服務器可能在某段時間以後更新了該資源。
考慮這樣的場景,當用戶打開網頁後,他想刷新當前的頁面。這種狀況下,資源池會出現怎樣的狀況呢?是清除全部的資源,從新得到?仍是直接利用當前的資源?都不是。對於某些資源,WebKit 須要直接從新發送請求,要求服務器將內容從新發送過來。但對於不少資源,WebKit 則能夠利用 HTTP 協議減小網絡負載。在 HTTP 協議的規範中對此有規定,瀏覽器能夠發送消息確認是否須要更新,若是有,瀏覽器則從新獲取該資源;不然就須要利用該資源。
WebKit 的作法是,首先判斷資源是否在資源池中,若是是,那麼發送一個 HTTP 請求給服務器,說明該資源在本地的一些信息,例如該資源什麼時間修改的,服務器則根據該信息做判斷,若是沒有更新,服務器則發送回狀態碼 304 ,代表無需更新,那麼直接利用資源池中原來的資源;不然。WebKit 申請下載最新的資源內容。
資源的實際加載在各個 WebKit 移植中有不一樣的實現。Chromium 採用的是多進程的資源加載機制。
圖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。
上圖4-13 是 「ent」 所包括的主要子目錄,也是 Chromium 網絡棧的主要模塊。這裏面除了一些基礎的部分,例如 HTTP 協議。NDS 解析等模塊,還包含了 Chromium 爲了減小網絡時間 而引入的新技術,例如 SPDY 、QUIC 等
圖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 就是描述這些類的關係。
當用戶設置代理時,用戶代理依賴如下類來處理。
圖 4-17 不只描述上面這些類,同時也描述了 Chromium 中獲取網絡代理的過程。圖中數據表示獲取網絡代理的次序,其中的分支 3.1 和 4.1 分別表示簡單的代理設置和代理腳本設置的處理過程。
若是沒有磁盤緩存,當用戶訪問網頁的時候,每次瀏覽器都要須要從網站下載網頁,圖片、js 等資源,這其實費力又不討好。解決這一問題的方法就是將以前瀏覽器下載的資源保存下來,存到磁盤中,以備從此使用。固然,資源是有時效性的,也會變得再也不有效,因此須要有相應的退出機制來解決這一問題。目前大多數瀏覽器都有磁盤緩存機制,由於緩存機制確實可以提升網頁的加載速度。
爲了適應網絡資源的本地緩存需求, Chromium 的本地磁盤緩存有幾個特性或者要求。
內部結構主要有個類:Backend 和 Entry 。 Backend 類表示整個磁盤緩存,是全部針對磁盤緩存操做的主入口,表示的是一個緩存表。Entry 類指的是表中的表項。緩存一般是一個表,對於整個表的操做做用在 Backend 類中,包括建立表中的一個個項,每一個項由關鍵字來惟一肯定,這個關鍵字注是資源的 URL。而對項目內的操做包括讀寫等都是由 Entry 類來處理。
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 類負責實際的存儲動做。
HTTP 是一種使用明文來傳輸數據的應用層協議。構建在 SSL 之上的 HTTPS 提供了安全的網絡傳輸機制,現已被普遍應用於網絡上。典型的是電子商務、銀行支付方面的應用。基本上全部的瀏覽器都支持該協議, Chromium 固然也不例外。
不只如此,Chromium 也支持一種新的標準,這就是 HSTS (HTTP Strict TransportSecurity)。該協議可以讓網絡服務器聲明它只支持 HTTPS 協議,因此瀏覽器可以理解服務器的聲明,發送基於 HTTPS 的鏈接和請求。一般狀況下,瀏覽器用戶不會輸入 「scheme(http://)」,瀏覽器的補充功能一般會加入該 「scheme」 ,可是,服務器可能須要輸入 」https://「 。在這樣子的狀況下,該協議就顯得很是有用。通常狀況下,服務在返回的消息頭中加入如下信息代表它支持該標準:
Strict-Transport-Security: max-age=16070400; includeSubDomains
Chromium 的網絡模塊有兩個重要目標,其一是安全,其二是速度。爲此,該項目引入了不少 WebKit 所沒有的新技術。
一次 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 鏈接了。
不少時候,服務器和瀏覽器通話是按順序來的,瀏覽器發送一個請求給服務器,等到服務器的回覆後,纔會發送另一個請求。弊端是效率極差。
HTTP 1.1 開始增長了管線化技術,Chromium 也支持這一技術,但它須要服務器的支持二者配合才能實現 HTTP 管線化。HTTP 管線化技術是一項同時將多個 HTTP 請求一次性提交給服務器的技術,所以無需等待服務器的回覆,由於它可能將多個 HTTP 請求填充在一個 TCP 數據包內。HTTP 管線化須要在網絡上傳輸較少的 TCP 數據包,所以減小了網絡負載。
圖4-24 描述了 HTTP 管線化技術是如何傳送請求和回覆的。
請求結果的管線化使得 HTML 網頁加載時間動態提高,特別是在具體有高延遲的鏈接環境下。在速度較快的網絡鏈接環境下,提速可能不是很明顯。由於這些請求仍是有明顯的前後順序的。管線化機制須要經過永久鏈接(Persistent Connection)完成,而且只有 GET 和 HEAD 等請求能夠進行管線化,使用場景有很大的限制。
SPDY 就是爲了解決網絡延遲和安全性問題。根據 Google 的官方數據,使用 SPDY 協議的服務器和客戶端能夠將網絡加載的時間減小 64。
SPDY 協議是一種新的會話層協議,由於網絡協議 是一種棧式結構,它被定義在 HTTP 協議和 TCP 協議之間,SPDY 協議的核心思想 是多數複用,僅使用一個鏈接來傳輸一個網頁中的衆多資源。
QUIC 是一種新的網絡傳輸協議,主要目標是改進 UDP 數據協議的能力。同 SPDY 創建在傳輸層之上不一樣,QUIC 所要解決的問題就是傳輸層的傳輸效率,並提供了數據的加密,因此,SPDY 能夠在 QUIC 之上工做。
WebKit 和 Chromium 爲了高效率地下載資源,設計出了各類各樣的策略和新技術。
## 12.1 DNS 和 TCP 鏈接
DNS 和 TCP 鏈接佔用大量的時間,因此爲了高效地加載網頁,網頁開發者能夠從如下方面着手改變以減小這一部分的時間。
咱們也能夠經過減小網頁中所需的資源數量來改善網頁的加載:
對於每一個資源而言,經過減小它的數據量來提升網頁的加載速度:
但願本文對你有點幫助。
下期分享 第五章 HTML解釋器與模型 敬請期待。
對 全棧開發 有興趣的朋友能夠掃下方二維碼關注個人公衆號 —— 愛寫bugger的阿拉斯加
分享 web 開發相關的技術文章,熱點資源,全棧程序員的成長之路。
你們一塊兒交流成長。
只要關注公衆號並回復 福利 便送你六套、而且每套價值 3999 元的視頻資源: Python、Java、Linux、Go、vue、react、javaScript