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

概覽

日期:2018-4-26
目標:瞭解從輸入URL到頁面加載完成的過程當中都發生了什麼事情
總用時:一天
完成狀況:達成html

基本過程

爲何會想要了解從輸入URL到頁面加載完成的過程當中都發生了什麼事情這個問題呢,由於課程參考資料的Web 建站技術中HTML、HTML五、XHTML、CSS、SQL、JavaScript、PHP、ASP.NET、Web Services 是什麼中最高票答案中給出了下圖所示的網站訪問基本過程,張秋怡學姐的解答也十分易懂:面試

clipboard.png

再者這個問題可謂是常見的面試題之一,而這張圖中只是給出了很是基本的一個先後端交互的過程,因爲本身有基礎,因此列出的相關概念也都基本理解了,因而就花些時間擴展一下數據庫

跟我一塊兒來學起來

  • 咱們在打開瀏覽器,而後在輸入URL的時候有沒有發現瀏覽器會給你一些你似曾相識且與你輸入的內容相匹配的網址呢?

    clipboard.png

    其實咱們在瀏覽器中輸入URL的時候,瀏覽器就會開始智能的匹配可能URL,瀏覽器會從歷史記錄,書籤等地方,找到你已經輸入的字符串可能對應的URL,而後給出智能提示windows

  • 在輸好URL後咱們會按下Enter鍵,瀏覽器會發起請求,若是URL是域名而不是IP地址,將進行域名解析,所謂域名解析是指什麼呢?後端

    IP地址是網絡上標識站點的數字地址,爲了 方便記憶,採用域名來代替 IP地址標識站點地址,域名解析就是域名到 IP地址的轉換過程。

    域名解析按下面的步驟進行(部份內容涉及到計算機網絡知識):瀏覽器

    • 咱們本地硬盤下有一個hosts(windows下路徑爲C:\Windows\System32\drivers\etc)文件,做用是將一些經常使用的網址域名與其對應的IP地址創建一個關聯「數據庫」。通常來講,系統會首先自動從hosts文件中尋找對應的IP地址,若是有的話就直接使用hosts文件裏面的IP地址,而後直接進行端口確認
    • 若是上一步沒有找到,瀏覽器將調用解析程序,併成爲DNS服務器的一個客戶,把待解析的域名放在DNS請求報文中,以UDP用戶數據報的方式發給本地DNS服務器
    • 若是本地DNS服務器查找到相應的域名的IP地址,就把對應的IP地址放在回答報文中返回
    • 若是上一步沒有找到,即本地DNS服務器不知道被查詢域名的IP地址,因爲主機向本地DNS服務器的查詢是遞歸查詢,因此此時,本地DNS服務器就會以DNS客戶的身份向其餘DNS服務器繼續發出查詢請求報文。本地DNS服務器向根DNS服務器的查詢是迭代查詢,當找到相應域名的IP地址後,就會把這個結果返回給最初發起查詢請求的瀏覽器緩存

      遞歸查詢:在該模式下 DNS服務器接收到客戶機請求,必須返回一個準確的查詢結果給客戶機。若是該 DNS服務器本地沒有存儲被查詢的 DNS信息,那麼該服務器會(替客戶機)詢問其餘服務器,並將返回的查詢結果再返回給客戶機。
      迭代查詢:在該模式下 DNS服務器接收到客戶機請求,若是該 DNS服務器本地沒有存儲被查詢的 DNS信息, DNS服務器會向客戶機提供其餘可以解析查詢請求的 DNS服務器地址,讓客戶機再向這臺 DNS服務器提交請求,依次循環直到返回查詢的結果爲止。
    • 通過上面的步驟後,瀏覽器已經得到輸入域名的IP地址,能夠進行下一步了。
  • 瀏覽器獲得IP地址後,還要確認一下端口,默認端口是80端口,一個服務器可能會提供不一樣的服務,這些服務經過端口來區分,能夠指定端口號
  • 瀏覽器獲得IP地址並確認端口後,會向目標服務器發起HTTP請求,HTTP請求是經過TCP鏈接來發送的(若是是HTTPS則須要先創建SSL鏈接,再是TCP鏈接,下面的討論基於HTTP),具體以下服務器

    • 瀏覽器會生成目標服務器的HTTP請求報文,請求報文通常包含請求方法、請求URI、協議版本、請求首部字段等內容,HTTP請求準備好後,HTTP請求報文從應用層傳到傳輸層後會被分割爲報文段,並會發起一條到達目標服務器的TCP鏈接,開始TCP三次握手,過程如圖所示:網絡

      clipboard.png

      通俗的能夠理解爲:異步

      A主動向B打電話:嗨,能聽到嗎(SYN=1,seq=x),而後A就開始等待B的回答(SYN-SENT狀態),此時A不知道B能不能聽到
      B聽到A的話以後,能夠確認它能聽到A,可是它還要確認一下A能不能聽到他本身的聲音,因而B說:我能聽到你的聲音(ACK=1,ack=x+1),你能聽到個人聲音嗎(SYN=1,seq=y),而後B開始等待A的恢復(SYN-RECD狀態)
      A聽到B的話以後,A能夠確認兩件事,一是B能聽到它說話,二是它也能聽到B說話,A已經能夠隨時說話和傾聽了(ESTABLISHED狀態)。可是此時的B還在等待中,並不知道A能不能聽到,因此此時A須要再回復B說:我能夠聽到你的聲音(ACK=1,ack=y+1),開始愉快的聊天吧~(seq=x+1),B聽到這句話後便也能夠隨時說話和傾聽了(ESTABLISHED狀態)
      以後兩我的就能夠balabalabala....
    • HTTP請求的請求報文是直接附在第三次握手的消息中
    • 穿插補充小知識,爲何是三次握手,而不是兩次四次?

      有一種觀點是三次握手是基於 TCP協議的可靠性( Reliability)要求,這是確認雙發都能進行收發的最小次數,兩次確認不了,四次多餘。可是並無徹底意義上的可靠,不論握手多少次都只能代表握手的時候是可靠的,不能保證後面數據傳輸時一直可靠,由於 信道是不可靠的,固然三次握手至少能夠代表它曾經可靠,這是兩次握手沒法完成的,而四次甚至更屢次握手僅僅是提升「它曾經可靠」這個結論的可信程度。因此這個握手也只是確保可靠的一個基本須要, TCP協議的可靠性(注意區分完整性 integrity)更多的是由校驗和、定時器超時重傳、確認機制

      在《計算機網絡》一書中也有講過這個問題,給出的解釋是:三次握手是爲了防止失效的鏈接請求報文段被服務端接收,從而產生錯誤。具體例子以下所述:
      client發出的一個鏈接請求報文段並無丟失,而是在某個網絡結點長時間的滯留了,以至延誤到鏈接釋放之後的某個時間纔到達server。原本這是一個早已失效的報文段。但server收到此失效的鏈接請求報文段後,就誤認爲是client再次發出的一個的鏈接請求。因而就向client發出確認報文段,贊成創建鏈接。
      假設不採用「三次握手」,那麼只要server發出確認,新的鏈接就創建了。可是因爲如今client並無發出創建鏈接的請求,所以不會理睬server的確認,也不會向server發送數據。而server卻覺得新的鏈接已經創建,並一直等待client發來數據。這樣,server的不少資源就白白浪費掉了。
      採用「三次握手」的辦法能夠防止上述現象發生。例如剛纔那種狀況,client不會向server的確認發出確認。server因爲收不到確認,就知道client並無要求創建鏈接

  • 鏈接創建以後,開始進行數據傳輸,雖然瀏覽器知道目標服務器的IP和端口,可是數據總不可能飛過去吧?HTTP請求報文段會從傳輸層傳到網絡層,在網絡層被封裝成IP數據包,網絡層規定了經過怎樣的路徑(所謂的傳輸路線)到達目標服務器,並把數據包傳送給對方。
  • 網絡層封裝好的IP數據包會進一步傳到下一層 --- 數據鏈路層,而後會再次被封裝到MAC數據幀結構中,因爲IP地址間的通訊依賴於MAC地址(網卡所屬的固定地址),因此MAC數據幀結構中會有通過ARP協議解析後的MAC地址(不必定是目標服務器的MAC地址,由於實際上通訊的雙方在同一局域網(LAN)內的狀況是不多的,通常都會通過路由中轉)。
  • 數據鏈路層的MAC數據幀再向下傳,便會到達物理層,這裏要注意物理層考慮的是怎樣才能在鏈接各類計算機的傳輸媒體上傳輸數據比特流,而不是指具體的傳輸媒體。 物理層須要確保原始的數據可在各類物理媒體上傳輸,它規定了傳輸媒體的機械特性、電氣特性、功能特性、過程特性:

    clipboard.png

    常見的傳輸媒體有雙絞線、電纜、光纜、無線信道等,物理層的任務就是要讓數據在這些傳輸媒體上都能能進行傳輸

  • 經過MAC地址匹配,數據經過傳輸媒體到達目標服務器的物理層,物理層接收數據比特流而後向上傳送到服務器的數據鏈路層,在數據鏈路層MAC數據幀將進行封裝的逆操做,還原成IP數據包以後向上傳送到網絡層,網絡層也進行封裝的逆操做還原成HTTP請求報文段(分割後的一小段一小段的),而後這些報文段向上傳到傳輸層,在傳輸層按原來的序號從新組裝成完整的HTTP請求報文,再向上傳到應用層,應用層的HTTP協議便會開始對請求進行處理
  • 這個處理多是直接返回靜態的資源,也可能通過PHPJAVA等語言進行處理等,等處理完成後,會返回一個HTTP響應,它生成一個HTTP響應報文,與HTTP請求報文結構相似,而後這個響應報文會「走過」請求報文來時的路到達瀏覽器
  • 瀏覽器接收HTTP響應,而後有可能釋放TCP鏈接,也有可能從新使用這個TCP鏈接發送新的請求(持久鏈接),此處瞭解一下TCP鏈接的釋放,不一樣於TCP鏈接創建的三次握手,TCP鏈接的釋放是四次揮手,客戶端和服務器端均可以發起關閉請求,也存在二者同時發起關閉請求的狀況,圖中爲客戶端A主動發起關閉請求:

    clipboard.png

    一樣通俗的解釋一波:

    A對B要傳的文件已經傳完了,因而他對B說:我要傳的文件已經傳完了,我要準備下線了(seq=u,FIN=1)。而後A就等待B的回覆(FIN-WAIT-1狀態)
    B看到A的消息後,回覆A說:知道了,可是我還有文件給你(ACK=1,ack=u+1,seq=v)。B進入等他文件傳完的狀態(CLOSE-WAIT狀態)。
    A收到B的回覆以後,下線不了了,因而繼續等待着B的文件傳完(FIN-WAIT-2狀態)
    幾分鐘後,B的文件傳完了,此時他對A說:個人文件傳完了,我也要下線了(seq=w,FIN=1,ACK=1,ack=u+1),而後B等待A的回覆來確認真的能夠下線了(LAST-ACK狀態)
    A收到B的回覆後,便對A說:好的,那你下線吧(ACK=1,seq=u+1,ack=w+1)。此時A會等待一段時間(2MSL,TIME-WAIT狀態),B收到後就直接下線了(CLOSE狀態),而後2MSL時間到了以後,A也下線(CLOSE狀態)
    • 爲何服務器B在接到A的斷開請求時不當即贊成斷開?
      當服務器B收到斷開鏈接的請求時,服務器可能仍然有數據未發送完畢,因此服務器先發送確認信號,等全部數據發送完畢後再贊成斷開
    • 爲何是四次揮手,而不是像創建鏈接同樣的三次
      由於TCP鏈接是全雙工模式,服務器B收到A的斷開請求時,僅僅代表A沒有東西傳給服務器B了,但此時服務器B可能向A的傳輸還沒結束,因此服務器B要先給A一個確認收到A的斷開請求的ACK報文,而後繼續向A把信息傳完,等傳完以後服務器B再向A發送斷開請求的報文段,等A收到並回復ACK報文後再釋放鏈接。
      也就是說對於A來講他要發送請求給B並等待B確認,對於B來講也要發送請求給A並等待A確認,二者都通過這兩個過程才能徹底釋放TCP鏈接,而非單方面的釋放。
      創建鏈接只須要創建,沒有數據的影響,而釋放鏈接還要考慮數據是否傳輸完,因此創建鏈接的時候B確認收到A的創建請求與B發送創建請求這一步能夠合成一步成爲TCP創建鏈接的第二次握手,而釋放鏈接時卻必須分開。
    • 最後一次握手後A爲何要等2MSL
      首先解釋一下MSLMSL是指最長報文段壽命,RFC793建議爲兩分鐘,但實際上可據實際狀況而定,也就是說一個報文段最久可存在的時間是MSL

      1. 這是爲了保證A發送的最後一個ACK報文可以到達服務器B,若是這個ACK報文丟失了,服務器B沒有收到,B會超時重傳第三次握手的FIN+ACK報文給A,這個時候處於等待的A就能夠收到這個重傳的FIN+ACK報文,並再次發送ACK報文給服務器B,而且從新啓動2MSL計時器,最終結果是A和B都正常進入CLOSE狀態。若是A發完ACK報文後就直接釋放了A-->B的鏈接,那麼A就收不到B重傳的FIN+ACK報文,也不能從新發送ACK`報文,那麼B就沒法按正常步驟釋放B-->A的鏈接
      2. 防止「已失效的鏈接請求報文」出如今下一個新的鏈接中,由於一個報文段的壽命是MSL,因此A在發送完最後一個ACK報文段以後,再通過時間2MSL,本鏈接持續的時間內所產生的全部報文段都將在網絡中消失,這樣這些舊的報文段便不會出如今下一個新的鏈接中
  • 瀏覽器以後會檢查HTTP的響應狀態,主要經過響應碼來判斷

    1xx: 表示通知信息的,好比請求收到了或正在處理
    2xx:表示成功,操做被成功接收並處理
    3xx:表示重定向,通常完成請求還必須採起進一步的行動
    4xx:表示客戶端的差錯
    5xx:表示服務器的差錯
  • 若是響應可緩存,瀏覽器將把響應存入緩存
  • 瀏覽器根據HTTP報頭信息解碼響應,決定如何處理這些響應,並展示響應,以響應爲一個HTML爲例
  • 瀏覽器開始自上而下,自左而右的加載HTML文檔,最開始會遇到<!DOCTYPE>聲明,而後根據<!DOCTYPE>聲明瀏覽器就知道該用哪一種規範來解析這個文檔
  • 再繼續邊加載邊解析,邊生成DOM樹,加載過程當中遇到外部CSS文件,瀏覽器便會另外發出一個請求,來獲取CSS文件(過程和上面說的同樣),獲取CSS後會生成CSS Rule樹。DOM樹和CSS Rule樹生成Render樹,頁面能夠開始邊加載邊渲染了

    • 渲染樹和DOM樹的關係:那些不可見的DOM元素(如<head>…</head>display=none的元素)不會被插入渲染樹中;還有像一些節點是絕對定位或浮動,這些節點會在文本流以外,所以他們會在渲染樹和DOM樹的不一樣位置,渲染樹標識出真實的位置,並用一個佔位結構標識出他們原來的位置,而DOM樹上是他們原來的位置
    • 渲染包含"佈局"(layout)和"繪製"(paint)這兩個步驟,所謂"佈局"是指給出每一個DOM節點在瀏覽器窗口中的準確位置,"繪製"是指遍歷Render樹將佈局好的DOM節點繪製在屏幕上。

      clipboard.png

  • 瀏覽器繼續加載渲染,若是遇到<script>標籤,瀏覽器會當即執行(暫不考慮deferasync屬性),此時會出現頁面阻塞,不只要等待文檔中JS文件下載加載完畢,還要等待JS解析執行完畢,才能夠恢復HTML文檔的加載解析。

    • 這是瀏覽器爲了防止出現JS修改DOM樹,須要從新構建DOM樹的狀況,DOM樹改變瀏覽器須要回過頭來從新渲染這部分代碼,因此瀏覽器但願經過阻塞其餘內容的下載和呈現,來避免出現更多的沒必要要的Reflow(稱爲迴流或者重排)
    • 若是<script>放在的<head>中,則<body>標籤沒法被加載,那麼頁面天然就沒法渲染了,所以這將致使在該JS代碼徹底執行完以前,頁面都是一片空白,用戶體驗很是很差,通常我看到長時間的空白頁面,我都很是想直接關閉它。所以會推薦將全部<script>標籤儘量放到<body>標籤的底部,以儘可能減小對整個頁面下載的影響,此時雖然還會存在一個腳本阻塞另外一個腳本的問題,可是用戶體驗比上面的好不少,由於用戶看到了大部份內容,而不是空白
    • defer屬性至關於告訴瀏覽器當即下載,延遲執行。它使得加載後續文檔元素的過程將和JS文件的加載並行進行(異步),可是JS文件的執行要在整個頁面解析完成以後,DOMContentLoaded事件觸發以前完成,執行順序爲出現的前後順序。(高程中指出現實中不必定會按照順序執行,也不必定會在DOMContentLoaded事件觸發以前完成,所以最好只包含一個延遲腳本,這多是與瀏覽器的實現有關,具體什麼狀況下會出現我還不知道???)
    • async屬性至關於告訴瀏覽器當即下載執行,而且頁面的加載渲染不須要等待該腳本加載和執行,它們二者會異步進行。標記爲async的腳本不會按照它們出現的前後順序執行,而是誰先下載完了誰就先執行,它們必定會在頁面的load事件觸發以前執行,但可能會在DOMContentLoaded事件觸發以前或以後執行。基於前面所說的一點緣由,異步腳本最好不要修改DOM,若是由多個異步腳本,它們之間最好沒有依賴關係
  • 瀏覽器繼續加載渲染,若是遇到圖片資源,瀏覽器也會另外發出一個請求,來獲取圖片資源,這是異步請求,因此不會等到圖片下載完,而是繼續渲染後面的HTML文檔。
  • 等到服務器返回圖片文件,若是先前並無爲這個圖片設定寬高,那麼因爲圖片佔用了必定面積,影響了後面段落的排布,瀏覽器會進行Reflow
  • 而後而後終於和</html>碰面了,這次的頁面加載渲染過程完成,瀏覽器也是很累了,而後會當即觸發DOMContentLoaded事件,該事件是在造成完整的DOM樹以後就會觸發,而不會理會圖像、JS文件、CSS文件或其餘資源是否已經下載完畢
  • 當頁面徹底加載後,也就是全部圖像、JS文件、CSS文件等外部資源都加載完成後會觸發load事件
  • 用戶在頁面上進行交互時,可能會致使頁面進行RepaintReflow

    • Repaint:若是隻是改變了某個元素的背景顏色,文字顏色等,不影響元素周圍或內部佈局的屬性,將只會引發瀏覽器的Repaint,重繪某一部分
    • Reflow:若是某個部分發生了的變化影響了佈局,那瀏覽器就須要倒回去從新渲染,每次Reflow必然會致使Repaint

尾聲

原本只是想了解了解,結果一入深似海,看似簡單的操做背後藏着數不清的小動做,文中也只是涉及了一部分,還有不少相關的過程沒有涉及到,可是能力有限,仍是慢慢來,暫時就先告一段落,文中若有錯誤還請指正哦~

參考

相關文章
相關標籤/搜索