前言
開啟瀏覽器從輸入網址到網頁呈現在大家面前,背後到底發生了什麼?經歷怎麼樣的一個過程?先給大家來張總體流程圖,具體步驟請看下文分解!寫文章不易,請多多支援與關注!
總體來說分為以下幾個過程:
- DNS 解析:將域名解析成 IP 地址
- TCP 連線:TCP 三次握手
- 傳送 HTTP 請求
- 伺服器處理請求並返回 HTTP 報文
- 瀏覽器解析渲染頁面
- 斷開連線:TCP 四次揮手
具體過程
URL 到底是啥
URL(Uniform Resource Locator),統一資源定位符
,用於定位網際網路上資源,俗稱網址。 比如 http://www.w3school.com.cn/html/index.asp
,遵守以下的語法規則:
scheme://host.domain:port/path/filename 各部分解釋如下:
scheme:
定義因特網服務的型別。常見的協議有 http、https、ftp、file,其中最常見的型別是 http,而 https 則是進行加密的網路傳輸。host:
定義域主機(http 的預設主機是 www)domain:
定義因特網域名,比如 w3school.com.cnport:
定義主機上的埠號(http 的預設埠號是 80)path:
定義伺服器上的路徑(如果省略,則文件必須位於網站的根目錄中)filename:
定義文件/資源的名稱
域名解析(DNS)
在瀏覽器輸入網址後,首先要經過域名解析,因為瀏覽器並不能直接通過域名找到對應的伺服器,而是要通過 IP 地址。大家這裡或許會有個疑問----計算機既可以被賦予 IP 地址,也可以被賦予主機名和域名。比如 www.hackr.jp
。那怎麼不一開始就賦予個 IP 地址?這樣就可以省去解析麻煩。我們先來了解下什麼是 IP 地址
IP 地址
IP 地址是指網際網路協議地址,是 IP Address 的縮寫。IP 地址是 IP 協議提供的一種統一的地址格式,它為網際網路上的每一個網路和每一臺主機分配一個邏輯地址,以此來遮蔽實體地址的差異。IP 地址是一個 32 位的二進位制數,比如 127.0.0.1 為本機 IP。 域名就相當於 IP 地址喬裝打扮的偽裝者,帶著一副面具。它的作用就是便於記憶和溝通的一組伺服器的地址。 使用者通常使用主機名或域名來訪問對方的計算機,而不是直接通過 IP 地址訪問。因為與 IP 地址的一組純數字相比,用字母配合數字的表示形式來指定計算機名更符合人類的記憶習慣。但要讓計算機去理解名稱,相對而言就變得困難了。因為計算機更擅長處理一長串數字。為了解決上述的問題,DNS 服務應運而生。
什麼是域名解析
DNS 協議提供通過域名查詢 IP 地址,或逆向從 IP 地址反查域名的服務。DNS 是一個網路伺服器,我們的域名解析簡單來說就是在 DNS 上記錄一條資訊記錄。
例如 baidu.com 220.114.23.56(伺服器外網IP地址)80(伺服器埠號)
複製程式碼
域名解析過程
上述圖片是查詢www.google.com的IP地址過程。
首先在本地域名伺服器
中查詢IP地址,如果沒有找到的情況下,本地域名伺服器會向根域名伺服器
傳送一個請求,如果根域名伺服器也不存在該域名時,本地域名會向com頂級域名伺服器
傳送一個請求,依次類推下去。直到最後本地域名伺服器得到google的IP地址並把它快取到本地,供下次查詢使用。從上述過程中,可以看出網址的解析是一個從右向左的過程: com -> google.com -> www.google.com
。但是你是否發現少了點什麼,根域名伺服器的解析過程呢?事實上,真正的網址是www.google.com.
,並不是我多打了一個.,這個.對應的就是根域名伺服器,預設情況下所有的網址的最後一位都是.,既然是預設情況下,為了方便使用者,通常都會省略,瀏覽器在請求DNS的時候會自動加上,所有網址真正的解析過程為: . -> .com -> google.com. -> www.google.com.
。
DNS優化
瞭解了DNS的過程,可以為我們帶來哪些?上文中請求到google的IP地址時,經歷了8個步驟,這個過程中存在多個請求(同時存在UDP和TCP請求,為什麼有兩種請求方式,請自行查詢)。如果每次都經過這麼多步驟,是否太耗時間?如何減少該過程的步驟呢?那就是DNS快取。
DNS快取
DNS存在著多級快取,從離瀏覽器的距離排序的話,有以下幾種: 瀏覽器快取
,作業系統快取
,路由器快取
,ISP伺服器快取
,根域名伺服器快取
,頂級域名伺服器快取
,主域名伺服器快取
。
-
瀏覽器快取:瀏覽器會按照一定的頻率快取 DNS 記錄。
-
作業系統快取:如果瀏覽器快取中找不到需要的 DNS 記錄,那就去作業系統中找。
-
路由器快取:路由器也有 DNS 快取。
-
ISP伺服器快取:ISP 是網際網路服務提供商(Internet Service Provider)的簡稱,ISP 有專門的 DNS 伺服器應對 DNS 查詢請求。
-
根域名伺服器快取:ISP 的 DNS 伺服器還找不到的話,它就會向根域名伺服器發出請求,進行遞迴查詢(DNS 伺服器先問
根域名伺服器
.com 域名伺服器的 IP 地址,然後再問.baidu 域名伺服器,依次類推)- 在你的chrome瀏覽器中輸入
chrome://net-internals/#dns
,你可以看到chrome瀏覽器的DNS快取。 - 作業系統快取主要存在
/etc/hosts
(Linux系統)中
- 在你的chrome瀏覽器中輸入
DNS負載均衡
不知道大家有沒有思考過一個問題:
DNS返回的IP地址是否每次都一樣?如果每次都一樣是否說明你請求的資源都位於同一臺機器上面,那麼這臺機器需要多高的效能和儲存才能滿足億萬請求呢?其實真實的網際網路世界背後存在成千上百臺伺服器,大型的網站甚至更多。但是在使用者的眼中,它需要的只是處理他的請求,哪臺機器處理請求並不重要。DNS可以返回一個合適的機器的IP給使用者,例如可以根據每臺機器的負載量
,該機器離使用者地理位置的距離
等等,這種過程就是DNS負載均衡
,又叫做DNS重定向
。大家耳熟能詳的CDN(Content Delivery Network)就是利用DNS的重定向技術,DNS伺服器會返回一個跟使用者最接近的點的IP地址給使用者,CDN節點的伺服器負責響應使用者的請求,提供所需的內容。
小結
瀏覽器通過向 DNS 伺服器傳送域名,DNS 伺服器查詢到與域名相對應的 IP 地址,然後返回給瀏覽器,瀏覽器再將 IP 地址打在協議上,同時請求引數也會在協議搭載,然後一併傳送給對應的伺服器。接下來介紹向伺服器傳送 HTTP 請求階段,HTTP 請求分為三個部分:TCP 三次握手、http 請求響應資訊、關閉 TCP 連線。
TCP 三次握手
在客戶端傳送資料之前會發起 TCP 三次握手用以同步客戶端和服務端的序列號和確認號,並交換 TCP 視窗大小資訊。
TCP 三次握手的過程如下:
- 客戶端傳送一個帶 SYN=1,Seq=X 的資料包到伺服器埠(第一次握手,由瀏覽器發起,告訴伺服器我要傳送請求了)
- 伺服器發回一個帶 SYN=1, ACK=X+1, Seq=Y 的響應包以示傳達確認資訊(第二次握手,由伺服器發起,告訴瀏覽器我準備接受了,你趕緊傳送吧)
- 客戶端再回傳一個帶 ACK=Y+1, Seq=Z 的資料包,代表“握手結束”(第三次握手,由瀏覽器傳送,告訴伺服器,我馬上就發了,準備接受吧)
為啥需要三次握手
- 為了防止已失效的連線請求報文段突然又傳送到了服務端,因而產生錯誤
傳送 HTTP 請求
TCP 三次握手結束後,開始傳送 HTTP 請求報文。
其實這部分又可以稱為前端工程師眼中的HTTP,它主要發生在客戶端。傳送HTTP請求的過程就是構建HTTP請求報文並通過TCP協議傳送到伺服器指定埠(HTTP協議80/8080, HTTPS協議443)。
請求報文由請求行(request line)、請求頭(header)、請求空行(blank line)、請求體四個部分組成, 如下圖所示:
請求行包含請求方法、URL、協議版本
- 請求方法包含 8 種:GET、POST、PUT、DELETE、CONNECT、HEAD、OPTIONS、TRACE
- URL 即請求地址,由 <協議>://<主機>:<埠>/<路徑>?<引數> 組成
- 協議版本即 http 版本號
POST /chapter17/user.html HTTP/1.1
複製程式碼
以上程式碼中“POST”代表請求方法,“/chapter17/user.html”表示 URL,“HTTP/1.1”代表協議和協議的版本。現在比較流行的是 Http1.1 版本
請求頭包含請求的附加資訊,由關鍵字/值對組成,每行一對,關鍵字和值用英文冒號“:”分隔。
請求頭部通知伺服器有關於客戶端請求的資訊。它包含許多有關的客戶端環境
和請求正文的有用資訊
。其中比如:Host,表示主機名,虛擬主機;Connection,HTTP/1.1 增加的,使用 keepalive,即持久連線,一個連線可以發多個請求;User-Agent,請求發出者,相容性以及定製化需求。
請求報頭允許客戶端向伺服器傳遞請求的附加資訊和客戶端自身的資訊。 PS: 客戶端不一定特指瀏覽器,有時候也可使用Linux下的CURL命令以及HTTP客戶端測試工具等。 常見的請求報頭有: Accept, Accept-Charset, Accept-Encoding, Accept-Language, Content-Type, Authorization, Cookie, User-Agent等。
上圖是使用Chrome開發者工具擷取的對百度的HTTP請求以及響應報文,從圖中可以看出,請求報頭中使用了Accept, Accept-Encoding, Accept-Language, Cache-Control, Connection, Cookie等欄位。Accept用於指定客戶端用於接受哪些型別的資訊,Accept-Encoding與Accept類似,它用於指定接受的編碼方式。Connection設定為Keep-alive用於告訴客戶端本次HTTP請求結束之後並不需要關閉TCP連線,這樣可以使下次HTTP請求使用相同的TCP通道,節省TCP連線建立的時間。
請求體,可以承載多個請求引數的資料,包含回車符、換行符和請求資料,並不是所有請求都具有請求資料。
當使用POST, PUT等方法時,通常需要客戶端向伺服器傳遞資料。這些資料就儲存在請求正文中。在請求頭中有一些與請求正文相關的資訊,例如: 現在的Web應用通常採用Rest架構,請求的資料格式一般為json。這時就需要設定Content-Type: application/json。
name=tom&password=1234&realName=tomson
複製程式碼
上面程式碼,承載著 name、password、realName 三個請求引數。
伺服器處理請求並返回 HTTP 報文
伺服器
伺服器是網路環境中的高效能運算機,它偵聽網路上的其他計算機(客戶機)提交的服務請求,並提供相應的服務,比如網頁服務、檔案下載服務、郵件服務、視訊服務。而客戶端主要的功能是瀏覽網頁、看視訊、聽音樂等等,兩者截然不同。 每臺伺服器上都會安裝處理請求的應用——web server。常見的 web server 產品有 apache、nginx、IIS 或 Lighttpd 等。 web server 擔任管控的角色,對於不同使用者傳送的請求,會結合配置檔案,把不同請求委託給伺服器上處理相應請求的程式進行處理(例如 CGI 指令碼,JSP 指令碼,servlets,ASP 指令碼,伺服器端 JavaScript,或者一些其它的伺服器端技術等),然後返回後臺程式處理產生的結果作為響應。
MVC 後臺處理階段
後臺開發現在有很多框架,但大部分都還是按照 MVC 設計模式進行搭建的。 MVC 是一個設計模式,將應用程式分成三個核心部件:模型(model)-- 檢視(view)--控制器(controller),它們各自處理自己的任務,實現輸入、處理和輸出的分離。
-
檢視(view)
它是提供給使用者的操作介面,是程式的外殼。
-
模型(model)
模型主要負責資料互動。 在 MVC 的三個部件中,模型擁有最多的處理任務。一個模型能為多個檢視提供資料。
-
控制器(controller)
它負責根據使用者從"檢視層"輸入的指令,選取"模型層"中的資料,然後對其進行相應的操作,產生最終結果。 控制器屬於管理者角色,從檢視接收請求並決定呼叫哪個模型構件去處理請求,然後再確定用哪個檢視來顯示模型處理返回的資料。 這三層是緊密聯絡在一起的,但又是互相獨立的,每一層內部的變化不影響其他層。每一層都對外提供介面(Interface),供上面一層呼叫。 至於這一階段發生什麼?簡而言之,首先瀏覽器傳送過來的請求先經過控制器,控制器進行邏輯處理和請求分發,接著會呼叫模型,這一階段模型會獲取 redis db 以及 MySQL 的資料,獲取資料後將渲染好的頁面,響應資訊會以響應報文的形式返回給客戶端,最後瀏覽器通過渲染引擎將網頁呈現在使用者面前。
http 響應報文
自然而然這部分對應的就是後端工程師眼中的HTTP。後端從在固定的埠接收到TCP報文開始,這一部分對應於程式語言中的socket。它會對TCP連線進行處理,對HTTP協議進行解析,並按照報文格式進一步封裝成HTTP Request物件,供上層使用。這一部分工作一般是由Web伺服器去進行,我使用過的Web伺服器有Tomcat, Jetty和Netty等等。
響應報文由響應行(request line)、響應頭部(header)、響應主體三個部分組成。 如下圖所示:
響應行包含:協議版本,狀態碼,狀態碼描述
http狀態碼
分類 | 分類描述 |
---|---|
1** | 資訊,伺服器收到請求,需要請求者繼續執行操作 |
2** | 成功,操作被成功接收並處理 |
3** | 重定向,需要進一步的操作以完成請求 |
4** | 客戶端錯誤,請求包含語法錯誤或無法完成請求 |
5** | 伺服器錯誤,伺服器在處理請求的過程中發生了錯誤 |
響應頭部包含響應報文的附加資訊,由 名/值 對組成
常見的響應報頭欄位有: Server, Connection...。
響應主體包含回車符、換行符和響應返回資料,並不是所有響應報文都有響應資料
伺服器返回給瀏覽器的文字資訊,通常HTML, CSS, JS, 圖片等檔案就放在這一部分。
瀏覽器解析渲染頁面
瀏覽器拿到index.html檔案後,就開始解析其中的html程式碼,遇到js/css/image等靜態資源時,就向伺服器端去請求下載(會使用多執行緒下載,每個瀏覽器的執行緒數不一樣),這個時候就用上keep-alive特性了,建立一次HTTP連線,可以請求多個資源,下載資源的順序就是按照程式碼裡的順序,但是由於每個資源大小不一樣,而瀏覽器又多執行緒請求請求資源,所以顯示的順序並不一定是程式碼裡面的順序。
如遇到影像,iconfont,JS等的請求時,由於請求過程是非同步的,並不會影響HTML文件進行載入,但是當文件載入過程中遇到JS檔案,HTML文件會掛起渲染過程,不僅要等到文件中JS檔案載入完畢還要等待解析執行完畢,才會繼續HTML的渲染過程。原因是因為JS有可能修改DOM結構,這就意味著JS執行完成前,後續所有資源的下載是沒有必要的,這就是JS阻塞後續資源下載的根本原因。CSS檔案的載入不影響JS檔案的載入,但是卻影響JS檔案的執行。JS程式碼執行前瀏覽器必須保證CSS檔案已經下載並載入完畢。
瀏覽器在請求靜態資源時(在未過期的情況下),向伺服器端發起一個http請求(詢問自從上一次修改時間到現在有沒有對資源進行修改),如果伺服器端返回304狀態碼(告訴瀏覽器伺服器端沒有修改),那麼瀏覽器會直接讀取本地的該資源的快取檔案。
接下來看一下瀏覽器渲染頁面的過程,如下圖:
瀏覽器解析渲染頁面分為一下五個步驟:
- 根據 HTML 解析出 DOM 樹
- 根據 CSS 解析生成 CSS 規則樹
- 結合 DOM 樹和 CSS 規則樹,生成渲染樹
- 根據渲染樹計算每一個節點的資訊
- 根據計算好的資訊繪製頁面
瀏覽器是一個邊解析邊渲染的過程。首先瀏覽器解析HTML檔案構建DOM樹,然後解析CSS檔案構建渲染樹,等到渲染樹構建完成後,瀏覽器開始佈局渲染樹並將其繪製到螢幕上。這個過程比較複雜,涉及到兩個概念: reflow(迴流)和repain(重繪)。DOM節點中的各個元素都是以盒模型的形式存在,這些都需要瀏覽器去計算其位置和大小等,這個過程稱為relow;當盒模型的位置,大小以及其他屬性,如顏色,字型,等確定下來之後,瀏覽器便開始繪製內容,這個過程稱為repain。頁面在首次載入時必然會經歷reflow和repain。reflow和repain過程是非常消耗效能的,尤其是在移動裝置上,它會破壞使用者體驗,有時會造成頁面卡頓。所以我們應該儘可能少的減少reflow和repain。
根據 HTML 解析 DOM 樹
- 根據 HTML 的內容,將標籤按照結構解析成為 DOM 樹,DOM 樹解析的過程是一個深度優先遍歷。即先構建當前節點的所有子節點,再構建下一個兄弟節點。
- 在讀取 HTML 文件,構建 DOM 樹的過程中,若遇到 script 標籤,則 DOM 樹的構建會暫停,直至指令碼執行完畢。
根據 CSS 解析生成 CSS 規則樹
- 解析 CSS 規則樹時 js 執行將暫停,直至 CSS 規則樹就緒。
- 瀏覽器在 CSS 規則樹生成之前不會進行渲染。
結合 DOM 樹和 CSS 規則樹,生成渲染樹
- DOM 樹和 CSS 規則樹全部準備好了以後,瀏覽器才會開始構建渲染樹。
- 精簡 CSS 並可以加快 CSS 規則樹的構建,從而加快頁面相應速度。
根據渲染樹計算每一個節點的資訊(佈局)
- 佈局:通過渲染樹中渲染物件的資訊,計算出每一個渲染物件的位置和尺寸
- 迴流:在佈局完成後,發現了某個部分發生了變化影響了佈局,那就需要倒回去重新渲染。
根據計算好的資訊繪製頁面
- 繪製階段,系統會遍歷呈現樹,並呼叫呈現器的“paint”方法,將呈現器的內容顯示在螢幕上。
- 重繪:某個元素的背景顏色,文字顏色等,不影響元素周圍或內部佈局的屬性,將只會引起瀏覽器的重繪。
- 迴流:某個元素的尺寸發生了變化,則需重新計算渲染樹,重新渲染。
詳細的瀏覽器原理:請戳這裡
斷開連線
當資料傳送完畢,需要斷開 tcp 連線,此時發起 tcp 四次揮手。
- 發起方向被動方傳送報文,Fin、Ack、Seq,表示已經沒有資料傳輸了。並進入 FIN_WAIT_1 狀態。(第一次揮手:由瀏覽器發起的,傳送給伺服器,我請求報文傳送完了,你準備關閉吧)
- 被動方傳送報文,Ack、Seq,表示同意關閉請求。此時主機發起方進入 FIN_WAIT_2 狀態。(第二次揮手:由伺服器發起的,告訴瀏覽器,我請求報文接受完了,我準備關閉了,你也準備吧)
- 被動方向發起方傳送報文段,Fin、Ack、Seq,請求關閉連線。並進入 LAST_ACK 狀態。(第三次揮手:由伺服器發起,告訴瀏覽器,我響應報文傳送完了,你準備關閉吧)
- 發起方向被動方傳送報文段,Ack、Seq。然後進入等待 TIME_WAIT 狀態。被動方收到發起方的報文段以後關閉連線。發起方等待一定時間未收到回覆,則正常關閉。(第四次揮手:由瀏覽器發起,告訴伺服器,我響應報文接受完了,我準備關閉了,你也準備吧)
Web優化
上面部分主要介紹了一次完整的請求對應的過程,瞭解該過程的目的無非就是為了Web優化。在談到Web優化之前,我們回到一個更原始的問題,Web前端的本質是什麼。我的理解是: 將資訊快速並友好的展示給使用者並能夠與使用者進行互動。快速的意思就是在儘可能短的時間內完成頁面的載入,試想一下當你在淘寶購買東西的時候,淘寶頁面載入了10幾秒才顯示出物品,這個時候你還有心情去購買嗎?怎麼快速的完成頁面的載入呢?優雅的學院派雅虎給出了常用的一些手段,也就是我們熟悉的雅虎34條軍規。這34軍規實際上就是圍繞請求過程進行的一些優化方式。
如何儘快的載入資源?答案就是能不從網路中載入的資源就不從網路中載入,當我們合理使用快取,將資源放在瀏覽器端,這是最快的方式。如果資源必須從網路中載入,則要考慮縮短連線時間,即DNS優化部分;減少響應內容大小,即對內容進行壓縮。另一方面,如果載入的資源數比較少的話,也可以快速的響應使用者。當資源到達瀏覽器之後,瀏覽器開始進行解析渲染,瀏覽器中最耗時的部分就是reflow,所以圍繞這一部分就是考慮如何減少reflow的次數。
給大家推薦一個好用的BUG監控工具Fundebug,歡迎免費試用!