從輸入U(xiǎn)RL到頁(yè)面加載完成,這中間到底發(fā)生了什么
精簡(jiǎn)版請(qǐng)直接跳往文末
文中包括
- TCP的三次握手;
- 瀏覽器頁(yè)面的渲染過(guò)程;
- javascript異步原理,事件循環(huán)(event loop)和任務(wù)隊(duì)列(task queue)
- TCP的四次揮手;
- HTTPS和HTTP工作原理及通信;
首先需要了解計(jì)算機(jī)的網(wǎng)絡(luò)體系結(jié)構(gòu)
- 應(yīng)用層(HTTP,SMTP.FTP,POP3)
- 運(yùn)輸層(TCP,UDP)
- 網(wǎng)絡(luò)層(IP(路由器))
- 數(shù)據(jù)鏈路層(網(wǎng)橋(CSMA/CD,PPP))
- 物理層(集線器)
這里需要注意的是,HTTP和TCP/UDP是兩個(gè)不同層上的協(xié)議。http(HyperText Transfer Protocol超文本傳輸協(xié)議,所有的WWW(World Wide Web萬(wàn)維網(wǎng))文件都必須遵守這個(gè)標(biāo)準(zhǔn))是應(yīng)用層的協(xié)議,而TCP/UDP是傳輸層的協(xié)議,http是再TCP/UDP之上的協(xié)議,http協(xié)議使用了TCP/UDP,http更加高級(jí)一點(diǎn),但是沒(méi)有很好的靈活性,也就是http使用起來(lái)比TCP/UDP要簡(jiǎn)單,只需要遵循規(guī)范就可以進(jìn)行網(wǎng)絡(luò)通信了。
接下來(lái)回到正題
1:查找域名對(duì)應(yīng)的IP地址
IP地址:IP協(xié)議為互聯(lián)網(wǎng)上的每一個(gè)網(wǎng)絡(luò)和每一臺(tái)主機(jī)分配一個(gè)邏輯地址。也就是說(shuō),IP實(shí)際上就是一個(gè)標(biāo)簽,為每一臺(tái)電腦打上一個(gè)獨(dú)有的標(biāo)記。而服務(wù)器本質(zhì)也是一臺(tái)主機(jī),想要訪問(wèn)某個(gè)服務(wù)器,就要先知道它的IP。
IP和域名并不是一一對(duì)應(yīng)的關(guān)系,可以把多個(gè)提供相同的服務(wù)器的IP設(shè)置為同一個(gè)域名,但是在同一時(shí)刻一個(gè)域名只能解析出一個(gè)IP地址;同時(shí),一個(gè)IP地址可以綁定多個(gè)域名,數(shù)量不限
域名:IP地址是由四組數(shù)字組成的,再使用過(guò)程中比較難記憶,因此使用了字母來(lái)代替數(shù)字,域名通過(guò)DNS解析到固定IP上,這樣就可以通過(guò)域名來(lái)訪問(wèn)到固定IP。(例如www.baidu.com)
域名和URL是兩個(gè)完全不同的東西:域名是一臺(tái)或一組服務(wù)器的名稱(chēng),用來(lái)確定服務(wù)器在Internet上的位置,而URL是統(tǒng)一資源定位符,用來(lái)確定某一個(gè)文件的具體位置(www.baidu.com這是域名,http://www.itdecent.cn/p/3944732228f0這個(gè)是URL)
DNS:每個(gè)域名都對(duì)應(yīng)一個(gè)或多個(gè)提供相同服務(wù)服務(wù)器的IP地址,叫做DNS解析(例如:A解析和Cname解析)
查找域名對(duì)應(yīng)的IP地址,具體要分為以下幾步
- 瀏覽器搜索自己的DNS緩存。
- 搜索操作系統(tǒng)中的DNS緩存。
- 搜索操作系統(tǒng)的hosts文件(windows系統(tǒng)下,維護(hù)一張域名與IP地址的對(duì)應(yīng)表(類(lèi)似于遵循域名與IP對(duì)應(yīng)的關(guān)系))
- 操作系統(tǒng)將域名發(fā)送至LDOS(本地區(qū)域名服務(wù)器,如果是學(xué)校,這個(gè)LDNS服務(wù)器就在學(xué)校,如果是電信,LDNS服務(wù)器就在當(dāng)?shù)仉娦牛?,查找成功則返回結(jié)果,失敗則發(fā)起一個(gè)迭代DNS請(qǐng)求。(這時(shí)LDNS將向ROOt Name Sever及根域名服務(wù)器發(fā)起請(qǐng)求,此處將返回你搜索的域的頂級(jí)域名服務(wù)器地址(比方說(shuō)你搜索的是baidu.com,它將會(huì)返回com域的頂級(jí)域名服務(wù)器地址,這之后還有兩步...詳情需要百度))
- LDNS將得到的IP地址返回給瀏覽器,同時(shí)自己也將IP緩存起來(lái)。
- 操作系統(tǒng)量IP地址返回給瀏覽器,同時(shí)自己也將IP緩存起來(lái)。
- 于是瀏覽器就得到了域名對(duì)應(yīng)的IP地址,(然而這僅僅是開(kāi)始)
2:瀏覽器根據(jù)IP地址與服務(wù)器建立socket鏈接(TCP的三次握手)
當(dāng)我們得到了服務(wù)器的IP地址之后,就可以開(kāi)始連接了,這里的通訊鏈接需要經(jīng)歷以下三個(gè)過(guò)程,因此被叫做TCP的三次握手:
- 客戶(hù)端發(fā)送syn包(syn=j)到服務(wù)器,并進(jìn)入SYN_SEND狀態(tài),等待服務(wù)器確認(rèn)(主機(jī)向服務(wù)器發(fā)送一個(gè)建立鏈接的請(qǐng)求);
- 服務(wù)器收到syn包,必須確認(rèn)客戶(hù)的SYN(ack=j+1),同時(shí)自己也發(fā)送一個(gè)syn包(syn=k),既syn+ack包,此時(shí)服務(wù)器進(jìn)入syn_recv狀態(tài)(服務(wù)器接到請(qǐng)求后發(fā)送同意連接的信號(hào))
- 客戶(hù)端收到服務(wù)器的syn+ack包,向服務(wù)器發(fā)送確認(rèn)包ack(ack=k+1),此包發(fā)送完畢,客戶(hù)端和服務(wù)器進(jìn)入established狀態(tài),完成三次握手(主機(jī)接到同意信息后,再次向服務(wù)器發(fā)送了確認(rèn)信號(hào))
這里的三次握手,采用了TCP協(xié)議,其可以保證信息傳輸?shù)目煽啃?,三次握手過(guò)程中,萬(wàn)一有一方收不到確認(rèn)信號(hào),協(xié)議會(huì)要求重新發(fā)送信號(hào)。
3:瀏覽器與服務(wù)器通信:瀏覽器請(qǐng)求,服務(wù)器處理請(qǐng)求
當(dāng)服務(wù)器與主機(jī)建立了鏈接后,主機(jī)就與服務(wù)器進(jìn)行通信。網(wǎng)頁(yè)請(qǐng)求是一個(gè)單項(xiàng)請(qǐng)求的過(guò)程,那就是一個(gè)主機(jī)向服務(wù)器請(qǐng)求數(shù)據(jù),服務(wù)器返回相應(yīng)的數(shù)據(jù)的過(guò)程。
- 瀏覽器根據(jù)URL(這里是URL)內(nèi)容生成HTTP請(qǐng)求,請(qǐng)求中包含請(qǐng)求文件的位置,請(qǐng)求文件的方式等。
- 服務(wù)器接到請(qǐng)求后,會(huì)根據(jù)HTTP請(qǐng)求中的內(nèi)容來(lái)決定如何獲取相應(yīng)的HTML文件。
- 服務(wù)器將得到的HTML文件發(fā)送給瀏覽器。
- 在瀏覽器還沒(méi)有完全接收HTML文件時(shí)便開(kāi)始渲染,顯示網(wǎng)頁(yè)。
- 在執(zhí)行HTML中代碼時(shí),根據(jù)需要,瀏覽器會(huì)繼續(xù)請(qǐng)求圖片,css,js等文件。
瀏覽器渲染展示網(wǎng)頁(yè)的過(guò)程
- HTML代碼轉(zhuǎn)化為DOM(DOM Tree)
- CSS代碼轉(zhuǎn)化成CSSOM(CSS Object Model)
- 結(jié)合DOM和CSSOM,生成一棵渲染樹(shù)(包含每個(gè)節(jié)點(diǎn)的視覺(jué)信息)(Render Tree)
- 生成布局(layout),將所有渲染樹(shù)的所有節(jié)點(diǎn)進(jìn)行平面合成
- 將布局繪制(paint)在屏幕上
- 使用JavaScript腳本來(lái)動(dòng)態(tài)的修改DOM,以便給Web應(yīng)用帶來(lái)動(dòng)態(tài)行為
- Web應(yīng)用的執(zhí)行分為兩個(gè)階段
- 全局JavaScript代碼遇到script節(jié)點(diǎn)時(shí)執(zhí)行。在這個(gè)執(zhí)行過(guò)程中,其能夠以任意程度改變當(dāng)前的DOM(有可能觸發(fā)頁(yè)面的重繪和回流)
- 關(guān)于事件處理,在同一時(shí)刻,只能處理多個(gè)不同事件中的一個(gè),處理順序是事件的生成循序(隊(duì)列順序),事件處理階段大量依賴(lài)事件隊(duì)列,事件循環(huán)會(huì)檢查事件隊(duì)列的頭部,如果檢測(cè)到了一個(gè)事件,相應(yīng)的事件處理器就會(huì)被調(diào)用(詳情見(jiàn)文末)
4:瀏覽器與服務(wù)器斷開(kāi)連接(四次揮手)
- 主機(jī)向服務(wù)器發(fā)送一個(gè)斷開(kāi)連接的請(qǐng)求。
- 服務(wù)器接到請(qǐng)求后發(fā)送確認(rèn)收到請(qǐng)求的信號(hào)。
- 服務(wù)器向主機(jī)發(fā)送斷開(kāi)通知。
- 主機(jī)接到斷開(kāi)通知后斷開(kāi)連接并反饋一個(gè)確認(rèn)信號(hào),服務(wù)器收到確認(rèn)信號(hào)后斷開(kāi)連接
為什么服務(wù)器在接到斷開(kāi)請(qǐng)求時(shí)不立即同意斷開(kāi):當(dāng)服務(wù)器收到斷開(kāi)連接的請(qǐng)求時(shí),可能仍然有數(shù)據(jù)未發(fā)送完成,所有服務(wù)器先發(fā)送確認(rèn)信號(hào),等所有數(shù)據(jù)發(fā)送完畢后在同意斷開(kāi)
四次揮手之后,主機(jī)發(fā)送確認(rèn)信號(hào)后并沒(méi)有立即斷開(kāi)連接,而是等待2個(gè)報(bào)文傳送周期,原因是:如果四次揮手的確認(rèn)信息丟失,服務(wù)器將會(huì)重新發(fā)送第三次揮手的斷開(kāi)連接的信號(hào),而服務(wù)器發(fā)覺(jué)丟包與重新發(fā)送的斷開(kāi)連接到達(dá)主機(jī)的時(shí)間正好為2個(gè)報(bào)文傳輸周期
注釋
- HTTPS與HTTP的區(qū)別
HTTPS:HTTPS在傳輸數(shù)據(jù)之前需要客戶(hù)端(瀏覽器)與服務(wù)端(網(wǎng)站)之間進(jìn)行一次握手,在握手過(guò)程中將確立雙方加密傳輸數(shù)據(jù)的密碼信息;
瀏覽器與網(wǎng)站互相發(fā)送加密的握手消息并驗(yàn)證,是為了保證雙方都有一致的密碼,為后續(xù)真正的數(shù)據(jù)傳輸做一次測(cè)試
HTTPS的工作原理
- 瀏覽器將自己支持的一套加密規(guī)則發(fā)送給網(wǎng)站;
- 網(wǎng)站從中選出一組加密算法與HASH算法,并將自己的身份信息以證書(shū)的形式發(fā)回給瀏覽器。證書(shū)里面包含了網(wǎng)站地址,加密公鑰,以及證書(shū)的頒發(fā)機(jī)構(gòu)等信息。
- 瀏覽器獲得網(wǎng)站證書(shū)之后驗(yàn)證證書(shū)的合法性(如果證書(shū)受信任,或者是用戶(hù)接受了不受信的證書(shū),瀏覽器會(huì)生成一串隨機(jī)數(shù)的密碼,并用證書(shū)中提供的公鑰加密)
- 使用約定好的HASH算法計(jì)算握手消息,并使用生成的隨機(jī)數(shù)對(duì)消息進(jìn)行加密,最后將之前生成的所有信息發(fā)送給網(wǎng)站
HTTPS和HTTP協(xié)議的區(qū)別
- https協(xié)議需要到ca申請(qǐng)證書(shū),一般免費(fèi)證書(shū)很少,需要交費(fèi)。
- http是超文本傳輸協(xié)議,信息是明文傳輸,https 則是具有安全性的ssl加密傳輸協(xié)議。
- http和https使用的是完全不同的連接方式用的端口也不一樣,前者是80,后者是443。
- http的連接很簡(jiǎn)單,是無(wú)狀態(tài)的 。無(wú)狀態(tài)的意思就是給他一樣的參數(shù)你要有一樣的結(jié)果,通過(guò)增加cookie和session機(jī)制,現(xiàn)在的網(wǎng)絡(luò)請(qǐng)求其實(shí)是有狀態(tài)的。
- HTTPS協(xié)議是由SSL+HTTP協(xié)議構(gòu)建的可進(jìn)行加密傳輸、身份認(rèn)證的網(wǎng)絡(luò)協(xié)議, 要比http協(xié)議安全。
- 事件循環(huán)(event loop)和事件隊(duì)列(task queue)
JS的異步機(jī)制由事件循環(huán)和任務(wù)隊(duì)列構(gòu)成。JS本身是單線程語(yǔ)言,所謂異步依賴(lài)于瀏覽器或者操作系統(tǒng)等完成。JavaScript主線程擁有一個(gè)執(zhí)行棧以及一個(gè)任務(wù)隊(duì)列,主線程會(huì)依次執(zhí)行代碼,當(dāng)遇到函數(shù)時(shí),會(huì)先將函數(shù)入棧,函數(shù)運(yùn)行完畢后再將該函數(shù)出棧,直到所有代碼執(zhí)行完畢。
遇到異步操作(例如:setTimeout, AJAX)時(shí),異步操作會(huì)由瀏覽器(OS)執(zhí)行,瀏覽器會(huì)在這些任務(wù)完成后,將事先定義的回調(diào)函數(shù)推入主線程的任務(wù)隊(duì)列(task queue)中,當(dāng)主線程的執(zhí)行棧清空之后會(huì)讀取task queue中的回調(diào)函數(shù),當(dāng)task queue被讀取完畢之后,主線程接著執(zhí)行,從而進(jìn)入一個(gè)無(wú)限的循環(huán),這就是事件循環(huán).
Microtask 與 Macrotask
一個(gè)瀏覽器環(huán)境只能擁有一個(gè)事件循環(huán)(event loop),而一個(gè)事件循環(huán)可以多個(gè)任務(wù)隊(duì)列(Task queue),每個(gè)任務(wù)都有一個(gè)任務(wù)源(Task source)。任務(wù)隊(duì)列是一個(gè)先進(jìn)先出的隊(duì)列.macrotask(macro-task: script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering) 和 microtask(micro-task: process.nextTick, Promises(這里指瀏覽器實(shí)現(xiàn)的原生 Promise), Object.observe, MutationObserver) 是異步任務(wù)的兩種分類(lèi)。在掛起任務(wù)時(shí),JS 引擎會(huì)將所有任務(wù)按照類(lèi)別分到這兩個(gè)隊(duì)列中,首先在 macrotask 的隊(duì)列(這個(gè)隊(duì)列也被叫做 task queue)中取出第一個(gè)任務(wù),執(zhí)行完畢后取出 microtask 隊(duì)列中的所有任務(wù)順序執(zhí)行;之后再取 macrotask 任務(wù),周而復(fù)始,直至兩個(gè)隊(duì)列的任務(wù)都取完。
全部代碼(script)是一個(gè)macrotask,js先執(zhí)行一個(gè)macrotask,執(zhí)行過(guò)程中遇到(setTimeout, setInterval, setImmediate等)異步操作則創(chuàng)建一個(gè)macrotask,遇到(process.nextTick, Promises等)創(chuàng)建一個(gè)microtask,這兩個(gè)queue分別被掛起.執(zhí)行棧為空時(shí)開(kāi)始處理macrotask,完成后處理microtask,直到該microtask全部執(zhí)行完,然后繼續(xù)主線程調(diào)用棧.
每一次事件循環(huán)(one cycle of the event loop),只處理一個(gè) (macro)task。待該 macrotask 完成后,所有的 microtask 會(huì)在同一次循環(huán)中處理。處理這些 microtask 時(shí),還可以將更多的 microtask 入隊(duì),它們會(huì)一一執(zhí)行,直到整個(gè) microtask 隊(duì)列處理完。
<script type="text/javascript"> console.log(1) setTimeout(function(){ console.log(2); let promise = new Promise(function(resolve, reject) { console.log(6); resolve() }).then(function(){ console.log(7) }); },0); let promise = new Promise(function(resolve, reject) { console.log(3); resolve() }).then(function(){ console.log(4) }).then(function(){ console.log(8) }); console.log(5) </script>以上代碼的打印順序 1>3>5>4>8>2>6>7
- 頁(yè)面的重繪和回流
在生成render tree之后,瀏覽器繪制頁(yè)面(paint),此時(shí)會(huì)觸發(fā)回流與重繪
回流
當(dāng)render tree中的一部分(或全部)因?yàn)樵氐囊?guī)模尺寸,布局,隱藏等改變而需要重新構(gòu)建。這就稱(chēng)為回流(reflow)。每個(gè)頁(yè)面至少需要一次回流,就是在頁(yè)面第一次加載的時(shí)候。在回流的時(shí)候,瀏覽器會(huì)使渲染樹(shù)中受到影響的部分失效,并重新構(gòu)造這部分渲染樹(shù),完成回流后,瀏覽器會(huì)重新繪制受影響的部分到屏幕中,該過(guò)程稱(chēng)為重繪。
以下是會(huì)觸發(fā)回流的幾種情況
- 添加或者刪除可見(jiàn)的DOM元素;
- 元素位置改變;
- 元素尺寸改變——邊距、填充、邊框、寬度和高度
- 頁(yè)面渲染初始化;
- 內(nèi)容改變——比如文本改變或者圖片大小改變而引起的計(jì)算值寬度和高度改變;
- 瀏覽器窗口尺寸改變——resize事件發(fā)生時(shí);
重繪
當(dāng)render tree中的一些元素需要更新屬性,而這些屬性只是影響元素的外觀,風(fēng)格,而不會(huì)影響布局的,比如background-color。則就叫稱(chēng)為重繪。
回流必將引起重繪,而重繪不一定會(huì)引起回流。
舉個(gè)栗子
當(dāng)我們?cè)跒g覽器中輸入www.baidu.com時(shí),直到我們呈現(xiàn)出頁(yè)面,會(huì)發(fā)生以下事情(Chrome瀏覽器)
- 首先 Chrome 搜索自身的 DNS 緩存。(如果 DNS 緩存中找到百度的 IP 地址,就跳過(guò)了接下來(lái)查找 IP 地址步驟,直接訪問(wèn)該 IP 地址。)
- 搜索操作系統(tǒng)自身的 DNS 緩存。(瀏覽器沒(méi)有找到緩存或者緩存已經(jīng)失效)
讀取硬盤(pán)中的 host 文件,里面記錄著域名到 IP 地址的映射關(guān)系,Mac 電腦中位于 /etc/hosts。(如果前1.2步驟都沒(méi)有找到) - 瀏覽器向?qū)拵н\(yùn)營(yíng)商服務(wù)器或者域名服務(wù)器發(fā)起一個(gè) DNS 解析請(qǐng)求,這里服務(wù)器有兩種方式解析請(qǐng)求,這在稍后會(huì)講到,之后瀏覽器獲得了百度首頁(yè)的 IP 地址。
- 拿到 IP 地址后,瀏覽器就向該 IP 所在的服務(wù)器建立 TCP 連接(即三次握手)。
- 連接建立起來(lái)之后,瀏覽器就可以向服務(wù)器發(fā)起 HTTP 請(qǐng)求了。(這里比如訪問(wèn)百度首頁(yè),就向服務(wù)器發(fā)起 HTTP 中的 GET 請(qǐng)求)
- 服務(wù)器接受到這個(gè)請(qǐng)求后,根據(jù)路徑參數(shù),經(jīng)過(guò)后臺(tái)一些處理之后,把處理后的結(jié)果返回給瀏覽器,如果是百度首頁(yè),就可以把完整的 HTML 頁(yè)面代碼返回給瀏覽器。
- 瀏覽器拿到了百度首頁(yè)的完整 HTML 頁(yè)面代碼,內(nèi)核和 JS 引擎就會(huì)解析和渲染這個(gè)頁(yè)面,里面的 JS,CSS,圖片等靜態(tài)資源也通過(guò)一個(gè)個(gè) HTTP 請(qǐng)求進(jìn)行加載。
- 瀏覽器根據(jù)拿到的資源對(duì)頁(yè)面進(jìn)行渲染,最終把完整的頁(yè)面呈現(xiàn)給用戶(hù)。如果瀏覽器沒(méi)有后續(xù)的請(qǐng)求,那么就會(huì)跟服務(wù)器端發(fā)起 TCP 斷開(kāi)(即四次揮手)。