1. 程式人生 > >從輸入URL到頁面載入完的過程中都發生了什麼事情?

從輸入URL到頁面載入完的過程中都發生了什麼事情?

以下是一個大概流程:

(1) 瀏覽器獲取輸入的域名www.google.com

(2) 瀏覽器向DNS請求解析www.google.com的IP地址

(3) 域名系統DNS解析出百度伺服器的IP地址

(4) 瀏覽器與該伺服器建立TCP連線(預設埠號80)

(5) 瀏覽器發出HTTP請求,請求google首頁

(6) 伺服器通過HTTP響應把首頁檔案傳送給瀏覽器

(7) TCP連線釋放

(8) 瀏覽器將首頁檔案進行解析,並將Web頁顯示給使用者。

涉及到的協議

(1) 應用層:HTTP(WWW訪問協議),DNS(域名解析服務)

(2) 傳輸層:TCP(為HTTP提供可靠的資料傳輸),UDP(DNS使用UDP傳輸)

(3) 網路層:IP(IP資料資料包傳輸和路由選擇),ICMP(提供網路傳輸過程中的差錯檢測),ARP(將本機的預設閘道器IP地址對映成物理MAC地址)

具體過程       

1.DNS解析

解析過程

1.DNS的解析過程:
第一步:客戶機提出域名解析請求,並將該請求傳送給本地的域名伺服器。

第二步:當本地的域名伺服器收到請求後,就先查詢本地的快取,如果有該紀錄項,則本地的域名伺服器就直接把查詢的結果返回。

第三步:如果本地的快取中沒有該紀錄,則本地域名伺服器就直接把請求發給根域名伺服器,然後根域名伺服器再返回給本地域名伺服器一個所查詢域(根的子域)的主域名伺服器的地址。

第四步:本地伺服器再向上一步返回的域名伺服器傳送請求,然後接受請求的伺服器查詢自己的快取,如果沒有該紀錄,則返回相關的下級的域名伺服器的地址。

第五步:重複第四步,直到找到正確的紀錄。

DNS解析是一個遞迴查詢的過程。

上述圖片是查詢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快取(快取時間比較短,大概只有1分鐘,且只能容納1000條快取),看自身的快取中是否有www.zipackage.com 對應的條目,而且沒有過期,如果有且沒有過期則解析到此結束。

② 如果瀏覽器自身的快取裡面沒有找到對應的條目,那麼瀏覽器會搜尋作業系統自身的DNS快取,如果找到且沒有過期則停止搜尋解析到此結束.

③ 如果在Windows系統的DNS快取也沒有找到,那麼嘗試讀取hosts檔案(位於C:\Windows\System32\drivers\etc),看看這裡面有沒有該域名對應的IP地址,如果有則解析成功。

④ 如果在hosts檔案中也沒有找到對應的條目,瀏覽器就會發起一個DNS的系統呼叫,就會向本地配置的首選DNS伺服器(一般是電信運營商提供的)發起域名解析請求(通過的是UDP協議向DNS的53埠發起請求,這個請求是遞迴的請求,也就是運營商的DNS伺服器必須得提供給我們該域名的IP地址),運營商的DNS伺服器首先查詢自身的快取,找到對應的條目,且沒有過期,則解析成功。如果沒有找到對應的條目,則有運營商的DNS代我們的瀏覽器發起迭代DNS解析請求,它首先是會找根域的DNS的IP地址(這個DNS伺服器都內建13臺根域的DNS的IP地址),找到根域的DNS地址,就會向其發起請求(請問www.google.com這個域名的IP地址是多少啊?),根域發現這是一個頂級域com域的一個域名,於是就告訴運營商的DNS我不知道這個域名的IP地址,但是我知道com域的IP地址,你去找它去,於是運營商的DNS就得到了com域的IP地址,又向com域的IP地址發起了請求(請問www.google.com這個域名的IP地址是多少?),com域這臺伺服器告訴運營商的DNS我不知道www.google.com這個域名的IP地址,但是我知道google.com這個域的DNS地址,你去找它去,於是運營商的DNS又向google.com這個域名的DNS地址(這個一般就是由域名註冊商提供的,像萬網,新網等)發起請求(請問www.google.com這個域名的IP地址是多少?),這個時候google.com域的DNS伺服器一查,誒,果真在我這裡,於是就把找到的結果傳送給運營商的DNS伺服器,這個時候運營商的DNS伺服器就拿到了www.google.com這個域名對應的IP地址,並返回給Windows系統核心,核心又把結果返回給瀏覽器,終於瀏覽器拿到了www.google.com 對應的IP地址,該進行一步的動作了。

2.DNS優化

瞭解了DNS的過程,可以為我們帶來哪些?上文中請求到google的IP地址時,經歷了8個步驟,這個過程中存在多個請求(同時存在UDP和TCP請求,(DNS伺服器之間傳輸時使用TCP,而客戶端與DNS伺服器之間傳輸時用的是UDP)。如果每次都經過這麼多步驟,是否太耗時間?如何減少該過程的步驟呢?那就是DNS快取。

1)DNS快取

DNS存在著多級快取,從離瀏覽器的距離排序的話,有以下幾種: 瀏覽器快取,系統快取,路由器快取,IPS伺服器快取,根域名伺服器快取,頂級域名伺服器快取,主域名伺服器快取。

2)DNS負載均衡

DNS可以返回一個合適的機器的IP給使用者,例如可以根據每臺機器的負載量,該機器離使用者地理位置的距離等等,這種過程就是DNS負載均衡,又叫做DNS重定向。大家耳熟能詳的CDN(Content Delivery Network)就是利用DNS的重定向技術,DNS伺服器會返回一個跟使用者最接近的點的IP地址給使用者,CDN節點的伺服器負責響應使用者的請求,提供所需的內容

2.發起TCP的3次握手

拿到域名對應的IP地址之後,User-Agent(一般是指瀏覽器)會以一個隨機埠(1024 < 埠 < 65535)向伺服器的WEB程式(常用的有httpd,nginx等)80埠發起TCP的連線請求。這個連線請求(原始的http請求經過TCP/IP4層模型的層層封包)到達伺服器端後(這中間通過各種路由裝置,區域網內除外),進入到網絡卡,然後是進入到核心的TCP/IP協議棧(用於識別該連線請求,解封包,一層一層的剝開),還有可能要經過Netfilter防火牆(屬於核心的模組)的過濾,最終到達WEB程式

Client首先發送一個連線試探,ACK=0 表示確認號無效,SYN = 1 表示這是一個連線請求或連線接受報文,同時表示這個資料報不能攜帶資料,seq = x 表示Client自己的初始序號(seq = 0 就代表這是第0號幀),這時候Client進入syn_sent狀態,表示客戶端等待伺服器的回覆

2) Server監聽到連線請求報文後,如同意建立連線,則向Client傳送確認。TCP報文首部中的SYN 和 ACK都置1 ,ack = x + 1表示期望收到對方下一個報文段的第一個資料位元組序號是x+1,同時表明x為止的所有資料都已正確收到(ack=1其實是ack=0+1,也就是期望客戶端的第1個幀),seq = y 表示Server 自己的初始序號(seq=0就代表這是伺服器這邊發出的第0號幀)。這時伺服器進入syn_rcvd,表示伺服器已經收到Client的連線請求,等待client的確認。

3) Client收到確認後還需再次傳送確認,同時攜帶要傳送給Server的資料。ACK 置1 表示確認號ack= y + 1 有效(代表期望收到伺服器的第1個幀),Client自己的序號seq= x + 1(表示這就是我的第1個幀,相對於第0個幀來說的),一旦收到Client的確認之後,這個TCP連線就進入Established狀態,就可以發起http請求了。

3.建立TCP連線後發起http請求

進過TCP3次握手之後,瀏覽器發起了http的請求(第4幀),它主要發生在客戶端。傳送HTTP請求的過程就是構建HTTP請求報文並通過TCP協議中傳送到伺服器指定埠(HTTP協議80/8080, HTTPS協議443)。HTTP請求報文是由三部分組成: 請求行請求報頭請求正文

HTTP報文是包裹在TCP報文中傳送的,伺服器端收到TCP報文時會解包提取出HTTP報文。但是這個過程中存在一定的風險,HTTP報文是明文,如果中間被擷取的話會存在一些資訊洩露的風險。那麼在進入TCP報文之前對HTTP做一次加密就可以解決這個問題了。HTTPS協議的本質就是HTTP + SSL(or TLS)。在HTTP報文進入TCP報文之前,先使用SSL對HTTP報文進行加密。從網路的層級結構看它位於HTTP協議與TCP協議之間。

4.伺服器端響應http請求,瀏覽器得到html程式碼

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

(狀態碼

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

  • 1xx:指示資訊–表示請求已接收,繼續處理。

  • 2xx:成功–表示請求已被成功接收、理解、接受。

  • 3xx:重定向–要完成請求必須進行更進一步的操作。

  • 4xx:客戶端錯誤–請求有語法錯誤或請求無法實現。

  • 5xx:伺服器端錯誤–伺服器未能實現合法的請求。)

伺服器端WEB程式接收到http請求以後,就開始處理該請求,處理之後就返回給瀏覽器html檔案。

前面3個tcp包為3次握手的過程,主機向伺服器傳送一個http應用請求,伺服器收到請求後,返回一個tcp確認幀(第5幀),接著傳送一個http應答給主機(載有實際資料,第6,7幀,由於資料較大,分成多個包傳輸),主機收到伺服器的http應答資料後,又傳送一個tcp確認幀(第8幀),確認收到了資料,反覆進行傳輸,應答,直到所有資料傳輸完成(比如6到18幀)。

第4號包是http請求包,第19號包是http響應包

5. 瀏覽器解析html程式碼,並請求html程式碼中的資源

瀏覽器拿到index.html檔案後,就開始解析其中的html程式碼,遇到js/css/image等靜態資源時,就向伺服器端去請求下載(會使用多執行緒下載,每個瀏覽器的執行緒數不一樣),這個時候就用上keep-alive特性了,建立一次HTTP連線,可以請求多個資源,下載資源的順序就是按照程式碼裡的順序。

瀏覽器在請求靜態資源時(在未過期的情況下),向伺服器端發起一個http請求(詢問自從上一次修改時間到現在有沒有對資源進行修改),如果伺服器端返回304狀態碼(告訴瀏覽器伺服器端沒有修改),那麼瀏覽器會直接讀取本地的該資源的快取檔案。

PS:HTTP1.0和HTTP1.1的區別

在HTTP1.0協議中,客戶端與web伺服器建立連線後,只能獲得一個web資源。

HTTP1.1協議,允許客戶端與web伺服器建立連線後,在一個連線上獲取多個web資源

6.瀏覽器對頁面進行渲染呈現給使用者

1.瀏覽器在收到HTML,CSS,JS檔案後,它是如何把頁面呈現到螢幕上的?下圖對應的就是WebKit渲染的過程。

2.概念:

(1) DOM:Document Object Model,瀏覽器將HTML解析成樹形的資料結構,簡稱DOM。

(2) CSSOM:CSS Object Model,瀏覽器將CSS程式碼解析成樹形的資料結構。

(3) Render Tree:DOM 和 CSSOM 合併後生成 Render Tree

DOM樹的構建過程是一個深度遍歷過程:當前節點的所有子節點都構建好後才會去構建當前節點的下一個兄弟節點。

3.瀏覽器的渲染過程

(1) Create/Update DOM And request css/image/js:瀏覽器請求到HTML程式碼後,在生成DOM的最開始階段(應該是 Bytes → characters 後),並行發起css、圖片、js的請求,無論他們是否在HEAD裡。

注意:發起js檔案的下載request並不需要DOM處理到那個script節點,比如:簡單的正則匹配就能做到這一點,雖然實際上並不一定是通過正則:)。這是很多人在理解渲染機制的時候存在的誤區。

(2) Create/Update Render CSSOM: CSS檔案下載完成,開始構建CSSOM。

(3) Create/Update Render Tree:所有CSS檔案下載完成,CSSOM構建結束後,和 DOM 一起生成 Render Tree。

(4) Layout:有了Render Tree,瀏覽器已經能知道網頁中有哪些節點、各個節點的CSS定義以及他們的從屬關係。下一步操作稱之為Layout,顧名思義就是計算出每個節點在螢幕中的位置。

(5) Painting:Layout後,瀏覽器已經知道了哪些節點要顯示(which nodes are visible)、每個節點的CSS屬性是什麼(their computed styles)、每個節點在螢幕中的位置是哪裡(geometry)。就進入了最後一步:Painting,按照算出來的規則,通過顯示卡,把內容畫到螢幕上。

以上五個步驟前3個步驟之所有使用 “Create/Update” 是因為DOM、CSSOM、Render Tree都可能在第一次Painting後又被更新多次,比如JS修改了DOM或者CSS屬性。

Layout 和 Painting 也會被重複執行,除了DOM、CSSOM更新的原因外,圖片下載完成後也需要呼叫Layout 和 Painting來更新網頁。

我們先看一段HTML程式碼:

<html>
<head>
  <title>Beautiful page</title>
</head>
<body>
    
  <p>
    Once upon a time there was 
    a looong paragraph...
  </p>
  
  <div style="display: none">
    Secret message
  </div>
  
  <div>![](...)</div>
  ...
 
</body>
</html>

其DOM樹大致如此:

documentElement (html)
    head
        title
    body
        p
            [text node]
        
        div 
            [text node]
        
        div
            img
        
        ...

渲染樹為DOM樹中可視的部分:

root (RenderView)
    body
        p
            line 1
            line 2
            line 3
            ...
        
        div
            img
        
    ...

渲染樹的根結點囊括了所有的可視元素,它是瀏覽器視窗的一部分,並且能夠進行伸縮調整。一般來說,渲染區域為自瀏覽器左上角(0,0)起始,終止於右下角(window.innerWidth, window.innerHeight)的矩形部分。

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

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

4.迴流與重繪

1) 當render tree中的一部分(或全部)因為元素的規模尺寸,佈局,隱藏等改變而需要重新構建。這就稱為迴流(reflow)。每個頁面至少需要一次迴流,就是在頁面第一次載入的時候。在迴流的時候,瀏覽器會使渲染樹中受到影響的部分失效,並重新構造這部分渲染樹,完成迴流後,瀏覽器會重新繪製受影響的部分到螢幕中,該過程成為重繪。

2) 當render tree中的一些元素需要更新屬性,而這些屬性只是影響元素的外觀,風格,而不會影響佈局的,比如background-color。則就叫稱為重繪。

注意:迴流必將引起重繪,而重繪不一定會引起迴流。

3)迴流何時發生:

當頁面佈局和幾何屬性改變時就需要回流。下述情況會發生瀏覽器迴流:

1、新增或者刪除可見的DOM元素;

2、元素位置改變;

3、元素尺寸改變——邊距、填充、邊框、寬度和高度

4、內容改變——比如文字改變或者圖片大小改變而引起的計算值寬度和高度改變;

5、頁面渲染初始化;

6、瀏覽器視窗尺寸改變——resize事件發生時;

4)如何減少迴流、重繪

減少迴流、重繪其實就是需要減少對render tree的操作(合併多次多DOM和樣式的修改),並減少對一些style資訊的請求,儘量利用好瀏覽器的優化策略。具體方法有:

a, 直接改變className,如果動態改變樣式,則使用cssText(考慮沒有優化的瀏覽器)

js 程式碼:

  1. // 不好的寫法
  2. var left = 1;
  3. var top = 1;
  4. el.style.left = left + "px";
  5. el.style.top = top + "px";// 比較好的寫法
  6. el.className += " className1";
  7. // 比較好的寫法
  8. el.style.cssText += ";
  9. left: " + left + "px;
  10. top: " + top + "px;";

b. 讓要操作的元素進行”離線處理”,處理完後一起更新

a) 使用DocumentFragment進行快取操作,引發一次迴流和重繪;
b) 使用display:none技術,只引發兩次迴流和重繪;
c) 使用cloneNode(true or false) 和 replaceChild 技術,引發一次迴流和重繪;

c.不要經常訪問會引起瀏覽器flush佇列的屬性,如果你確實要訪問,利用快取

js 程式碼:
  1. // 不好的寫法
  2. for(迴圈) {
  3. el.style.left = el.offsetLeft + 5 + "px";
  4. el.style.top = el.offsetTop + 5 + "px";
  5. }
  6. // 比較好的寫法
  7. var left = el.offsetLeft,
  8. top = el.offsetTop,
  9. s = el.style;
  10. for (迴圈) {
  11. left += 10;
  12. top += 10;
  13. s.left = left + "px";
  14. s.top = top + "px";
  15. }

d. 讓元素脫離動畫流,減少迴流的Render Tree的規模

js 程式碼:

  1. $("#block1").animate({left:50});
  2. $("#block2").animate({marginLeft:50});

7.傳輸完成,斷開四次揮手

斷開連線端可以是Client端,也可以是Server端。假設Client端發起中斷連線請求:

第一次揮手:客戶端先發送FIN報文(第24幀),用來關閉主動方到被動關閉方的資料傳送,也就是客戶端告訴伺服器:我已經不會再給你發資料了(當然,在fin包之前傳送出去的資料,如果沒有收到對應的ack確認報文,客戶端依然會重發這些資料),但此時客戶端還可以接受資料。

第二次揮手:Server端接到FIN報文後,但是如果還有資料沒有傳送完成,則不必急著關閉Socket,可以繼續傳送資料。所以伺服器端先發送ACK(第25幀),告訴Client端:請求已經收到了,但是我還沒準備好,請繼續等待停止的訊息。這個時候Client端就進入FIN_WAIT狀態,繼續等待Server端的FIN報文。

第三次揮手:當Server端確定資料已傳送完成,則向Client端傳送FIN報文(第26幀),告訴Client端:伺服器這邊資料發完了,準備好關閉連線了。

第四次揮手:Client端收到FIN報文後,就知道可以關閉連線了,但是他還是不相信網路,所以傳送ACK後進入TIME_WAIT狀態(第27幀), Server端收到ACK後,就知道可以斷開連線了。Client端等待了2MSL後依然沒有收到回覆,則證明Server端已正常關閉,最後,Client端也可以關閉連線了至此,TCP連線就已經完全關閉了!

下圖是個完整的過程,便於理解和記憶。

8.Web優化

上面部分主要介紹了一次完整的請求對應的過程,瞭解該過程的目的無非就是為了Web優化。在談到Web優化之前,我們回到一個更原始的問題,Web前端的本質是什麼。我的理解是: 將資訊快速並友好的展示給使用者並能夠與使用者進行互動。快速的意思就是在儘可能短的時間內完成頁面的載入,如何儘快的載入資源?答案就是能不從網路中載入的資源就不從網路中載入,當我們合理使用快取,將資源放在瀏覽器端,這是最快的方式。如果資源必須從網路中載入,則要考慮縮短連線時間,即DNS優化部分;減少響應內容大小,即對內容進行壓縮。另一方面,如果載入的資源數比較少的話,也可以快速的響應使用者。當資源到達瀏覽器之後,瀏覽器開始進行解析渲染,瀏覽器中最耗時的部分就是reflow,所以圍繞這一部分就是考慮如何減少reflow的次數。

和七層協議的對應關係

主要考察五層協議棧的理解(物理層-資料鏈路層-網路層-傳輸層(-會話層-表示層)-應用層

1、應用層:客戶端瀏覽器通過DNS解析到www.google.com的IP地址為197.199.254.1,通過這個ip地址找到客戶端到伺服器的路徑,客戶端瀏覽器發起一個http會話到197.199.254.1,然後通過運輸層TCP協議封裝資料包,在TCP協議基礎上進行傳輸,輸入到網路層。

2、傳輸層:把HTTP會話請求分成報文段,新增源和目的埠,如伺服器端用80埠監聽客戶端的請求,客戶端由系統隨機選擇一個埠,如5000,與客戶端進行交換,伺服器把相應的請求返回給客戶端的5000埠。然後使用ip層的ip地址查詢目的端。TCP協議進行主要工作

3、網路層:客戶端的網路層不用關心應用層和傳輸層的東西,主要做的是為資料包選擇路由,通過查詢路由表確定如何到達伺服器,期間可能經過多個路由器。IP協議進行主要工作

4、資料鏈路層:客戶端的鏈路層,包通過鏈路層傳送到路由器,通過鄰居協議查詢給定的ip地址和MAC地址,然後傳送ARP請求查詢目的地址,如果得到迴應後就可以使用ARP的請求應答交換的ip資料包現在就可以傳輸了,然後傳送Ip資料包到達伺服器的地址。

http是超文字傳輸協議,主要特點是客戶端每次請求都需服務端響應

tcp/ip主要解決了資料如何在網路中傳輸

web用http封裝http文字資訊,用tcp/ip做傳輸協議發到網上,但需要提供對外封裝的操作介面,這就是socket介面,實現了不同程式的併發服務

tcp面向連線,其三次握手最大程度的保證了連線的可靠性,udp不是面向連線,但是其傳輸和接受資料都不需要確認,開銷小傳輸率高

Http--->DNS---(IP)-->TCP----->IP------>伺服器(處理請求)-------->TCP----->http

其實就是應用層與網路層的連線,只是少不了DNS與TCP的幫忙