對瀏覽器原理有過了解的一定不會(huì)陌生這篇神文《How Browsers Work》中文翻譯:瀏覽器原理:新式網(wǎng)絡(luò)瀏覽器幕后揭秘。另外還有一篇 《What really happens when you navigate to a URL》。大神寫的東西很長很復(fù)雜,閱讀成本雖然大,但能學(xué)到東西。所以,我也試著用自己的理解去寫一寫,算是做個(gè)鞏固。里面有很多參考,如涉及版權(quán),侵權(quán)刪!表述有誤,請指正!
前端為什么要研究渲染原理?
像素完美(Pixel Perfection)、分辨率無關(guān)(Resolution Independent)和多平臺體驗(yàn)一致性是設(shè)計(jì)師的追求。而訪問性(Accessability)、加載性能和重構(gòu)靈活性則是前端工程師必懂的技能。最重要的加載性能又與瀏覽器渲染機(jī)制深深掛鉤,所以只有弄明白了瀏覽器背后的渲染機(jī)制,才能在日常的前端開發(fā)中明白如何進(jìn)行性能優(yōu)化。
還有就是像類似:
從輸入 URL 到頁面加載完成的過程中都發(fā)生了什么事情?
從按下鍵盤到屏幕上出現(xiàn)字符,中間都發(fā)生了什么事情?
用戶反應(yīng)網(wǎng)站卡,請問都有哪些可能性,以及解決方法?
這樣的問題面試官都是比較喜歡問的,今天嘗試來詳細(xì)說一下這個(gè)過程。
從輸入 URL 到頁面加載完成的過程中都發(fā)生了什么?
簡單路徑線:
- 鍵盤或觸屏輸入U(xiǎn)RL并回車確認(rèn)
- URL解析/DNS解析查找域名IP地址
- 網(wǎng)絡(luò)連接發(fā)起HTTP請求
- HTTP報(bào)文傳輸過程
- 服務(wù)器接收數(shù)據(jù)
- 服務(wù)器響應(yīng)請求/MVC
- 服務(wù)器返回?cái)?shù)據(jù)
- 客戶端接收數(shù)據(jù)
- 瀏覽器加載/渲染頁面
- 打印繪制輸出
實(shí)際上并沒有這么簡單,下面說說它的詳細(xì)路徑線:
1. 鍵盤或觸屏輸入U(xiǎn)RL并回車確認(rèn)
當(dāng)然故事其實(shí)并不是從輸入一個(gè)URL或抓著鼠標(biāo)點(diǎn)擊一個(gè)鏈接開始的,事情的開端要追溯到服務(wù)器啟動(dòng)監(jiān)聽服務(wù)的時(shí)候,在某個(gè)未知的時(shí)刻,一臺機(jī)房里普普通通的服務(wù)器,加上電,啟動(dòng)了操作系統(tǒng),隨著操作系統(tǒng)的就緒,服務(wù)器啟動(dòng)了 http 服務(wù)進(jìn)程,這個(gè) http 服務(wù)的守護(hù)進(jìn)程(daemon),可能是 Apache、Nginx、IIS、Lighttpd中的一個(gè),不管怎么說,這個(gè) http 服務(wù)進(jìn)程開始定位到服務(wù)器上的 www 文件夾(網(wǎng)站根目錄),一般是位于 /var/www ,然后啟動(dòng)了一些附屬的模塊,例如 php,或者,使用 fastcgi 方式連接到 php 的 fpm 管理進(jìn)程,然后,向操作系統(tǒng)申請了一個(gè) tcp 連接,然后綁定在了 80 端口,調(diào)用了 accept 函數(shù),開始了默默的監(jiān)聽,監(jiān)聽著可能來自位于地球任何一個(gè)地方的請求,隨時(shí)準(zhǔn)備做出響應(yīng)。這個(gè)時(shí)候,典型的情況下,機(jī)房里面應(yīng)該還有一個(gè)數(shù)據(jù)庫服務(wù)器,或許,還有一臺緩存服務(wù)器,如果對于流量巨大的網(wǎng)站,那么動(dòng)態(tài)腳本的解釋器可能還有單獨(dú)的物理機(jī)器來跑,如果是中小的站點(diǎn),那么,上述的各色服務(wù),甚至都可能在一臺物理機(jī)上,這些服務(wù)監(jiān)聽之間的關(guān)系,可以通過自己搭建一次 Apache PHP MySQL 環(huán)境來了解一下,不管怎么說,他們做好了準(zhǔn)備,靜候差遣。
然后是開始鍵盤或手機(jī)觸屏輸入U(xiǎn)RL,然后通過某種機(jī)制傳到CPU(過程略),CPU進(jìn)行內(nèi)部處理(過程略),處理完后,再從CPU傳到操作系統(tǒng)內(nèi)核(過程略),然后再由操作系統(tǒng)GUI傳到瀏覽器,再由瀏覽器到瀏覽器內(nèi)核。這個(gè)過程因?yàn)樯婕昂芏嗟讓拥闹R,自己也只是了解皮毛,過程這里不多講了,具體請參考我的另一篇博客《字符集歷史和亂碼問題》和以下書籍:
《編碼》
《操作系統(tǒng)概念》
《CPU自制入門》
《計(jì)算機(jī)體系結(jié)構(gòu)》
《Linux內(nèi)核設(shè)計(jì)與實(shí)現(xiàn)》
《精通Linux設(shè)備驅(qū)動(dòng)程序開發(fā)》
《計(jì)算機(jī)體系結(jié)構(gòu):量化研究方法》
《計(jì)算機(jī)組成與設(shè)計(jì):硬件/軟件接口》
上面一步操作系統(tǒng) GUI 會(huì)將輸入事件傳遞到瀏覽器中,在這過程中,瀏覽器可能會(huì)做一些預(yù)處理,甚至已經(jīng)在智能匹配所有可能的URL了,他會(huì)從歷史記錄,書簽等地方,找到已經(jīng)輸入的字符串可能對應(yīng)的URL,來預(yù)估所輸入字符對應(yīng)的網(wǎng)站,然后給出智能提示,比如輸入了「ba」,根據(jù)之前的歷史發(fā)現(xiàn) 90% 的概率會(huì)訪問「www.baidu.com 」,因此就會(huì)在輸入回車前就馬上開始建立 TCP 鏈接了。對于 Chrome這種變態(tài)的瀏覽器,他甚至?xí)苯訌木彺嬷邪丫W(wǎng)頁渲染出來,就是說,你還沒有按下「回車」鍵,頁面就已經(jīng)出來了,再比如Chrome會(huì)在瀏覽器啟動(dòng)時(shí)預(yù)先查詢10個(gè)你有可能訪問的域名等等,這里面還有很多其它策略,不詳細(xì)講了。感興趣的推薦閱讀 High Performance Networking in Chrome。
2. URL 解析/DNS 查詢
接著是輸入 URL 「回車」后,這時(shí)瀏覽器會(huì)對 URL 進(jìn)行檢查,這里需要對URL有個(gè)回顧,請見百科《URL》,完整的URL由幾個(gè)部分構(gòu)成:
協(xié)議、網(wǎng)絡(luò)地址、資源路徑、文件名、動(dòng)態(tài)參數(shù)
協(xié)議/模式(scheme)是從該計(jì)算機(jī)獲取資源的方式,一般有Http、Https、Ftp、File、Mailto、Telnet、News等協(xié)議,不同協(xié)議有不同的通訊內(nèi)容格式,協(xié)議主要作用是告訴瀏覽器如何處理將要打開的文件;
網(wǎng)絡(luò)地址指示該連接網(wǎng)絡(luò)上哪一臺計(jì)算機(jī)(服務(wù)器),可以是域名或者IP地址,域名或IP地址后面有時(shí)還跟一個(gè)冒號和一個(gè)端口號;
端口號如果地址不包含端口號,根據(jù)協(xié)議的類型會(huì)確定一個(gè)默認(rèn)端口號。端口號之于計(jì)算機(jī)就像窗口號之于銀行,一家銀行有多個(gè)窗口,每個(gè)窗口都有個(gè)號碼,不同窗口可以負(fù)責(zé)不同的服務(wù)。端口只是一個(gè)邏輯概念,和計(jì)算機(jī)硬件沒有關(guān)系。一般如果你的端口號就是默認(rèn)的,那么url是不需要輸入端口號的,但如果你更改了默認(rèn)端口號,你就必須要在url后輸入新端口號才能正常訪問。例如:http協(xié)議默認(rèn)端口號是80。如果你輸入的url是http://www.zhihu.com:8080/ ,那表示不使用默認(rèn)的端口號,而使用指定的端口號8080。如果使用的就是默認(rèn)端口號那么輸入http://www.zhihu.com:80 和http://www.zhihu.com是一樣的。有個(gè)特殊情況有所不同,比如本地IP 127.0.0.1 其實(shí)走的是 loopback,和網(wǎng)卡設(shè)備沒關(guān)系。
資源路徑指示從服務(wù)器上獲取哪一項(xiàng)資源的等級結(jié)構(gòu)路徑,以斜線/分隔;
文件名一般是需要真正訪問的文件,有時(shí)候,URL以斜杠“/”結(jié)尾,而隱藏了文件名,在這種情況下,URL引用路徑中最后一個(gè)目錄中的默認(rèn)文件(通常對應(yīng)于主頁),這個(gè)文件常被稱為 index.html 或 default.htm。
動(dòng)態(tài)參數(shù)有時(shí)候路徑后面會(huì)有以問號?開始的參數(shù),這一般都是用來傳送對服務(wù)器上的數(shù)據(jù)庫進(jìn)行動(dòng)態(tài)詢問時(shí)所需要的參數(shù),有時(shí)候沒有,很多為了seo優(yōu)化,都已處理成偽靜態(tài)了。要注意區(qū)分url和路由的區(qū)別。
URL完整格式為:協(xié)議://用戶名:密碼@子域名.域名.頂級域名:端口號/目錄/文件名.文件后綴?參數(shù)=值#標(biāo)志
例如:https://www.zhihu.com/question/55998388/answer/166987812
協(xié)議部分:https
網(wǎng)絡(luò)地址:www.zhihu.com(依次為 子/三級域名.二級域名.頂/一級域名)
資源路徑:/question/55998388/answer/166987812
瀏覽器對 URL 進(jìn)行檢查時(shí)首先判斷協(xié)議,如果是 http/https 就按照 Web 來處理,另外還會(huì)對 URL 進(jìn)行安全檢查,然后直接調(diào)用瀏覽器內(nèi)核中的對應(yīng)方法,接下來是對網(wǎng)絡(luò)地址進(jìn)行處理,如果地址不是一個(gè)IP地址而是域名則通過DNS(域名系統(tǒng))將該地址解析成IP地址。IP地址對應(yīng)著網(wǎng)絡(luò)上一臺計(jì)算機(jī),DNS服務(wù)器本身也有IP,你的網(wǎng)絡(luò)設(shè)置包含DNS服務(wù)器的IP。 例如:www.zhihu.com域名請求對應(yīng)獲得的IP是 116.211.167.187。DNS 在解析域名的時(shí)候有兩種方式:遞歸查詢和迭代查詢,
遞歸查詢的流程如下:
一般來說,瀏覽器會(huì)首先查詢瀏覽器緩存(DNS 在各個(gè)層級都有緩存的,相應(yīng)的,緩存當(dāng)然有過期時(shí)間,Time to live),如果沒有找到,就會(huì)檢查系統(tǒng)緩存,檢查本地硬盤的hosts文件,這個(gè)文件保存了一些以前訪問過的網(wǎng)站的域名和IP對應(yīng)的數(shù)據(jù)。它就像是一個(gè)本地的數(shù)據(jù)庫。如果找到就可以直接獲取目標(biāo)主機(jī)的IP地址了(注意這個(gè)地方存在安全隱患,如果有病毒把一些常用的域名,修改 hosts 文件,指向一些惡意的IP,那么瀏覽器也會(huì)不加判斷的去連接,是的,這正是很多病毒的慣用手法)。如果本地hosts也沒有找到的話,則需要再向上層找路由器緩存,路由器有自己的DNS緩存,可能就包括了查詢的內(nèi)容;如果還是沒有,需要接著往上找,查詢ISP DNS 緩存(本地名稱服務(wù)器緩存,就是客戶端電腦TCP/IP參數(shù)中設(shè)置的首選DNS服務(wù)器,此解析具有權(quán)威性。一般情況下你在不同的地區(qū)或者不同的網(wǎng)絡(luò),如電信、聯(lián)通、移動(dòng)的情況下,轉(zhuǎn)換后的IP地址很可能是不一樣的,這涉及到負(fù)載均衡,通過DNS解析域名時(shí)會(huì)將你的訪問分配到不同的入口,先找附近的本地 DNS 服務(wù)器去請求解析域名,盡可能保證你所訪問的入口是所有入口中較快的一個(gè),這和CDN還不一樣,比如我們經(jīng)常使用的114.114.114.114或Google的8.8.8.8就是本地名稱服務(wù)器)。如果附近的本地DNS服務(wù)器還是沒有緩存我們請求的域名記錄的話,這時(shí)候會(huì)根據(jù)本地DNS服務(wù)器的設(shè)置(是否設(shè)置轉(zhuǎn)發(fā)器)進(jìn)行查詢,如果未用轉(zhuǎn)發(fā)模式,則本地名稱服務(wù)器再以DNS客戶端的角色發(fā)送與前面一樣的DNS域名查詢請求轉(zhuǎn)發(fā)給上一層。這里可能經(jīng)過一次或者多次轉(zhuǎn)發(fā),從本地名稱服務(wù)器到權(quán)威名稱服務(wù)器再到頂級名稱服務(wù)器最后到根名稱服務(wù)器。(順便一提,根服務(wù)器是互聯(lián)網(wǎng)域名解析系統(tǒng)DNS中最高級別的域名服務(wù)器,全球一共13組,每組都只有一個(gè)主根名稱服務(wù)器采用同一個(gè)IP。注意不是13個(gè),前期是個(gè)現(xiàn)在已經(jīng)是集群了,據(jù)說已經(jīng)有上千臺了,好多臺用于負(fù)載均衡,備份等,全球有386臺根物理服務(wù)器,被編號為A到M共13個(gè)標(biāo)號。中國包括臺港也持有其中5組14臺輔根服務(wù)器或叫鏡像也可以,386臺根服務(wù)器總共只使用了13個(gè)IP,因此可以抵抗針對其所進(jìn)行的分布式拒絕服務(wù)攻擊DDoS。具體情況可以參看維基百科的 根域名服務(wù)器 條目)所以,最終請求到了根服務(wù)器后,根服務(wù)器查詢發(fā)現(xiàn)我們這個(gè)被請求的域名是由類似A或者B這樣的服務(wù)器解析的,但是,根服務(wù)器并不會(huì)送佛送到西地找A或B之類的直接去解析,因?yàn)樗鼪]有保存全部互聯(lián)網(wǎng)域名記錄,并不直接用于名稱解析,它只是負(fù)責(zé)頂級名稱服務(wù)器(如.com/.cn/.net等)的相關(guān)內(nèi)容。所以它會(huì)把所查詢得到的被請求的DNS域名中頂級域名所對應(yīng)的頂級名稱服務(wù)器IP地址返回給本地名稱服務(wù)器。本地名稱服務(wù)器拿到地址后再向?qū)?yīng)的頂級名稱服務(wù)器發(fā)送與前面一樣的DNS域名查詢請求。對應(yīng)的頂級名稱服務(wù)器在收到DNS查詢請求后,也是先查詢自己的緩存,如果有則直接把對應(yīng)的記錄項(xiàng)返回給本地名稱服務(wù)器,然后再由本地名稱服務(wù)器返回給DNS客戶端,如果沒有則向本地名稱服務(wù)器返回所請求的DNS域名中的二級域名所對應(yīng)的二級名稱服務(wù)器(如baidu.com/qq.com/net.cn等)地址。然后本地名稱服務(wù)器繼續(xù)按照前面介紹的方法一次次地向三級(如www.baidu.com/www.qq.com/bbs.taobao.com等)、四級名稱服務(wù)器查詢,直到最終的對應(yīng)域名所在區(qū)域的權(quán)威名稱服務(wù)器返回最終記錄給本地名稱服務(wù)器。同時(shí)本地名稱服務(wù)器會(huì)緩存本次查詢得到的記錄項(xiàng)(每層都應(yīng)該會(huì)緩存)。再層層下傳,最后到了我們的DNS客戶端機(jī)子,一次 DNS 解析請求就此完成。如果最終權(quán)威名稱服務(wù)器都說找不到對應(yīng)的域名記錄,則會(huì)向本地名稱服務(wù)器返回一條查詢失敗的DNS應(yīng)答報(bào)文,這條報(bào)文最終也會(huì)由本地名稱服務(wù)器返回給DNS客戶端。當(dāng)然,如果這個(gè)權(quán)威名稱服務(wù)器上配置了指向其它名稱服務(wù)器的轉(zhuǎn)發(fā)器,則權(quán)威名稱服務(wù)器還會(huì)在轉(zhuǎn)發(fā)器指向的名稱服務(wù)器上進(jìn)一步查詢。另外,如果DNS客戶端上配置了多個(gè)DNS服務(wù)器,則還會(huì)繼續(xù)向其它DNS服務(wù)器查詢的。

所以,我們看到DNS的域名解析是遞歸的,遞歸的DNS首先會(huì)查看自己的DNS緩存,如果緩存能夠命中,那么就從緩存中把IP地址返回給瀏覽器,如果找不到對應(yīng)的域名的IP地址,那么就依此層層向上轉(zhuǎn)發(fā)請求,從根域名服務(wù)器到頂級域名服務(wù)器再到極限域名服務(wù)器依次搜索對應(yīng)目標(biāo)域名的IP,最高達(dá)到根節(jié)點(diǎn),找到或者全部找不到為止。然后把找到的這個(gè)域名對應(yīng)的 nameserver 的地址拿到,再向這個(gè) namserver 去請求域名對應(yīng)的IP,最后把這個(gè)IP地址返回給瀏覽器,在這個(gè)遞歸查詢的過程中,對于瀏覽器來說是透明的,如果DNS客戶端的本地名稱服務(wù)器不能解析的話,則后面的查詢都會(huì)以本地名稱服務(wù)器為中心,全交由本地名稱服務(wù)器代替DNS客戶端進(jìn)行查詢,DNS客戶端只是發(fā)出原始的域名查詢請求報(bào)文,然后就一直處于坐等狀態(tài),直到本地名稱服務(wù)器最終從權(quán)威名稱服務(wù)器得到了正確的IP地址查詢結(jié)果并返回給它。雖然遞歸查詢是默認(rèn)的DNS查詢方式,但是如果有以下情況發(fā)生的話,則會(huì)使用迭代的查詢方式進(jìn)行。
情況一:DNS客戶端的請求報(bào)文中沒有申請使用遞歸查詢,即在DNS請求報(bào)頭部的RD字段沒有置1。
情況二:DNS客戶端的請求報(bào)文中申請使用的是遞歸查詢(也就是RD字段置1了),但在所配置的本地名稱服務(wù)器上是禁用遞歸查詢了(即在應(yīng)答DNS報(bào)文頭部的RA字段置0)。
迭代查詢的流程如下:
開始也是從瀏覽器緩存到系統(tǒng)緩存到路由緩存,如果還是沒找到則客戶端向本機(jī)配置的本地名稱服務(wù)器(在此僅以首先DNS服務(wù)器為例進(jìn)行介紹,其它備用DNS服務(wù)器的解析流程完全一樣)發(fā)出DNS域名查詢請求。本地名稱服務(wù)器收到請求后,先查詢本地的緩存,如果有該域名的記錄項(xiàng),則本地名稱服務(wù)器就直接把查詢的結(jié)果返回給客戶端;如果本地緩存中沒有該域名的記錄,則向DNS客戶端返回一條DNS應(yīng)答報(bào)文,報(bào)文中會(huì)給出一些參考信息,如本地名稱服務(wù)器上的根名稱服務(wù)器地址等。DNS客戶端在收到本地名稱服務(wù)器的應(yīng)答報(bào)文后,會(huì)根據(jù)其中的根名稱服務(wù)器地址信息,向?qū)?yīng)的根名稱服務(wù)器再次發(fā)出與前面一樣的DNS查詢請求報(bào)文。根名稱服務(wù)器在收到DNS查詢請求報(bào)文后,通過查詢自己的DNS數(shù)據(jù)庫得到請求DNS域名中頂級域名所對應(yīng)的頂級名稱服務(wù)器信息,然后以一條DNS應(yīng)答報(bào)文返回給DNS客戶端。DNS客戶端根據(jù)來自根名稱服務(wù)器應(yīng)答報(bào)文中的對應(yīng)頂級名稱服務(wù)器地址信息,向該頂級名稱服務(wù)器發(fā)出與前面一樣的DNS查詢請求報(bào)文。頂級名稱服務(wù)器在收到DNS查詢請求后,先查詢自己的緩存,如果有請求的DNS域名的記錄項(xiàng),則直接把對應(yīng)的記錄項(xiàng)返回給DNS客戶端,否則通過查詢后把對應(yīng)域名中二級域名所對應(yīng)的二級名稱服務(wù)器地址信息以一條DNS應(yīng)答報(bào)文返回給DNS客戶端。然后DNS客戶端繼續(xù)按照前面介紹的方法一次次地向三級、四級名稱服務(wù)器查詢,直到最終的權(quán)威名稱服務(wù)器返回到最終的記錄。如果權(quán)威名稱服務(wù)器也找不到對應(yīng)的域名記錄,則會(huì)向DNS客戶端返回一條查詢失敗的DNS應(yīng)答報(bào)文。當(dāng)然,如果這個(gè)權(quán)威名稱服務(wù)器上配置了指向其它名稱服務(wù)器的轉(zhuǎn)發(fā)器,則權(quán)威名稱服務(wù)器還會(huì)在轉(zhuǎn)發(fā)器指向的名稱服務(wù)器上進(jìn)一步查詢。另外,如果DNS客戶端上配置了多個(gè)DNS服務(wù)器,則還會(huì)繼續(xù)向其它DNS服務(wù)器查詢。

所以,我們發(fā)現(xiàn)在遞歸查詢中后面的查詢工作是由本地名稱服務(wù)器替代DNS客戶端進(jìn)行的(以“本地名稱服務(wù)器”為中心),只需要本地名稱服務(wù)器向DNS客戶端返回最終的查詢結(jié)果即可。而DNS迭代查詢的所有查詢工作則全部是DNS客戶端自己進(jìn)行(以“DNS客戶端”自己為中心)。
DNS遞歸查詢和迭代查詢的區(qū)別?
遞歸查詢是以本地名稱服務(wù)器為中心的,是DNS客戶端和服務(wù)器之間的查詢活動(dòng),遞歸查詢的過程中“查詢的遞交者” 一直在更替,其結(jié)果是直接告訴DNS客戶端需要查詢的網(wǎng)站目標(biāo)IP地址。
迭代查詢則是DNS客戶端自己為中心的,是各個(gè)服務(wù)器和服務(wù)器之間的查詢活動(dòng),迭代查詢的過程中“查詢的遞交者”一直沒變化,其結(jié)果是間接告訴DNS客戶端另一個(gè)DNS服務(wù)器的地址。

舉個(gè)例子來說,一次選修課上你碰到了你的女神,但你只知道她的名字并不知道她的電話,于是在手機(jī)通訊錄里找,沒找到,然后你回到寢室把她名字告訴了一個(gè)很仗義的哥們,讓他幫你找,這個(gè)哥們兒(本地名稱服務(wù)器)二話沒說,開始替你查(此處完成了一次遞歸查詢,即問詢的人由你變成了你的哥們)。然后你哥們帶著名字去問了學(xué)院大四的學(xué)長,學(xué)長一看,我擦,這姑娘我認(rèn)識啊,于是在手機(jī)里找到了她的電話告訴了你哥們,你哥們回來告訴了你,你于是知道了她的電話(這里一次遞歸查詢結(jié)束)。還有一種可能你哥們跑去問學(xué)長,學(xué)長也沒她電話,但學(xué)長告訴你哥們,這姑娘是xx系的;然后你哥們兒馬不停蹄又問了xx系的辦公室主任助理同學(xué),助理同學(xué)說是xx系yy班的,然后很仗義的哥們兒去xx系yy班的班長那里取到了該女孩的電話(此處完成若干次迭代查詢,即問詢的人一直是你哥們不變,但反復(fù)更替的是問詢對象)。最后,他把號碼交到了你手里,完成整個(gè)查詢過程。
擴(kuò)展閱讀:
什么是DNS劫持?
什么是301重定向?與301重定向設(shè)置教程
電腦上不了網(wǎng)將DNS改為114.114.114.114或8.8.8.8可以解決或加快網(wǎng)速的原理是什么?
局域網(wǎng) IP 和公網(wǎng) IP 有何差別?
根域名服務(wù)器的作用是什么?全球 13 組根域名服務(wù)器中有 10 組在美國,意味著什么?
遞歸和迭代的區(qū)別?
3. 應(yīng)用層客戶端發(fā)送HTTP請求
互聯(lián)網(wǎng)內(nèi)各網(wǎng)絡(luò)設(shè)備間的通信都遵循TCP/IP協(xié)議,利用TCP/IP協(xié)議族進(jìn)行網(wǎng)絡(luò)通信時(shí),會(huì)通過分層順序與對方進(jìn)行通信。分層由高到低分別為:應(yīng)用層、傳輸層、網(wǎng)絡(luò)層、數(shù)據(jù)鏈路層。發(fā)送端從應(yīng)用層往下走,接收端從數(shù)據(jù)鏈路層網(wǎng)上走。如圖所示:

從上面的步驟中得到 IP 地址后,瀏覽器會(huì)開始構(gòu)造一個(gè) HTTP 請求,應(yīng)用層客戶端向服務(wù)器端發(fā)送的HTTP請求包括:請求報(bào)頭和請求主體兩個(gè)部分,其中請求報(bào)頭(request header)包含了至關(guān)重要的信息,包括請求的方法(GET / POST和不常用的PUT / DELETE以及更不常用的HEAD / OPTION / TRACE,一般的瀏覽器只能發(fā)起 GET 或者 POST 請求)、目標(biāo)url、遵循的協(xié)議(HTTP / HTTPS / FTP…),返回的信息是否需要緩存,以及客戶端是否發(fā)送Cookie等信息。需要注意的是,因?yàn)?HTTP 請求是純文本格式的,所以在 TCP 的數(shù)據(jù)段中可以直接分析 HTTP 文本的。
4. 傳輸層TCP傳輸報(bào)文
當(dāng)應(yīng)用層的 HTTP 請求準(zhǔn)備好后,瀏覽器會(huì)在傳輸層發(fā)起一條到達(dá)服務(wù)器的 TCP 連接,位于傳輸層的TCP協(xié)議為傳輸報(bào)文提供可靠的字節(jié)流服務(wù)。它為了方便傳輸,將大塊的數(shù)據(jù)分割成以報(bào)文段為單位的數(shù)據(jù)包進(jìn)行管理,并為它們編號,方便服務(wù)器接收時(shí)能準(zhǔn)確地還原報(bào)文信息。TCP協(xié)議通過“三次握手”等方法保證傳輸?shù)陌踩煽俊!叭挝帐帧钡倪^程是,發(fā)送端先發(fā)送一個(gè)帶有SYN(synchronize)標(biāo)志的數(shù)據(jù)包給接收端,在一定的延遲時(shí)間內(nèi)等待接收的回復(fù)。接收端收到數(shù)據(jù)包后,傳回一個(gè)帶有SYN/ACK標(biāo)志的數(shù)據(jù)包以示傳達(dá)確認(rèn)信息。接收方收到后再發(fā)送一個(gè)帶有ACK標(biāo)志的數(shù)據(jù)包給接收端以示握手成功。在這個(gè)過程中,如果發(fā)送端在規(guī)定延遲時(shí)間內(nèi)沒有收到回復(fù)則默認(rèn)接收方?jīng)]有收到請求,而再次發(fā)送,直到收到回復(fù)為止。

這里需要談一下 TCP 的 Head-of-line blocking 問題:假設(shè)客戶端的發(fā)送了 3 個(gè) TCP 片段(segments),編號分別是 1、2、3,如果編號為 1 的包傳輸時(shí)丟了,即便編號 2 和 3 已經(jīng)到達(dá)也只能等待,因?yàn)?TCP 協(xié)議需要保證順序,這個(gè)問題在 HTTP pipelining 下更嚴(yán)重,因?yàn)?HTTP pipelining 可以讓多個(gè) HTTP 請求通過一個(gè) TCP 發(fā)送,比如發(fā)送兩張圖片,可能第二張圖片的數(shù)據(jù)已經(jīng)全收到了,但還得等第一張圖片的數(shù)據(jù)傳到。為了解決 TCP 協(xié)議的性能問題,Chrome 團(tuán)隊(duì)提出了 QUIC 協(xié)議,它是基于 UDP 實(shí)現(xiàn)的可靠傳輸,比起 TCP,它能減少很多來回(round trip)時(shí)間,還有前向糾錯(cuò)碼(Forward Error Correction)等功能。目前 Google Plus、 Gmail、Google Search、blogspot、Youtube 等幾乎大部分 Google 產(chǎn)品都在使用 QUIC,可以通過chrome://net-internals/#spdy 頁面來發(fā)現(xiàn)。另外,瀏覽器對同一個(gè)域名有連接數(shù)限制,大部分是 6,但并非將這個(gè)連接數(shù)改大后就會(huì)提升性能,Chrome 團(tuán)隊(duì)有做過實(shí)驗(yàn),發(fā)現(xiàn)從 6 改成 10 后性能反而下降了,造成這個(gè)現(xiàn)象的因素有很多,如建立連接的開銷、擁塞控制等問題,而像 SPDY、HTTP 2.0 協(xié)議盡管只使用一個(gè) TCP 連接來傳輸數(shù)據(jù),但性能反而更好,而且還能實(shí)現(xiàn)請求優(yōu)先級。
5. 網(wǎng)絡(luò)層IP協(xié)議查詢MAC地址
IP協(xié)議的作用是把TCP分割好的各種數(shù)據(jù)包封裝到IP包里面?zhèn)魉徒o接收方。而要保證確實(shí)能傳到接收方還需要接收方的MAC地址,也就是物理地址才可以。IP地址和MAC地址是一一對應(yīng)的關(guān)系,一個(gè)網(wǎng)絡(luò)設(shè)備的IP地址可以更換,但是MAC地址一般是固定不變的。ARP協(xié)議可以將IP地址解析成對應(yīng)的MAC地址。當(dāng)通信的雙方不在同一個(gè)局域網(wǎng)時(shí),需要多次中轉(zhuǎn)才能到達(dá)最終的目標(biāo),在中轉(zhuǎn)的過程中需要通過下一個(gè)中轉(zhuǎn)站的MAC地址來搜索下一個(gè)中轉(zhuǎn)目標(biāo)。
6. 數(shù)據(jù)到達(dá)數(shù)據(jù)鏈路層
在找到對方的MAC地址后,已被封裝好的IP包再被封裝到數(shù)據(jù)鏈路層的數(shù)據(jù)幀結(jié)構(gòu)中,將數(shù)據(jù)發(fā)送到數(shù)據(jù)鏈路層傳輸,再通過物理層的比特流送出去。這時(shí),客戶端發(fā)送請求的階段結(jié)束。
這些分層的意義在于分工合作,數(shù)據(jù)鏈路層通過 CSMA/CD 協(xié)議保證了相鄰兩臺主機(jī)之間的數(shù)據(jù)報(bào)文傳遞,而網(wǎng)絡(luò)層的 IP 數(shù)據(jù)包通過不同子網(wǎng)之間的路由器的路由算法和路由轉(zhuǎn)發(fā),保證了互聯(lián)網(wǎng)上兩臺遙遠(yuǎn)主機(jī)之間的點(diǎn)對點(diǎn)的通訊,不過這種傳輸是不可靠,于是可靠性就由傳輸層的 TCP 協(xié)議來保證,TCP 通過慢開始,乘法減小等手段來進(jìn)行流量控制和擁塞避免,同時(shí)提供了兩臺遙遠(yuǎn)主機(jī)上進(jìn)程到進(jìn)程的通信,最終保證了 HTTP 的請求頭能夠被遠(yuǎn)方的服務(wù)器上正在監(jiān)聽的 HTTP 服務(wù)器進(jìn)程收到,終于,數(shù)據(jù)包在跳與跳之間被拆了又封裝,在子網(wǎng)與子網(wǎng)之間被轉(zhuǎn)發(fā)了又轉(zhuǎn)發(fā),最后進(jìn)入了服務(wù)器的操作系統(tǒng)的緩沖區(qū),服務(wù)器的操作系統(tǒng)由此給正在被阻塞住的 accept 函數(shù)一個(gè)返回,將他喚醒。
7. 服務(wù)器接收數(shù)據(jù)
接收端的服務(wù)器在鏈路層接收到數(shù)據(jù)包,再層層向上直到應(yīng)用層。這過程中包括在傳輸層通過TCP協(xié)議將分段的數(shù)據(jù)包重新組成原來的HTTP請求報(bào)文。
8. 服務(wù)器響應(yīng)請求并返回相應(yīng)文件
服務(wù)接收到客戶端發(fā)送的HTTP請求后,服務(wù)器上的的 http 監(jiān)聽進(jìn)程會(huì)得到這個(gè)請求,然后一般情況下會(huì)啟動(dòng)一個(gè)新的子進(jìn)程去處理這個(gè)請求,同時(shí)父進(jìn)程繼續(xù)監(jiān)聽。http 服務(wù)器首先會(huì)查看重寫規(guī)則,然后如果請求的文件是真實(shí)存在,例如一些圖片,或 html、css、js 等靜態(tài)文件,則會(huì)直接把這個(gè)文件返回,如果是一個(gè)動(dòng)態(tài)的請求,那么會(huì)根據(jù) url 重寫模塊的規(guī)則,把這個(gè)請求重寫到一個(gè) rest 風(fēng)格的 url 上,然后根據(jù)動(dòng)態(tài)語言的腳本,來決定調(diào)用什么類型的動(dòng)態(tài)文件腳本解釋器來處理這個(gè)請求。
我們以 php 語言為例來說的話,請求到達(dá)一個(gè) php 的 mvc 框架之后,框架首先應(yīng)該會(huì)初始化一些環(huán)境的參數(shù),例如遠(yuǎn)端 ip,請求參數(shù)等等,然后根據(jù)請求的 url 送到一個(gè)路由器類里面去匹配路由,路由由上到下逐條匹配,一旦遇到 url 能夠匹配的上,而且請求的方法也能夠命中的話,那么請求就會(huì)由這個(gè)路由所定義的處理方法去處理。
請求進(jìn)入處理函數(shù)之后,如果客戶端所請求需要瀏覽的內(nèi)容是一個(gè)動(dòng)態(tài)的內(nèi)容,那么處理函數(shù)會(huì)相應(yīng)的從數(shù)據(jù)源里面取出數(shù)據(jù),這個(gè)地方一般會(huì)有一個(gè)緩存,例如 memcached 來減小 db 的壓力,如果引入了 orm 框架的話,那么處理函數(shù)直接向 orm 框架索要數(shù)據(jù)就可以了,由 orm 框架來決定是使用內(nèi)存里面的緩存還是從 db 去取數(shù)據(jù),一般緩存都會(huì)有一個(gè)過期的時(shí)間,而 orm 框架也會(huì)在取到數(shù)據(jù)回來之后,把數(shù)據(jù)存一份在內(nèi)存緩存中的。
orm 框架負(fù)責(zé)把面向?qū)ο蟮恼埱蠓g成標(biāo)準(zhǔn)的 sql 語句,然后送到后端的 db 去執(zhí)行,db 這里以 mysql 為例的話,那么一條 sql 進(jìn)來之后,db 本身也是有緩存的,不過 db 的緩存一般是用 sql 語言 hash 來存取的,也就是說,想要緩存能夠命中,除了查詢的字段和方法要一樣以外,查詢的參數(shù)也要完全一模一樣才能夠使用 db 本身的查詢緩存,sql 經(jīng)過查詢緩存器,然后就會(huì)到達(dá)查詢分析器,在這里,db 會(huì)根據(jù)被搜索的數(shù)據(jù)表的索引建立情況,和 sql 語言本身的特點(diǎn),來決定使用哪一個(gè)字段的索引,值得一提的是,即使一個(gè)數(shù)據(jù)表同時(shí)在多個(gè)字段建立了索引,但是對于一條 sql 語句來說,還是只能使用一個(gè)索引,所以這里就需要分析使用哪個(gè)索引效率最高了,一般來說,sql 優(yōu)化在這個(gè)點(diǎn)上也是很重要的一個(gè)方面。
sql 由 db 返回結(jié)果集后,再由 orm 框架把結(jié)果轉(zhuǎn)換成模型對象,然后由 orm 框架進(jìn)行一些邏輯處理,把準(zhǔn)備好的數(shù)據(jù),送到視圖層的渲染引擎去渲染,渲染引擎負(fù)責(zé)模板的管理,字段的友好顯示,也包括負(fù)責(zé)一些多國語言之類的任務(wù)。對于一條請求在 mvc 中的生命周期,可以參考這里,臨摹了一個(gè) PHP MVC 框架,在視圖層把頁面準(zhǔn)備好后,再從動(dòng)態(tài)腳本解釋器送回到 http 服務(wù)器,由 http 服務(wù)器把這些正文加上一個(gè)響應(yīng)頭,封裝成一個(gè)標(biāo)準(zhǔn)的 http 響應(yīng)包,再通過 tcp ip 協(xié)議,送回到客戶機(jī)瀏覽器。
9)瀏覽器開始處理數(shù)據(jù)信息并渲染頁面
歷經(jīng)千辛萬苦,我們請求的響應(yīng)終于成功到達(dá)了客戶端的瀏覽器,響應(yīng)到達(dá)瀏覽器之后,瀏覽器首先會(huì)根據(jù)返回的響應(yīng)報(bào)文里的一個(gè)重要信息——狀態(tài)碼,來做個(gè)判斷。如果是 200 開頭的就好辦,表示請求成功,直接進(jìn)入渲染流程,如果是 300 開頭的就要去相應(yīng)頭里面找 location 域,根據(jù)這個(gè) location 的指引,進(jìn)行跳轉(zhuǎn),這里跳轉(zhuǎn)需要開啟一個(gè)跳轉(zhuǎn)計(jì)數(shù)器,是為了避免兩個(gè)或者多個(gè)頁面之間形成的循環(huán)的跳轉(zhuǎn),當(dāng)跳轉(zhuǎn)次數(shù)過多之后,瀏覽器會(huì)報(bào)錯(cuò),同時(shí)停止。比如:301表示永久重定向,即請求的資源已經(jīng)永久轉(zhuǎn)移到新的位置。在返回301狀態(tài)碼的同時(shí),響應(yīng)報(bào)文也會(huì)附帶重定向的url,客戶端接收到后將http請求的url做相應(yīng)的改變再重新發(fā)送。如果是 400 開頭或者 500 開頭的狀態(tài)碼,瀏覽器也會(huì)給出一個(gè)錯(cuò)誤頁面。比如:404 not found 就表示客戶端請求的資源找不到。
當(dāng)瀏覽得到一個(gè)正確的 200 響應(yīng)之后,接下來面臨的一個(gè)問題就是多國語言的編碼解析了,響應(yīng)頭是一個(gè) ascii 的標(biāo)準(zhǔn)字符集的文本,這個(gè)還好辦,但是響應(yīng)的正文本質(zhì)上就是一個(gè)字節(jié)流,對于這一坨字節(jié)流,瀏覽器要怎么去處理呢?首先瀏覽器會(huì)去看響應(yīng)頭里面指定的 encoding 域,如果有了這個(gè)東西,那么就按照指定的 encoding 去解析字符,如果沒有的話,那么瀏覽器會(huì)使用一些比較智能的方式,去猜測和判斷這一坨字節(jié)流應(yīng)該使用什么字符集去解碼。相關(guān)的筆記可以看這里,字符集編碼
接下來就是構(gòu)建 dom 樹了,在 html 語言嵌套正常而且規(guī)范的情況下,這種 xml 標(biāo)記的語言是比較容易的能夠構(gòu)建出一棵 dom 樹出來的,當(dāng)然,對于互聯(lián)網(wǎng)上大量的不規(guī)范的頁面,不同的瀏覽器應(yīng)該有自己不同的容錯(cuò)去處理。構(gòu)建出來的 dom 本質(zhì)上還是一棵抽象的邏輯樹,構(gòu)建 dom 樹的過程中,如果遇到了由 script 標(biāo)簽包起來的 js 動(dòng)態(tài)腳本代碼,那么會(huì)把代碼送到 js 引擎里面去跑,如果遇到了 style 標(biāo)簽包圍起來的 css 代碼,也會(huì)保存下來,用于稍后的渲染。如果遇到了 img 或 css 和 js等引用外部文件的標(biāo)簽,那么瀏覽器會(huì)根據(jù)指定的 url 再次發(fā)起一個(gè)新的 http 請求,去把這個(gè)文件拉取回來,值得一提的是,對于同一個(gè)域名下的下載過程來說,瀏覽器一般允許的并發(fā)請求是有限的,通常控制在兩個(gè)左右,所以如果有很多的圖片的話,一般出于優(yōu)化的目的,都會(huì)把這些圖片使用一臺靜態(tài)文件的服務(wù)器來保存起來,負(fù)責(zé)響應(yīng),從而減少主服務(wù)器的壓力。
dom 樹構(gòu)造好了之后,就是根據(jù) dom 樹和 css 樣式表來構(gòu)造 render 樹了,這個(gè)才是真正的用于渲染到頁面上的一個(gè)一個(gè)的矩形框的樹,網(wǎng)頁渲染是瀏覽器最復(fù)雜、最核心的功能,對于 render 樹上每一個(gè)框,需要確定他的 x y 坐標(biāo),尺寸,邊框,字體,形態(tài),等等諸多方面的東西,render 樹一旦構(gòu)建完成,整個(gè)頁面也就準(zhǔn)備好了,可以上菜了。需要說明的是,下載頁面,構(gòu)建 dom 樹,構(gòu)建 render 樹這三個(gè)步驟,實(shí)際上并不是嚴(yán)格的先后順序的,為了加快速度,提高效率,讓用戶不要等那么久,現(xiàn)在一般都并行的往前推進(jìn)的,現(xiàn)代的瀏覽器都是一邊下載,下載到了一點(diǎn)數(shù)據(jù)就開始構(gòu)建 dom 樹,也一邊開始構(gòu)建 render 樹,構(gòu)建了一點(diǎn)就顯示一點(diǎn)出來,這樣用戶看起來就不用等待那么久了。
10)將渲染好的頁面圖像顯示出來,并開始響應(yīng)用戶的操作。
這一步主要涉及顯卡,內(nèi)存及顯示器原理等知識,不做詳細(xì)解說,大概就是從內(nèi)存到 LCD/LED,再由光線進(jìn)入人眼的一個(gè)過程。
以上過程簡單講主要是:從輸入 URL 到瀏覽器接收(回車前),從瀏覽器接收到數(shù)據(jù)如何發(fā)送給網(wǎng)卡(回車后),再把接收的數(shù)據(jù)從本機(jī)網(wǎng)卡發(fā)送到服務(wù)器,服務(wù)器接收到數(shù)據(jù)后做了怎么的處理?服務(wù)器返回?cái)?shù)據(jù)后瀏覽器又做了哪些處理?瀏覽器又是如何將處理好的頁面展現(xiàn)在屏幕上的?的這么一個(gè)過程。
但只是最基本的一些步驟,實(shí)際不可能就這么簡單,一些可選的步驟例如網(wǎng)頁緩存、連接池、加載策略、加密解密、代理中轉(zhuǎn)等等都沒有提及。即使基本步驟本身也有很復(fù)雜的子步驟,TCP/IP、DNS、HTTP、HTML等等,還需要考慮很多情況,比如廣播、拆包解包合并包丟包重傳、路由表,NAT、TCP 狀態(tài)機(jī)、CDN、HTTPS 證書校驗(yàn)與中間人攻擊檢測、RSA 密鑰協(xié)商、AES 加解密、瀏覽器解析 HTTP 的有限自動(dòng)狀態(tài)機(jī)、GUI 庫與繪圖、OpenGL 繪圖、GPU 加速(OpenCL 與 CUDA)、JIT(JavaScript 會(huì)把 JavaScript 代碼編譯成匯編代碼)、服務(wù)器的數(shù)據(jù)庫 NoSQL 或 SQL 查詢、主從數(shù)據(jù)庫同步、服務(wù)器和瀏覽器的內(nèi)存管理(WebKit 實(shí)現(xiàn)的 fastMalloc(),服務(wù)器上可能是 TCMalloc 或者 JeMalloc)、服務(wù)器上的語言解釋器(可能也是 JIT)、多媒體:傅里葉變換、H.264 解碼(硬件解碼,硬件解碼的話 GPU 的處理單元又在計(jì)算.......或軟件解碼)、音頻解碼、WebGL 繪圖、瀏覽器的 Sandbox、服務(wù)器的 SQL 注入檢查、產(chǎn)生的鍵盤中斷信號處理(或者是高級層面的輸入輸出驅(qū)動(dòng))、網(wǎng)卡驅(qū)動(dòng)、網(wǎng)絡(luò)棧的 TCP FastOpen、SYN Cookie 之類眾多技術(shù)……每一個(gè)都可以展開成龐大的課題,而瀏覽器的基礎(chǔ)——操作系統(tǒng)、編譯器、硬件等更是一個(gè)比一個(gè)復(fù)雜。即便是計(jì)算機(jī)專業(yè)的同學(xué)看了也會(huì)頭大,但我保證這里面的每一個(gè)步驟都經(jīng)過深思熟慮和時(shí)間的考驗(yàn)的,并不是誰閑的蛋疼非要搞得那么復(fù)雜,不復(fù)雜也不行啊。你輸入U(xiǎn)RL即可瀏覽互聯(lián)網(wǎng),而計(jì)算機(jī)系統(tǒng)在背后做了無數(shù)你看不到的工作,計(jì)算機(jī)各個(gè)子領(lǐng)域無數(shù)工程師為此付出了你難以想象的努力。有興趣的可以閱讀下
你剛才在淘寶上買了一件東西

http://coolshell.cn/articles/9666.html
http://m.itdecent.cn/p/e305ace24ddf
https://segmentfault.com/a/1190000005169412
https://www.chengrang.com/how-browsers-work.html
參考資料:
了解html頁面的渲染過程
前端工程師手冊
瀏覽器渲染那些事
手機(jī)上從輸入U(xiǎn)RL到頁面加載完成的過程中都發(fā)生了什么?
當(dāng)頁面渲染時(shí),瀏覽器發(fā)生了什么?
瀏覽器工作原理分析與首屏加載
https://www.youtube.com/watch?v=eeS4brbDVuU