從輸入url到頁面展示發生了什麼?

從用戶的角度出發,得益於各大瀏覽器廠商的不懈努力,這一切都顯得已經很理所固然,輸入一個地址,訪問網絡,顯示一個絢麗多彩的界面,你能夠能夠在上面瀏覽視頻,看文章,甚至玩遊戲。
可是站在開發者的角度,這是一個縱觀全局的大問題,每個步驟都是一個能夠延伸的話題。對於項目的優化都離不開這裏的方方面面,是有深刻理解的價值的。咱們能夠從一個總覽出發,看看背後發生了什麼。

大體是以下步驟

  1. 根據地址欄輸入的地址向DNS(Domain Name System)查詢IP
  2. 經過IP向服務器發起TCP鏈接
  3. 向服務器發起請求
  4. 服務器返回請求內容
  5. 瀏覽器開始解析渲染頁面並顯示
  6. 關閉鏈接

clipboard.png


一.DNS

首先咱們要知道什麼是DNSweb

域名系統(英文:Domain Name System,縮寫:DNS)是互聯網的一項服務。它做爲將域名和IP地址相互映射的一個分佈式數據庫,可以令人更方便地訪問互聯網。DNS使用TCP和UDP端口53。當前,對於每一級域名長度的限制是63個字符,域名總長度則不能超過253個字符。 --維基百科

域名解析的過程是逐級查詢的chrome

  1. 瀏覽器緩存: 首先會向瀏覽器的緩存中讀取上一次訪問的記錄,在chrome能夠經過地址欄中輸入chrome://net-internals/#dns查看緩存的當前狀態
  2. 操做系統緩存:查找存儲在系統運行內存中的緩存。在mac中能夠經過下面的命令清除系統中的DNS緩存。
dscacheutil -flushcache
  1. 在host文件中查找:若是在緩存中都查找不到的狀況下,就會讀取系統中預設的host文件中的設置。
  2. 路由器緩存:有些路由器也有DNS緩存的功能,訪問過的域名會存在路由器上。
  3. ISP DNS緩存:互聯網服務提供商(如中國電信)也會提供DNS服務,好比比較著名的 114.114.114.114,在本地查找不到的狀況下,就會向ISP進行查詢,ISP會在當前服務器的緩存內查找是否有記錄,若是有,則返回這個IP,若沒有,則會開始向根域名服務器請求查詢。
  4. 頂級DNS服務器/根DNS服務器:根域名收到請求後,會判別這個域名(.com)是受權給哪臺服務器管理,並返回這個頂級DNS服務器的IP。請求者收到這臺頂級DNS的服務器IP後,會向該服務器發起查詢,若是該服務器沒法解析,該服務器就會返回下一級的DNS服務器IP(nicefilm.com),本機繼續查找,直到服務器找到(www.nicefilm.com)的主機。

咱們能夠經過dig命令查看域名解析的記錄數據庫

dig math.stackexchange.com

咱們重點看返回的應答,會看到有四條記錄,返回了該網址的四個IPsegmentfault

;; ANSWER SECTION:
math.stackexchange.com.    31    IN    A    151.101.1.69
math.stackexchange.com.    31    IN    A    151.101.129.69
math.stackexchange.com.    31    IN    A    151.101.193.69
math.stackexchange.com.    31    IN    A    151.101.65.69

31是TTL的值,表示該域名的緩存時間,即該時間內不用從新查詢。A是該DNS查詢的記錄類型,表示返回一個IPv4格式的地址。還有其餘記錄類型諸如 NS(返回查詢的服務器地址)、AAAA(返回IPV6格式的地址)、CNAME(域名的別名)等。瀏覽器


二.TCP 鏈接

拿到了要請求的資源服務器IP後,瀏覽器經過操做OS的socket與服務器進行TCP鏈接(通常來講操做系統已經封裝好了TCP/IP等協議,提供套接字給應用去使用,該部分涉及到標準網絡模型的知識,另外再開篇拓展。)緩存

這個鏈接就是咱們所熟知的三次握手
本機主動打開鏈接服務器

  • 第一次,本機將標識位 SYN 置爲 1, seq = x(Sequence number)發送給服務端。此時本機狀態爲SYN-SENT
  • 第二次,服務器收到包以後,將狀態切換爲SYN-RECEIVED,並將標識位 SYN 和 ACK都置爲1, seq = y, ack = x + 1, 併發送給客戶端。
  • 第三次,客戶端收到包後,將狀態切換爲ESTABLISHED,並將標識位ACK置爲1,seq = x + 1, ack = y + 1, 併發送給服務端。服務端收到包以後,也將狀態切換爲ESTABLISHED

須要注意的一點是,有一些文章對ACK標識位 和 ack(Acknowledgement Number)的解釋比較模糊,有一些畫圖的時候乾脆就寫在一塊兒了。雖然這二者有關聯,但不是同一個東西,搞清楚這個誤區能夠更方便去理解。還有一些會把第二次握手描述成兩個包(好比某百科……),實際上這也是不正確的網絡

  • 標識位ACK置爲1 表示我已確認收到seq爲x的包,並回復確認序號ack = x + 1
  • 而SYN表示這是我第一次隨機生成seq的序列x,此後我每次發送的包都會在上一次發送的基礎上增長y(有數據的時候,y是數據的長度,沒有的時候y = 1)。因此,當seq已初始化完成以後,不必再把SYN置爲1

理解了這兩點,也就不難理解爲何三次握手分別是SYN、ACK/SYN、ACK了。併發

標識位(TCP FLAG)

TCP的頭部固定有20個字節,其中分配了6bits給TCP FLAG,組合起來用來表示當前包的類型。分別是
URGACKPSHRSTSYNFIN(CWRECE放在保留位,暫不考慮)socket

  • URG:緊急指針,用於將要發送的包標識爲「緊急」,這意味着沒必要等待前段數據被響應處理完便可發送給接收端。
  • ACK:確認標識,用於表示對數據包的成功接收。
  • PSH:推送標識,表示這個數據包應該被當即發送,不須要等待額外的數據。
  • RST:reset標識,用來異常關閉鏈接。
  • SYN:同步標識,表示TCP鏈接已初始化。
  • FIN:完成標識,用於拆除上一個SYN標識。一個完整的TCP鏈接過程必定會有SYN 和 FIN包。

至此咱們瞭解了一個TCP 鏈接的過程,通道通了,是時候利用這個通道送東西了。
咱們從傳輸層再回到應用層。


三.HTTP請求與響應

超文本傳輸協議(英文:HyperText Transfer Protocol,縮寫:HTTP)是一種用於分佈式、協做式和超媒體信息系統的應用層協議[1]。HTTP是萬維網的數據通訊的基礎。設計HTTP最初的目的是爲了提供一種發佈和接收HTML頁面的方法。經過HTTP或者HTTPS協議請求的資源由統一資源標識符(Uniform Resource Identifiers,URI)來標識。 --wiki

咱們用 https://www.segmentfault.com 舉例子。
在應用層,瀏覽器會分析這個url,並設置好請求報文發出。請求報文中包括請求行、請求頭、空行、請求主體。https默認請求端口443, http默認80。

  • 請求行:請求行中包括請求的方法,路徑和協議版本。
  • 請求頭:請求頭中包含了請求的一些附加的信息,通常是以鍵值的形式成對存在,好比設置請求文件的類型accept-type,以及服務器對緩存的設置。
  • 空行:協議中規定請求頭和請求主體間必須用一個空行隔開
  • 請求主體:對於post請求,所須要的參數都不會放在url中,這時候就須要一個載體了,這個載體就是請求主題。

服務端收到請求以後,會根據url匹配到的路徑作相應的處理,最後返回瀏覽器須要的頁面資源。瀏覽器會收到一個響應報文,而所須要的資源就就在報文主體上。與請求報文相同,響應報文也有與之對應的起始行、首部、空行、報文主體,不一樣的地方在於包含的東西不同。

  • 響應行:響應報文的起始行一樣包含了協議版本,與請求的起始行不一樣的是其包含的還有狀態碼和狀態碼的緣由短語。
  • 響應頭:對應請求報文中的請求頭,格式一致,可是各自有不一樣的首部。也有一塊兒用的通用首部。
  • 空行
  • 報文主體:請求所須要的資源。
http緩存

請求是瀏覽器的一個優化點,咱們能夠經過緩存來減小沒必要要的請求,進而加快頁面的呈現。經過簡單地設置http頭部可使用緩存的功能。通常來講有三種設置的方式

Last-Modify(響應頭) + If-Modified-Since(請求頭)

服務器在返回資源的時候設置Last-Modify當前資源最後一次修改的時間,瀏覽器會把這個時間保存下來,在下次請求的時候,請求頭部If-Modified-Since 會包含這個時間,服務端收到請求後,會比對資源最後更新的時間是否在If-Modified-Since設置的時間以後,若是不是,返回304狀態碼,瀏覽器將從緩存中獲取資源。反之返回200和資源內容

ETag(響應頭) + If-None-Match(請求頭)

根據資源標識符來肯定文件是否存在修改,服務器每一次返回資源,都會在Etag中存放資源的標識符,瀏覽器收到這個標識符,在下一次請求的時候將標識符放在If-None-Match中,服務端將判斷是否匹配,若是不匹配,返回200以及新的資源,反之返回304,瀏覽器從緩存中獲取資源

Cache-Control/Expires(響應頭)

首先這不是一種方法,而是協議更替中的一種演化。
在http 1.0的時代,咱們基於Pragma 和 Expires 控制緩存的生命週期。咱們能夠經過設置Pragma爲no-cache關閉緩存功能,一樣也能夠在Expires中設置一個緩存失效的時間。須要注意的是,這個失效的時間是相對於服務器的實踐而言的,若是人爲地改變了客戶端的時間,是會致使緩存失效的。

因此,爲了解決這個問題,HTTP1.1的協議加入了Cache-Control,經過設置Cache-Control的max-age能夠控制緩存的週期。在這個週期內,資源是新鮮的,瀏覽器再一次須要使用資源的時候,就不會發出請求了。


四.頁面呈現

至此瀏覽器已經拿到了一個HTML文檔,併爲了呈現文檔而開始解析。呈現引擎開始工做,基本流程以下(以webkit爲例)

  • 經過HTML解析器解析HTML文檔,構建一個DOM Tree,同時經過CSS解析器解析HTML中存在的CSS,構建Style Rules,二者結合造成一個Attachment。
  • 經過Attachment構造出一個呈現樹(Render Tree)
  • Render Tree構建完畢,進入到佈局階段(layout/reflow),將會爲每一個階段分配一個應出如今屏幕上的確切座標。
  • 最後將所有的節點遍歷繪製出來後,一個頁面就展示出來了。

從構建DOM樹到呈現的過程以下

op=>operation: Parsing HTML to construct the DOM tree
op1=>operation: Render Tree construction
op2=>operation: Layout of the Render Tree
op3=>operation: Painting the Render Tree
op->op1->op2->op3

須要注意的是,這是一個漸進的過程,呈現引擎爲了力求顯示的及時,會在文檔請求不徹底的狀況下就開始渲染頁面,同時,若是在解析的過程當中遇到script的時候,文檔的解析將會中止下來,當即解析執行腳本,若是腳本是外部的,則會等待請求完成並解析執行。因此,爲了避免阻塞頁面地呈現,通常會把script腳本放在文檔的最後。

在最新的HTML4和HTML5規範中,也能夠將腳本標註爲defer,這樣就不會中止文檔解析,而是等到解析結束後才執行。HTML5 增長了一個選項,可將腳本標記爲async,以便由其餘線程解析和執行。


五. 鏈接關閉

如今的頁面爲了優化請求的耗時,默認都會開啓持久鏈接(keep-alive),那麼一個TCP鏈接確切關閉的時機,是這個tab標籤頁關閉的時候。這個關閉的過程就是著名的四次揮手。關閉是一個全雙工的過程,發包的順序的不必定的。通常來講是客戶端主動發起的關閉,過程以下。

假如最後一次客戶端發出的數據seq = x, ack = y;

  1. 客戶端發送一個FIN置爲1的包,ack = y, seq = x + 1,此時客戶端的狀態爲 FIN_WAIT_1
  2. 服務端收到包後,狀態切換爲CLOSE_WAIT發送一個ACK爲1的包, ack = x + 2。客戶端收到包以後狀態切換爲FNI_WAIT_2
  3. 服務端處理完任務後,向客戶端發送一個 FIN包,seq = y; 同時將本身的狀態置爲LAST_ACK
  4. 客戶端收到包後狀態切換爲TIME_WAIT,並向服務端發送ACK包,ack = y + 1,等待2MSL後關閉鏈接。
爲何客戶端等待2MSL?

MSL: 全程Maximum Segment Lifetime,中文能夠翻譯爲報文最大生存時間。等待是爲了保證鏈接的可靠性,確保服務端收到ACK包,若是服務端沒有收到這個ACK包,將會重發FIN包給客戶端,而這個時間恰好是服務端等待超時重發的時間 + FIN的傳輸時間。

相關文章
相關標籤/搜索