從輸入URL到頁面加載發生了什麼?

本文總結自:

面試時,總是避不開一個問題,在瀏覽器中輸入URL到整個頁面顯示在用戶面前時這個過程中到底發生了什麼。

總體來說分爲以下幾個過程:
DNS解析
TCP連接
發送HTTP請求
服務器處理請求並返回HTTP報文
瀏覽器解析渲染頁面
連接結束


DNS解析
DNS解析的過程就是尋找哪臺機器上有你需要資源的過程。一個網址到IP地址的轉換,這個過程就是DNS解析。它實際上充當了一個翻譯的角色,實現了網址到IP地址的轉換。

解析過程
解析過程如下,這是一個遞歸的過程。




上述圖片是查找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存在着多級緩存,從離瀏覽器的距離排序的話,有以下幾種: 瀏覽器緩存,系統緩存,路由器緩存,IPS服務器緩存,根域名服務器緩存,頂級域名服務器緩存,主域名服務器緩存。

提示:
在你的chrome瀏覽器中輸入:chrome://dns/,你可以看到chrome瀏覽器的DNS緩存。
在你的linux中,存在/etc/hosts文件

 

DNS負載均衡
不知道大家有沒有思考過一個問題: DNS返回的IP地址是否每次都一樣?如果每次都一樣是否說明你請求的資源都位於同一臺機器上面,那麼這臺機器需要多高的性能和儲存才能滿足億萬請求呢?其實真實的互聯網世界背後存在成千上百臺服務器,大型的網站甚至更多。但是在用戶的眼中,它需要的只是處理他的請求,哪臺機器處理請求並不重要。DNS可以返回一個合適的機器的IP給用戶,例如可以根據每臺機器的負載量,該機器離用戶地理位置的距離等等,這種過程就是DNS負載均衡,又叫做DNS重定向。大家耳熟能詳的CDN(Content Delivery Network)就是利用DNS的重定向技術,DNS服務器會返回一個跟用戶最接近的點的IP地址給用戶,CDN節點的服務器負責響應用戶的請求,提供所需的內容。



TCP連接

建立TCP連接中,最關鍵的地方就是TCP建立連接時三次握手,當然TCP協議中斷開連接設計到的四次揮手也很重要,但斷開連接是後話了。

三次握手示意圖



簡述三次握手

第一次握手
client發送一個SYN(J)包給server,然後等待server的ACK回覆,進入SYN-SENT狀態。p.s: SYN爲synchronize的縮寫,ACK爲acknowledgment的縮寫。

第二次握手
server接收到SYN(seq=J)包後就返回一個ACK(J+1)包以及一個自己的**SYN(K)**包,然後等待client的ACK回覆,server進入SYN-RECIVED狀態。

第三次握手
client接收到server發回的ACK(J+1)包後,進入ESTABLISHED狀態。然後根據server發來的SYN(K)包,返回給等待中的server一個ACK(K+1)包。等待中的server收到ACK回覆,也把自己的狀態設置爲ESTABLISHED。到此TCP三次握手完成,client與server可以正常進行通信了。


爲什麼要進行三次握手

謝希仁版《計算機網絡》中的例子是這樣的,“已失效的連接請求報文段”的產生在這樣一種情況下:client發出的第一個連接請求報文段並沒有丟失,而是在某個網絡結點長時間的滯留了,以致延誤到連接釋放以後的某個時間纔到達server。本來這是一個早已失效的報文段。但server收到此失效的連接請求報文段後,就誤認爲是client再次發出的一個新的連接請求。於是就向client發出確認報文段,同意建立連接。假設不採用“三次握手”,那麼只要server發出確認,新的連接就建立了。由於現在client並沒有發出建立連接的請求,因此不會理睬server的確認,也不會向server發送數據。但server卻以爲新的運輸連接已經建立,並一直等待client發來數據。這樣,server的很多資源就白白浪費掉了。採用“三次握手”的辦法可以防止上述現象發生。例如剛纔那種情況,client不會向server的確認發出確認。server由於收不到確認,就知道client並沒有要求建立連接。”



HTTP請求

其實這部分又可以稱爲前端工程師眼中的HTTP,它主要發生在客戶端。發送HTTP請求的過程就是構建HTTP請求報文並通過TCP協議中發送到服務器指定端口(HTTP協議80/8080, HTTPS協議443)。HTTP請求報文是由三部分組成: 請求行, 請求報頭和請求正文。



請求行
格式如下:
Method Request-URL HTTP-Version CRLF
  1. eg: GET index.html HTTP/1.1
常用的方法有: GET, POST, PUT, DELETE, OPTIONS, HEAD。這方面的知識,又可以設計到RESTful風格接口,GET等方法作用等。


請求報頭
請求報頭,也就是請求頭部,允許客戶端向服務器傳遞請求的附加信息和客戶端自身的信息。
PS: 客戶端不一定特指瀏覽器,有時候也可使用Linux下的CURL命令以及HTTP客戶端測試工具等。
常見的請求報頭有: Accept, Accept-Charset, Accept-Encoding, Accept-Language, Content-Type, Authorization, Cookie, User-Agent等。


請求報頭中使用了Accept, Accept-Encoding, Accept-Language, Cache-Control, Connection, Cookie等字段。Accept用於指定客戶端用於接受哪些類型的信息,Accept-Encoding與Accept類似,它用於指定接受的編碼方式。Connection設置爲Keep-alive用於告訴客戶端本次HTTP請求結束之後並不需要關閉TCP連接,這樣可以使下次HTTP請求使用相同的TCP通道,節省TCP連接建立的時間。


請求正文
這是可選部分,當我們使用GET方法請求時,就不會有請求正文,當使用POST, PUT等方法時,通常需要客戶端向服務器傳遞數據。這些數據就儲存在請求正文中。在請求包頭中有一些與請求正文相關的信息,例如: 現在的Web應用通常採用Rest架構,請求的數據格式一般爲json。這時就需要設置Content-Type: application/json。



服務器處理請求並返回HTTP報文

自然而然這部分對應的就是後端工程師眼中的HTTP。後端從在固定的端口接收到TCP報文開始,這一部分對應於編程語言中的socket。它會對TCP連接進行處理,對HTTP協議進行解析,並按照報文格式進一步封裝成HTTP Request對象,供上層使用。這一部分工作一般是由Web服務器去進行,我使用過的Web服務器有Tomcat, Jetty和Netty等等。

HTTP響應報文也是由三部分組成: 狀態碼, 響應報頭和響應報文。

狀態碼
狀態碼是由3位數組成,第一個數字定義了響應的類別,且有五種可能取值:

1xx:指示信息–表示請求已接收,繼續處理。
2xx:成功–表示請求已被成功接收、理解、接受。
3xx:重定向–要完成請求必須進行更進一步的操作。
4xx:客戶端錯誤–請求有語法錯誤或請求無法實現。
5xx:服務器端錯誤–服務器未能實現合法的請求。
平時遇到比較常見的狀態碼有:200, 204, 301, 302, 304, 400, 401, 403, 404, 422, 500(分別表示什麼請自行查找)。

響應報頭
與請求頭部類似,爲響應報文添加了一些附加信息。

常見響應頭部如下:

響應頭

說明

Server

服務器應用程序軟件的名稱和版本

Content-Type

響應正文的類型(是圖片還是二進制字符串)

Content-Length

響應正文長度

Content-Charset

響應正文使用的編碼

Content-Encoding

響應正文使用的數據壓縮格式

Content-Language

響應正文使用的語言

響應頭部示例
 

響應報文
服務器返回給瀏覽器的文本信息,通常HTML, CSS, JS, 圖片等文件就放在這一部分。



瀏覽器解析渲染頁面
瀏覽器在收到HTML,CSS,JS文件後,它是如何把頁面呈現到屏幕上的?下圖對應的就是WebKit渲染的過程。



瀏覽器是一個邊解析邊渲染的過程。首先瀏覽器解析HTML文件構建DOM樹,然後解析CSS文件構建渲染樹,等到渲染樹構建完成後,瀏覽器開始佈局渲染樹並將其繪製到屏幕上。這個過程比較複雜,涉及到兩個概念: reflow(迴流)和repain(重繪)。DOM節點中的各個元素都是以盒模型的形式存在,這些都需要瀏覽器去計算其位置和大小等,這個過程稱爲relow;當盒模型的位置,大小以及其他屬性,如顏色,字體,等確定下來之後,瀏覽器便開始繪製內容,這個過程稱爲repain。頁面在首次加載時必然會經歷reflow和repain。reflow和repain過程是非常消耗性能的,尤其是在移動設備上,它會破壞用戶體驗,有時會造成頁面卡頓。所以我們應該儘可能少的減少reflow和repain。




JS的解析是由瀏覽器中的JS解析引擎完成的。JS是單線程運行,也就是說,在同一個時間內只能做一件事,所有的任務都需要排隊,前一個任務結束,後一個任務才能開始。但是又存在某些任務比較耗時,如IO讀寫等,所以需要一種機制可以先執行排在後面的任務,這就是:同步任務(synchronous)和異步任務(asynchronous)。JS的執行機制就可以看做是一個主線程加上一個任務隊列(task queue)。同步任務就是放在主線程上執行的任務,異步任務是放在任務隊列中的任務。所有的同步任務在主線程上執行,形成一個執行棧;異步任務有了運行結果就會在任務隊列中放置一個事件;腳本運行時先依次運行執行棧,然後會從任務隊列裏提取事件,運行任務隊列中的任務,這個過程是不斷重複的,所以又叫做事件循環(Event loop)。

瀏覽器在解析過程中,如果遇到請求外部資源時,如圖像,iconfont,JS等。瀏覽器將重複1-6過程下載該資源。請求過程是異步的,並不會影響HTML文檔進行加載,但是當文檔加載過程中遇到JS文件,HTML文檔會掛起渲染過程,不僅要等到文檔中JS文件加載完畢還要等待解析執行完畢,纔會繼續HTML的渲染過程。原因是因爲JS有可能修改DOM結構,這就意味着JS執行完成前,後續所有資源的下載是沒有必要的,這就是JS阻塞後續資源下載的根本原因。CSS文件的加載不影響JS文件的加載,但是卻影響JS文件的執行。JS代碼執行前瀏覽器必須保證CSS文件已經下載並加載完畢。



連接結束
此過程中,最重要的是TCP連接斷開的四次揮手。



第一次揮手
client發送一個FIN(M)包,此時client進入FIN-WAIT-1狀態,這表明client已經沒有數據要發送了。

第二次揮手
server收到了client發來的FIN(M)包後,向client發回一個ACK(M+1)包,此時server進入CLOSE-WAIT狀態,client進入FIN-WAIT-2狀態。

第三次揮手
server向client發送FIN(N)包,請求關閉連接,同時server進入LAST-ACK狀態。

第四次揮手
client收到server發送的FIN(N)包,進入TIME-WAIT狀態。向server發送**ACK(N+1)**包,server收到client的ACK(N+1)包以後,進入CLOSE狀態;client等待一段時間還沒有得到回覆後判斷server已正式關閉,進入CLOSE狀態。


爲什麼TCP斷開連接是四次揮手
TCP有個半關閉狀態,假設A.B要釋放連接,那麼A發送一個釋放連接報文給B,B收到後發送確認,這個時候A不發數據,但是B如果發數據A還是要接受,這叫半關閉。也就是,第二次揮手後,進入半關閉狀態。這時候server還可以向client發送數據,所以如果要完全斷開連接,那麼server需要再向client發送關閉連接的請求。因此就有後面兩次揮手。




參考:


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章