1.瀏覽器接收URL
URL包含的信息:協議、網絡地址:端口號、資源路徑、查詢字符串?、片段標識符#
2.將URL與緩存進行比對如果請求的頁面在緩存中且未過期,則直接進行第8步
緩存分為徹底緩存和緩存協商,這里的確認是否過期是指徹底緩存(緩存失效之前不再需要跟服務器交互)。
2.1 徹底緩存的機制(http首部字段):cache-control,Expires
--Expires是一個絕對時間,即服務器時間。瀏覽器檢查當前時間,如果還沒到失效時間就直接使用緩存文件。但是該方法存在一個問題:服務器時間與客戶端時間可能不一致。因此該字段已經很少使用,現在基本用cache-control進行判斷。
--cache-control中的max-age保存一個相對時間。例如Cache-Control: max-age = 484200,表示瀏覽器收到文件后,緩存在484200s內均有效。 如果同時存在cache-control和Expires,瀏覽器總是優先使用cache-control。
cache-control還有其他指令:
(請求/響應指令)
no-cache,使用緩存前必須和服務器進行確認,也就是需要發起請求。
no-store,不緩存;
(響應指令)
public,緩存文件保存在緩存服務器上,且其他用戶也可以訪問;
private,只有特定用戶才能訪問該緩存資源。
2.2 當緩存過期時,瀏覽器會向服務器發起請求詢問資源是否真正過期,這就是緩存協商。對應http首部字段:last-modified,Etag
--last-modified是第一次請求資源時,服務器返回的字段,表示最后一次更新的時間。下一次瀏覽器請求資源時就發送if-modified-since字段。服務器用本地Last-modified時間與if-modified-since時間比較,如果不一致則認為緩存已過期并返回新資源給瀏覽器;如果時間一致則發送304狀態碼,讓瀏覽器繼續使用緩存。當然,用該方法也存在問題,比如修改時間有變化但實際內容沒有變化,而服務器卻再次將資源發送給瀏覽器。所以,使用Etag進行判斷更好。
--Etag:資源的實體標識(哈希字符串),當資源內容更新時,Etag會改變。服務器會判斷Etag是否發生變化,如果變化則返回新資源,否則返回304。
緩存協商的過程需要發起一起HTTP請求,如果返回304則繼續使用緩存。對于移動端一次請求還是有代價的,所以我們需要避免304。
對于很少進行更改的靜態文件,可以在文件名中加入版本號,如get.v1.js,并且把Cache-Control的max-age設置成一年半載,這樣就不會發送請求。
需要注意的是,當這些文件更新的時候,我們需要更新其版本號,這樣瀏覽器才會到服務器下載新資源。
2.3 貼一個緩存機制的圖(來自淺談Web緩存)
2.4 除了http首部設置緩存,HTML5的manifest文件也可以設置緩存。但現在已經被標準舍棄,也就沒有討論的必要。
3.如果網絡地址不是一個 IP 地址,通過DNS解析域名返回一個IP地址
3.1 DNS協議:
DNS數據庫是域名和IP地址相互映射的一個分布式數據庫,DNS協議用來將域名轉換為IP地址,它運行在UDP協議之上。為什么選擇UDP而非TCP?原因如下:UDP無需連接,時效性更好,進行一次查詢只需要兩個DNS包。而TCP需要先用3個包建立連接,再用2個DNS包進行查詢,最后用4個包斷開連接,連接成本遠大于查詢本身,容易讓DNS服務器不堪重負。
3.2 DNS查詢:
操作系統會先檢查本地hosts文件是否有這個網址映射關系,如果有就調用這個IP地址映射,完成域名解析。
否則,查找本地DNS解析器緩存,如果查找到則返回。
否則,查找本地DNS服務器,如果查找到則返回。
否則,1)未用轉發模式,按根域服務器 ->頂級域,.com->第二層域,example.com ->子域,www.example.com的順序找到IP地址。2)用轉發模式,按上一級DNS服務器->上上級->....逐級向上查詢找到IP地址。
4.瀏覽器與服務器通過三次握手(SYN,SYN/ACK,ACK)建立TCP 連接
為什么需要進行三次握手,而不是兩次握手?
原因是兩次握手不可靠。比如,瀏覽器發送一個連接請求包A,但包A在半路上堵車了,瀏覽器就認為包A丟失了,所以重新發生一個請求包B給服務器。服務器收到請求,建立連接。兩端進行通信,結束后關閉連接。但是這時候,包A到達了服務器,服務器不知道這是一個無效的包,所以進行響應。這時兩次握手已經完成,兩端就建立起一個無效的連接。但瀏覽器認為自己沒發出請求,所以不會回應,這樣就讓服務器白白等待回應,浪費了服務器資源。而三次握手的機制下,瀏覽器知道自己并沒有請求連接,會發送拒絕包給服務器,服務器收到回應后也會結束這次無效的連接。
5.瀏覽器向服務器發送HTTP請求。
數據經過應用層、傳輸層、網絡層、物理層逐層封裝,傳輸到下一個目的地。
其中,每一層的作用如下。
應用層:為應用進程提供服務,加應用層首部封裝為協議數據單元。
傳輸層:實現端到端通信,加TCP首部封裝為數據包,TCP控制了數據包的發送序列的產生,不斷的調整發送序列,實現流控和數據完整。
網絡層:轉發分組并選擇路由;加IP首部封裝為IP分組。
數據鏈路層:相鄰的節點間的數據傳輸;加首部[mac地址]和尾部封裝為幀。
物理層:具體物理媒介中的數據傳送,數據轉換為比特流。
下一個目的地接受到數據后,從物理層得到數據然后經過逐層的解包 到 鏈路層 到 網絡層,然后開始上述的處理,在經網絡層 鏈路層 物理層將數據封裝好繼續傳往下一個地址。
到達最終目的地,再經過5層結構,逐層剝離,最終將數據送到目的主機的目的端口。
6.服務器收到請求,從它的文檔空間中查找資源并返回HTTP響應。
7.瀏覽器接受 HTTP 響應,檢查 HTTP header 里的狀態碼,并做出不同的處理方式。
比如404顯示錯誤頁面,304使用緩存,200下一步解碼和渲染, 204頁面不會發生更新。
常見狀態碼:200 ok, 204 no content, 206 partial content
301 moved permanently(資源已分配新的uri),302 found(本次使用新uri訪問),303 see other(以get定向到另一個uri),304 not modified, 307 temporary redirect(不會從post改為get)
400 bad request,402 unauthorized,403 forbidden, 404 not found
500 internal server error,503 service unavailable
8.如果是可以緩存的,這個響應則會被存儲起來。
根據首部字段判斷是否進行緩存。例如,Cache-Control, no-cache(每次使用緩存前和服務器確認),no-store 絕對禁止緩存
9.解碼
9.1 瀏覽器拿到index.html文件后,就開始解析其中的html代碼,遇到js/css/image等靜態資源時,就向服務器端去請求下載
9.2 解析成對應的樹形數據結構DOM樹、CSS規則樹,Javascript腳本通過DOM API和CSSOM API來操作DOM樹、CSS規則樹。
10.渲染
10.1 計算CSS樣式(JS可動態修改dom或css,進一步改變渲染樹和分布)
10.2 構建渲染樹(Repaint:屏幕的一部分要重畫,比如某個CSS的背景色變了,元素的幾何尺寸沒有變。Reflow:幾何尺寸變了,我們需要重新驗證并計算Render Tree。)
10.3 確認布局(定位坐標和大小,是否換行,各種position, overflow, z-index屬性 ……)
10.4 繪制(調用操作系統Native GUI的API繪制,將每個節點轉化為實際像素繪制到視口上)
11.關閉TCP連接或繼續保持連接
通過四次揮手關閉連接(FIN ACK, ACK, FIN ACK, ACK)。
為什么需要進行四次揮手?
第一次揮手是瀏覽器發完數據后,發送FIN請求斷開連接。第二次揮手是服務器發送ACK表示同意,如果在這一次服務器也發送FIN請求斷開連接似乎也沒有不妥,但考慮到服務器可能還有數據要發送,所以服務器發送FIN應該放在第三次揮手中。這樣瀏覽器需要返回ACK表示同意,也就是第四次揮手。
簡而言之,一端斷開連接需要兩次揮手(請求和回應),兩端斷開連接就需要四次揮手了。