從我們輸入URL並按下回車鍵到看到網頁結果之間發生了什麼?換句話說,一張網頁,要經歷怎樣的過程,才能抵達用戶面前?下麵來從一些細節上面嘗試一下探尋裡面的秘密。 前言:鍵盤與硬體中斷 說到輸入URL,當然是從手敲鍵盤開始。對於鍵盤,生活中用到的最常見的鍵盤有兩種:薄膜鍵盤、機械鍵盤。 薄膜鍵盤:由面板 ...
從我們輸入URL並按下回車鍵到看到網頁結果之間發生了什麼?換句話說,一張網頁,要經歷怎樣的過程,才能抵達用戶面前?下麵來從一些細節上面嘗試一下探尋裡面的秘密。
前言:鍵盤與硬體中斷
說到輸入URL,當然是從手敲鍵盤開始。對於鍵盤,生活中用到的最常見的鍵盤有兩種:薄膜鍵盤、機械鍵盤。
-
薄膜鍵盤:由面板、上電路、隔離層、下電路構成。有外觀優美、壽命較長、成本低廉的特點,是最為流行的鍵盤種類。鍵盤中有一整張雙層膠膜,通過膠膜提供按鍵的回彈力,利用薄膜被按下時按鍵處碳心於線路的接觸來控制按鍵觸發。
-
機械鍵盤:由鍵帽、機械軸組成。鍵盤打擊感較強,常見於游戲發燒友與打字愛好者。每一個按鍵都有一個獨立的機械觸點開關,利用柱型彈簧提供按鍵的回彈力,用金屬接觸觸點來控制按鍵的觸發。
鍵盤傳輸信號到操作系統後便會觸發硬體中斷處理程式。硬體中斷是操作系統中提高系統效率、滿足實時需求的非常重要的信號處理機制,它是一個非同步信號,並提供相關中斷的註冊表(IDT)與請求線(IRQ)。鍵盤被按壓時,將通過請求線將信號輸入給操作系統,CPU在當前指令結束之後,根據註冊表與信號響應該中斷並利用段寄存器裝入中斷程式入口地址。具體可參看操作系統與彙編相關書籍。
當然本文主要不是介紹硬體與操作系統中的細節,前言只是想說明,從輸入URL到瀏覽器展現結果頁面之間有太多底層相關的知識,懷著一顆敬畏的心並且在有限的篇幅中是無法詳細闡述的,所以本文會將關註點放在在一個稍高的角度上來看,從瀏覽器替我們發送請求之後到看到頁面顯示完成這中間中發生了什麼事情,比如DNS解析、瀏覽器渲染。
瀏覽器解析URL
按下回車鍵之前
比如我按下一個‘b’鍵,會出現很多待選URL給我,第一個便是百度。那麼其實是在瀏覽器接收到這個消息之後,會觸發瀏覽器的自動完成機制,會在你之前訪問過的搜索最匹配的相關URL,會根據特定的演算法顯示出來供用戶選擇。
按下回車鍵之後
依據上述鍵盤觸發原理,一個專用於回車鍵的電流迴路通過不同的方式閉合了。然後觸發硬體中斷,隨之操作系統內核去處理對應中斷。省略其中的過程,最後交給了瀏覽器這樣一個“回車”信號。那麼瀏覽器(本文涉及到的瀏覽器版本都為Chrome 61)會進行以下但不僅限於以下炫酷(亂七八糟)的步驟:
-
解析URL:您輸入的是http還是https開頭的網路資源 / file開頭的文件資源 / 待搜索的關鍵字?然後瀏覽器進行對應的資源載入進程
-
URL轉碼:RFC標準中規定部分字元可以不經過轉碼直接用於URL,但是漢字不在範圍內。所以如果在網址路徑中包含漢字將會被轉碼
-
HSTS:鑒於HTTPS遺留的安全隱患,大部分現代瀏覽器已經支持HSTS。對於瀏覽器來說,瀏覽器會檢測是否該網路資源存在於預設定的只使用HTTPS的網站列表,或者是否保存過以前訪問過的只能使用HTTPS的網站記錄,如果是,瀏覽器將強行使用HTTPS方式訪問該網站。
DNS解析
不查DNS,讀取緩存
-
瀏覽器中的緩存:對於Chrome,緩存查看地址為:chrome://net-internals/#dns
-
本地hosts文件:以Mac與Linux為例,hosts文件所在路徑為:/etc/hosts。所以有一種FQ的方式就是修改hosts文件避免GFW對DNS解析的干擾,直接訪問真正IP地址,但已經不能完全生效因為GFW還有根據IP過濾的機制。
發送DNS查找請求
DNS的查詢方式是:按根功能變數名稱->頂級功能變數名稱->次級功能變數名稱->主機名這樣的方式來查找的,對於某個URL,如下所示
123
|
|
查詢步驟為:
-
查詢本地DNS伺服器。本地DNS伺服器地址為連接網路時路由器指定的DNS地址,一般為DHCP自動分配的路由器地址,保存在/etc/resolv.conf,而路由器的DNS轉發器將請求轉發到上層ISP的DNS,所以此處本地DNS伺服器是區域網或者運營商的。
-
從"根功能變數名稱伺服器"查到"頂級功能變數名稱伺服器"的NS記錄和A記錄(IP地址)。世界上一共有十三組根功能變數名稱伺服器,從A.ROOT-SERVERS.NET一直到M.ROOT-SERVERS.NET,由於已經將這些根功能變數名稱伺服器的IP地址存放在本地DNS伺服器中。
-
從"頂級功能變數名稱伺服器"查到"次級功能變數名稱伺服器"的NS記錄和A記錄(IP地址)
-
從"次級功能變數名稱伺服器"查出"主機名"的IP地址
以www.google.com為例,下麵是一整個DNS查詢過程:
-
由於本次測試是在阿裡雲上的實例進行測試,所以首先從100.100.2.138這個阿裡內網DNS伺服器查找到所有根功能變數名稱伺服器的映射關係。
-
訪問根功能變數名稱伺服器(f.root-servers.net),拿到com頂級功能變數名稱伺服器的NS記錄與IP地址。
-
訪問頂級功能變數名稱伺服器(e.gtld-servers.net),拿到google.com次級功能變數名稱伺服器的NS記錄與IP地址。
-
訪問次級功能變數名稱伺服器(ns2.google.com),拿到www.google.com的IP地址
12345678910111213141516171819202122232425262728293031323334353637383940
|
|
所以總的來說,DNS的解析是一個逐步縮小範圍的查找過程。
建立HTTPS、TCP連接
確定發送目標
拿到IP之後,還需要拿到那台伺服器的MAC地址才行,在乙太網協議中規定,同一區域網中的一臺主機要和另一臺主機進行直接通信,必須要知道目標主機的MAC地址。所以根據ARP(根據IP地址獲取物理地址的一個TCP/IP協議)獲取到MAC地址之後保存到本地ARP緩存之後與目標主機準備開始通信。具體細節參見維基百科DHCH/ARP。
建立TCP連接
為什麼握手一定要是三次?
-
第一次與第二次握手完成意味著:A能發送請求到B,並且B能解析A的請求
-
第二次與第三次握手完成意味著:A能解析B的請求,並且B能發送請求到A
這樣就保證了A與B之間既能相互發送請求也能相互接收解析請求。同時避免了因為網路延遲產生的重覆連接問題,比如A發送一次連接請求但網路延遲導致這次請求是在A重發連接請求並完成與B通信之後的,有三次握手的話,B返回的建立請求A就不會理睬了。
短連接與長連接?
上圖是一個短連接的過程演示,對於長連接,A與B完成一次讀寫之後,它們之間的連接並不會主動關閉,後續的讀寫操作會繼續使用這個連接。另外,由於長連接的實現比較困難,需要要求長連接在沒有數據通信時,定時發送數據包(心跳),以維持連接狀態,並且長連接對於伺服器的壓力也會很大,所以推送服務對於一般的開發者是非常難以實現的,這樣的話就出現了很多不同的大型廠商提供的消息推送服務。
進行TLS加密過程
-
Hello - 握手開始於客戶端發送Hello消息。包含服務端為了通過SSL連接到客戶端的所有信息,包括客戶端支持的各種密碼套件和最大SSL版本。伺服器也返回一個Hello消息,包含客戶端需要的類似信息,包括到底使用哪一個加密演算法和SSL版本。
-
證書交換 - 現在連接建立起來了,伺服器必須證明他的身份。這個由SSL證書實現,像護照一樣。SSL證書包含各種數據,包含所有者名稱,相關屬性(功能變數名稱),證書上的公鑰,數字簽名和關於證書有效期的信息。客戶端檢查它是不是被CA驗證過的。註意伺服器被允許需求一個證書去證明客戶端的身份,但是這個只發生在敏感應用。
-
密鑰交換 - 先使用RSA非對稱公鑰加密演算法(客戶端生成一個對稱密鑰,然後用SSL證書裡帶的伺服器公鑰將改對稱密鑰加密。隨後發送到服務端,服務端用伺服器私鑰解密,到此,握手階段完成。)或者DH交換演算法在客戶端與服務端雙方確定一將要使用的密鑰,這個密鑰是雙方都同意的一個簡單,對稱的密鑰,這個過程是基於非對稱加密方式和伺服器的公鑰/私鑰的。
-
加密通信 - 在伺服器和客戶端加密實際信息是用到對稱加密演算法,用哪個演算法在Hello階段已經確定。對稱加密演算法用對於加密和解密都很簡單的密鑰,這個密鑰是基於第三步在客戶端與服務端已經商議好的。與需要公鑰/私鑰的非對稱加密演算法相反。
服務端的處理
靜態緩存、CDN
為了優化網站訪問速度並減少伺服器壓力,通常將html、js、css、文件這樣的靜態文件放在獨立的緩存伺服器或者部署在類似Amazon CloudFront的CDN雲服務上,然後根據緩存過期配置確定本次訪問是否會請求源伺服器來更新緩存。
負載均衡
負載均衡具體實現有多種,有直接基於硬體的F5,有操作系統傳輸層(TCP)上的 LVS,也有在應用層(HTTP)實現的反向代理(也叫七層代理),下麵簡單介紹一下最後者。
在請求發送到真正處理請求的伺服器之前,還需要將請求路由到適合的伺服器上,一個請求被負載均衡器拿到之後,需要做一些處理,比如壓縮請求(在nginx中gzip壓縮格式是預設配置在nginx.conf內的,所以預設開啟,如果不對數據量要求特別精細的話,預設配置完全可以滿足基本需求)、接收請求(接收完畢後才發給Server,提高Server處理效率),然後根據預定的路由演算法,將此次請求發送到某個後臺伺服器上。
其中需要提到的一點是反向代理,先回顧一下反向代理的原理:正向代理是將自己要訪問的資源告訴Proxy,讓Proxy幫你拿到數據返回給你,Proxy服務於Client,常用於FQ和跨許可權操作;反向代理也是將自己要訪問的資源告訴Proxy,讓Proxy幫你拿到數據返回給你,但是Proxy服務於Server,它會將請求接受完畢之後發送給某一合適的Server,這個時候Client是不知道是根據什麼規則並且也不知道最後是哪一個Server服務於它的,所以叫反向代理,常用於負載均衡、安全控制.
伺服器的處理
對於HTTPD(HTTP Daemon)在伺服器上部署,最常見的 HTTPD 有 Linux 上常用的 Apache 和 Nginx。對於Java平臺來說,Tomcat是Spring Boot也會預設選用的Servlet容器實現,Tomcat對於請求的處理如下:
-
請求到達Tomcat啟動時監聽的TCP埠。
-
解析請求中的各種信息之後創建一個Request對象並填充那些有可能被所引用的Servlet使用的信息,如參數,頭部、cookies、查詢字元串等。
-
創建一個Response對象,所引用的Servlet使用它來給客戶端發送響應。
-
調用Servlet的service方法,並傳入Request和Response對象。這裡Servlet會從Request對象取值,給Response寫值。
-
根據我們自己寫的Servlet程式或者框架攜帶的Servlet類做進一步的處理(業務處理、請求的進一步處理)
-
最後根據Servlet返回的Response生成相應的HTTP響應報文。
瀏覽器的渲染
瀏覽器的功能是從伺服器上取回你想要的資源,然後展示在瀏覽器視窗當中。資源通常是 HTML 文件,也可能是 PDF,圖片,或者其他類型的內容。也可以顯示其他類型的插件(瀏覽器擴展)。例如顯示PDF使用PDF瀏覽器插件。資源的位置通過用戶提供的 URI(Uniform Resource Identifier) 來確定。
瀏覽器解釋和展示 HTML 文件的方法,在 HTML 和 CSS 的標準中有詳細介紹。這些標準由 Web 標準組織 W3C(World Wide Web Consortium) 維護。
下麵會以Chrome中使用的瀏覽器引擎Webkit為例,根據上圖來簡單介紹瀏覽器的渲染。具體解析、渲染會涉及到非常多的細節,請參考HTML5渲染規範和對應的頁面GPU渲染實現。
HTML解析
瀏覽器拿到具體的HTML文檔之後,需要調用瀏覽器中使用的瀏覽器引擎中處理HTML的工具(HTML Parser)來將HTML文檔解析成為DOM樹,將以便外部介面(JS)調用。
-
文檔內容解析:將一大串字元串解析為DOM之前需要從中分析出結構化的信息讓HTML解析器可以很方便地提取數據進行其他操作,所以對於文檔內容的解析是第一步。解析器有兩個處理過程——詞法分析(將字元串切分成符合特定語法規範的符號)與語法分析(根據符合語法規範的符號構建對應該文檔的語法樹)。
-
HTML解析:根據HTML語法,將HTML標記到語法樹上構建成DOM(Document Object Model)。
CSS解析
根據CSS詞法和句法分析CSS文件和<style>標簽包含的內容以及style屬性的值
每個CSS文件都被解析成一個樣式表對象(StyleSheet object),這個對象里包含了帶有選擇器的CSS規則,和對應CSS語法的對象
頁面渲染
解析完成後,瀏覽器引擎會通過DOM樹和CSS Rule樹來構造渲染樹。渲染樹的構建會產生Layout,Layout是定位坐標和大小,是否換行,各種position, overflow, z-index屬性的集合,也就是對各個元素進行位置計算、樣式計算之後的結果。
接下來,根據渲染樹對頁面進行渲染(可以理解為“畫”元素)。
當然,將這個渲染的過程完成並顯示到屏幕上會涉及到顯卡的繪製,顯存的修改,有興趣的讀者可以深入瞭解。