大致流程
- 解析URL
- DNS域名解析
- TCP連接(三次握手)
- 發(fā)送HTTP請求
- 服務器處理請求,返回響應結(jié)果
- 瀏覽器解析文件并渲染頁面
- 斷開TCP連接(四次揮手)
一、URL解析
URL解析就是當在瀏覽器中輸入URL后,瀏覽器首先對拿到的URL進行識別,檢驗URL地址是否合法,抽取出域名字段。
例如http://www.baidu.com,這個URL主要由三部分組成:協(xié)議名、域名、端口號。通常端口號不見是因為大部分的是使用默認端口,如HTTP默認80,HTTPS默認443。
更詳細的說,URL包含以下幾個部分:
傳輸協(xié)議-服務器-域名-端口-虛擬目錄-文件名-參數(shù)-錨
http-www-aspxfans.com-8080-news-index.asp-boardID=5&ID=246&page=1-name
連接起來就是:
http://www.aspxfans.com:8080/news/index.asp?boardID=5&ID=246&page=1#name
二、DNS域名解析
例如:拿www.baidu.com舉例,www.baidu.com就是域名。
www.baidu.com (域名) - DNS解析 -> 11.22.33.44 (IP地址)
查詢URL對應的IP地址。DNS實際上是一個域名和IP地址對應的數(shù)據(jù)庫。域名和IP地址之間的轉(zhuǎn)換稱為域名解析。域名解析需要由專門的域名解析服務器來完成,整個過程是自動進行的。具體步驟為:
1、先到各種緩存信息中查找

① 瀏覽器緩存
瀏覽器會先檢查自己的DNS緩存(緩存時間比較短,TTL默認是1000且只能容納1000條緩存),看自己的緩存中是否有www.baidu.com對應的條目,而且沒有過期,如果有就返回其對應IP地址,沒有則進行下一步。
② 操作系統(tǒng)緩存
搜索操作系統(tǒng)的DNS緩存,如果沒有找到,接著檢查域名是否存在本地的Hosts(位于C:WindowSystem32driversetc)文件中,Hosts文件保存了一些以前訪問過的網(wǎng)站的域名和IP的數(shù)據(jù),它就像是一個本地的數(shù)據(jù)庫,如果找到則返回對應的IP地址,沒有則向DNS服務器發(fā)送查詢請求。
③ 路由器緩存
路由器查找其緩存,如果找到則返回對應IP地址,沒有則下一步
④本地DNS緩存
瀏覽器會向本地配置的首選DNS服務器ISP DNS發(fā)起域名解析請求(ISP DNS就是在客戶端電腦上設置的首選DNS服務器,它們在大多數(shù)情況下都會有緩存),通過UDP協(xié)議向DNS的53端口發(fā)起請求,這個請求是遞歸的請求,也就是運營商的DNS服務器必須得提供給我們該域名的IP地址。如果找到則返回對應IP地址,沒有則下一步。
⑤ LDNS向根域名服務器查詢、遞歸查詢
遞歸查找的順序是(根域名服務器,一級域名服務器,二級域名服務器,三級域名服務器)。
在前面所有步驟沒有緩存的情況下,LNDS(本地域名服務器)會向Root Name Server(根域名服務器)發(fā)起請求獲得根域的IP地址,然后再像com域發(fā)起請求獲得com域的IP地址,最后向www.baidu.com這個域名的DNS地址發(fā)起請求獲得對應的IP地址。
事實上,真正的網(wǎng)址是www.google.com.,并不是我多打了一個.,這個.對應的就是根域名服務器,默認情況下所有的網(wǎng)址的最后一位都是.,既然是默認情況下,為了方便用戶,通常都會省略,瀏覽器在請求DNS的時候會自動加上,所有網(wǎng)址真正的解析過程為: . -> .com -> google.com. -> www.google.com.
下面這個圖很好的詮釋了整個流程:

2、LDNS查找成功將IP地址返回給操作系統(tǒng)緩存起來,操作系統(tǒng)將IP地址返回給瀏覽器緩存起來,瀏覽器獲得IP地址,發(fā)起建立連接的請求。
三、TCP連接(三次握手)
瀏覽器主機根據(jù)IP地址與服務器建立TCP連接。瀏覽器向服務器發(fā)送SYN連接請求,經(jīng)過服務器與瀏覽器三次報文的交互連接建立完成。就可以發(fā)送數(shù)據(jù)了。
TCP三次握手:
第一次握手:客戶端向服務端發(fā)送請求(SYN=1),并進入SYN_SENT狀態(tài),等待服務器確認;(你在家嗎?我要來你家吃飯)
第二次握手:服務器收到請求并確認,回復一個指令(SYN=1,ACK=1),此時服務器進入SYN_RECV狀態(tài);(在的,來吧)
第三次握手:客戶端收到服務器的回復指令并返回確認(ACK=1),客戶端和服務端進入ESTABLISHED狀態(tài),完成三次握手。(好嘞)
注:SYN(synchronous建立連接)、ACK(acknowledgement 表示響應、確認)、PSH(push表示有DATA數(shù)據(jù)傳輸)

四、發(fā)送HTTP請求
握手過程中傳送的包里不包含數(shù)據(jù),三次握手完畢后,客戶端與服務器才正式開始傳送數(shù)據(jù)。根據(jù)HTTP協(xié)議的要求,組織一個HTTP數(shù)據(jù)包,向服務器發(fā)送HTTP請求。
HTTP的請求報文由請求行、請求頭、空行和請求數(shù)據(jù)4部分組成。
請求行:包括請求方法、URL、協(xié)議版本。如GET/HTTP/1.0
請求頭:包括一系列key:value成對格式的數(shù)據(jù)。比如Content-type:application/x-www-form-urlencoded。
空行:這個也是必須的。
請求數(shù)據(jù):真正請求所需要的數(shù)據(jù),比如登錄時的賬號和密碼。

注:HTTP請求方法詳見文章---《HTTP請求方法詳解》
五、服務器處理請求,瀏覽器接受響應
請求到達服務器,服務器會將收到的HTTP請求報文封裝成HTTP的Request對象,并通過不同的Web服務器進行處理,處理完的結(jié)果以HTTP的Response對象返回,即HTTP響應報文,最終返回給瀏覽器HTML文件。
HTTP響應報文由狀態(tài)行(status line)、響應頭(headers)、空行(blank line)和響應數(shù)據(jù)(response body)四部分組成。
1、狀態(tài)行由3部分組成,分別為:協(xié)議版本、狀態(tài)碼、狀態(tài)碼描述。其中協(xié)議版本與請求報文一致,狀態(tài)碼描述是對狀態(tài)碼的簡單描述。
響應的狀態(tài)碼
- 1xx:指示信息-表示請求已接收,繼續(xù)處理
- 2xx:成功-表示請求已被成功接收
- 3xx:重定向-要完成請求必須進行更進一步的操作
- 4xx:客戶端錯誤-請求有語法錯誤或請求無法實現(xiàn)
- 5xx:服務器錯誤-服務器未能實現(xiàn)合法的請求
注:平時遇到比較常見的狀態(tài)碼有:200, 204, 301, 302, 304, 400, 401, 403, 404, 422, 500。
2、響應報頭主要由Cache-Control、Server、Connection、Date等組成。
3、響應數(shù)據(jù)為服務器返回給瀏覽器的信息,主要由HTML、css、js、圖片等文件組成。
六、瀏覽器解析文件并渲染頁面
6.1 整體流程
1、解析html
2、構(gòu)建dom樹
3、處理CSS標記,構(gòu)成層疊樣式表模型CSSOM(CSS Object Model)。
4、dom樹結(jié)合cssOM,構(gòu)建渲染樹
5、布局
6、繪制
7、JavaScript 編譯執(zhí)行
6.2 過程解析
瀏覽器是一個邊解析邊渲染的過程。首先瀏覽器解析HTML文件構(gòu)建DOM樹,然后解析CSS文件構(gòu)建渲染樹,等渲染樹構(gòu)建完成,瀏覽器開始布局渲染樹并將其繪制到屏幕上,這個過程比較復雜,涉及到兩個概念:回流(reflow)和重繪(repaint)。DOM節(jié)點的各個元素都是以盒模型存在的,這些都需要瀏覽器去計算其位置和大小,這個過程稱為回流。當盒模型的位置、大小以及屬性如顏色、字體等確定之后,瀏覽器便開始繪制內(nèi)容,這個過程稱為重繪。頁面在首次加載時必然會經(jīng)歷回流和重繪,這是非常消耗性能的,尤其在移動設備上,它會破壞用戶體驗,有時會造成頁面卡頓,所以應該盡可能減少回流和重繪。
JS的解析是由瀏覽器中的JS解析引擎實現(xiàn)的。JS是單線程運行,即在同一個時間內(nèi)只能做一件事,所有的任務都需要排隊,前一個任務結(jié)束,后一個任務才開始。但是又存在某些任務比較耗時,所以需要一種機制可以先執(zhí)行排在后面的任務,這就是:同步任務(synchronous)和異步任務(asynchronous)。JS的執(zhí)行機制就可以看做是一個主線程加上一個任務隊列(task queue)。同步任務就是放在主線程上執(zhí)行的任務,異步任務就是放在任務隊列中的任務,所有的同步任務在主線程上執(zhí)行,形成一個執(zhí)行棧;異步任務有了運行結(jié)果就會在任務隊列中放置一個事件;腳本運行時先依次運行執(zhí)行棧,然后從任務隊列中提取事件,運行任務隊列中的任務,這個過程是不斷重復地,所有又叫事件循環(huán)。
瀏覽器在解析過程中,如果遇到請求外部資源時,瀏覽器將重復下載該資源。請求過程是異步的,并不會影響HTML文檔進行加載,但是當文檔加載過程中遇到JS文件,HTML文檔會掛起渲染過程,不僅要等到文檔中JS文件加載完畢還要等到解析執(zhí)行完畢,才會繼續(xù)HTML渲染。原因是因為JS有可能修改DOM結(jié)構(gòu),這就意味著JS執(zhí)行完成前,后續(xù)所有資源的下載是沒有必要的,這就是JS阻塞后續(xù)資源下載的根本原因。所以js我們大都建議放在body元素之后。CSS文件的加載不影響JS文件的加載,但是卻影響JS文件的執(zhí)行,JS代碼執(zhí)行前瀏覽器必須保證CSS文件已經(jīng)下載并執(zhí)行完畢。
注:瀏覽器對同一域名的并發(fā)連接數(shù)是有限的,通常為 6 個。
宏任務
- 同步任務:按照順序執(zhí)行,只有前一個任務完成后,才能執(zhí)行后一個任務
- 異步任務:不直接執(zhí)行,只有滿足觸發(fā)條件時,相關(guān)的線程將該異步任務推進任務隊列中,等待JS引擎主線程上的任務執(zhí)行完畢之后才開始執(zhí)行,例如異步ajax、DOM事件、setTimeOut等
微任務
微任務是ES6和Node環(huán)境下的,主要API有:Promise、process、nextTick
微任務的執(zhí)行在宏任務的同步任務之后,在異步任務之前。

具體執(zhí)行方式詳見文章-《JS事件循環(huán)機制(event loop)》

七、斷開TCP連接
四次揮手:
首先,需要明確的是,四次揮手不知道哪一方是主動方,哪一方是被動方。
第一次揮手:主動方發(fā)送一個FIN(finish),告訴被動方:我不會再給你發(fā)數(shù)據(jù)了(當然,在FIN包之前發(fā)出去的數(shù)據(jù),如果沒有收到對應的ack確認報文,主動方依然會重新發(fā)送這些數(shù)據(jù)),但此時,主動方依然可以接收數(shù)據(jù)。(我這邊沒數(shù)據(jù)了,我要走啦)
第二次揮手:被動方收到FIN包后,發(fā)送一個ACK給對方,確認序號為收到序號+1。(我知道了,我看看我這邊有沒有數(shù)據(jù))
第三次揮手:主動方進入Fin_Wait狀態(tài),等待被動方的FIN報文,被動方發(fā)送完畢后,向主動方發(fā)送FIN,告訴主動方,我的數(shù)據(jù)也發(fā)送完了,不會再給你發(fā)送數(shù)據(jù)了。(我這邊也沒數(shù)據(jù)了,咱可以斷開連接了)
第四次揮手:主動方收到FIN后,發(fā)送一個ACK給被動方,確認序號為收到序號+1,并關(guān)閉連接。至此完成四次揮手。(好的,拜拜)

斷開連接為什么必須這么繁瑣的四次揮手?
主要是因為客戶端告訴服務器想斷開連接的時候,服務器的數(shù)據(jù)不一定已經(jīng)處理完畢,所以服務器是先告訴客戶端已經(jīng)收到了它想斷開連接的請求,然后當服務器中數(shù)據(jù)處理完畢后,便斷開請求通知客戶端,客戶端收到后也斷開請求并通知服務器。