整理自:github.com/skyline7548…css
這個倉庫試圖回答一個古老的面試問題:當你在瀏覽器中輸入 google.com 而且按下回車以後發生了什麼?html
不過咱們再也不侷限於日常的回答,而是想辦法回答地儘量具體,不遺漏任何細節。node
接下來的內容介紹了物理鍵盤和系統中斷的工做原理,可是有一部份內容卻沒有涉及。當你按下「g」鍵,瀏覽器接收到這個消息以後,會觸發自動完成機制。瀏覽器根據本身的算法,以及你是否處於隱私瀏覽模式,會在瀏覽器的地址框下方給出輸入建議。大部分算法會優先考慮根據你的搜索歷史和書籤等內容給出建議。你打算輸入"google.com",所以給出的建議並不匹配。可是輸入過程當中仍然有大量的代碼在後臺運行,你的每一次按鍵都會使得給出的建議更加準確。甚至有可能在你輸入以前,瀏覽器就將"google.com" 建議給你。nginx
爲了從零開始,咱們選擇鍵盤上的回車鍵被按到最低處做爲起點。在這個時刻,一個專用於回車鍵的電流回路被直接地或者經過電容器間接地閉合了,使得少許的電流進入了鍵盤的邏輯電路系統。這個系統會掃描每一個鍵的狀態,對於按鍵開關的電位彈跳變化進行噪音消除(debounce),並將其轉化爲鍵盤碼值。在這裏,回車的碼值是13。鍵盤控制器在獲得碼值以後,將其編碼,用於以後的傳輸。如今這個傳輸過程幾乎都是經過通用串行總線(USB)或者藍牙(Bluetooth)來進行的,之前是經過PS/2或者ADB鏈接進行。git
USB鍵盤:github
虛擬鍵盤(觸屏設備):面試
鍵盤在它的中斷請求線(IRQ)上發送信號,信號會被中斷控制器映射到一箇中斷向量,實際上就是一個整型數。CPU使用中斷描述符表(IDT)把中斷向量映射到對應函數,這些函數被稱爲中斷處理器,它們由操做系統內核提供。當一箇中斷到達時,CPU根據IDT和中斷向量索引到對應的中斷處理器,而後操做系統內核出場了。算法
WM_KEYDOWN
消息被髮往應用程序HID把鍵盤按下的事件傳送給 KBDHID.sys
驅動,把HID的信號轉換成一個掃描碼(Scancode),這裏回車的掃描碼是 VK_RETURN(0x0d)
。 KBDHID.sys
驅動和 KBDCLASS.sys
(鍵盤類驅動,keyboard class driver)進行交互,這個驅動負責安全地處理全部鍵盤和小鍵盤的輸入事件。以後它又去調用Win32K.sys
,在這以前有可能把消息傳遞給安裝的第三方鍵盤過濾器。這些都是發生在內核模式。chrome
Win32K.sys
經過 GetForegroundWindow()
API函數找到當前哪一個窗口是活躍的。這個API函數提供了當前瀏覽器的地址欄的句柄。Windows系統的"message pump"機制調用 SendMessage(hWnd, WM_KEYDOWN, VK_RETURN, lParam)
函數,lParam
是一個用來指示這個按鍵的更多信息的掩碼,這些信息包括按鍵重複次數(這裏是0),實際掃描碼(可能依賴於OEM廠商,不過一般不會是VK_RETURN
),功能鍵(alt, shift, ctrl)是否被按下(在這裏沒有),以及一些其餘狀態。後端
Windows的 SendMessage
API直接將消息添加到特定窗口句柄 hWnd
的消息隊列中,以後賦給 hWnd
的主要消息處理函數 WindowProc
將會被調用,用於處理隊列中的消息。
當前活躍的句柄 hWnd
其實是一個edit control控件,這種狀況下,WindowProc
有一個用於處理 WM_KEYDOWN
消息的處理器,這段代碼會查看 SendMessage
傳入的第三個參數 wParam
,由於這個參數是 VK_RETURN
,因而它知道用戶按下了回車鍵。
KeyDown
NSEvent被髮往應用程序中斷信號引起了I/O Kit Kext鍵盤驅動的中斷處理事件,驅動把信號翻譯成鍵碼值,而後傳給OS X的WindowServer
進程。而後, WindowServer
將這個事件經過Mach端口分發給合適的(活躍的,或者正在監聽的)應用程序,這個信號會被放到應用程序的消息隊列裏。隊列中的消息能夠被擁有足夠高權限的線程使用mach_ipc_dispatch
函數讀取到。這個過程一般是由 NSApplication
主事件循環產生而且處理的,經過 NSEventType
爲 KeyDown
的 NSEvent
。
當使用圖形化的 X Server 時,X Server會按照特定的規則把鍵碼值再一次映射,映射成掃描碼。當這個映射過程完成以後,X Server 把這個按鍵字符發送給窗口管理器(DWM,metacity,i3等等),窗口管理器再把字符發送給當前窗口。當前窗口使用有關圖形API把文字打印在輸入框內。
瀏覽器經過 URL 可以知道下面的信息:
Protocol
"http" : 使用HTTP協議
Resource
"/" : 請求的資源是主頁(index)
當協議或主機名不合法時,瀏覽器會將地址欄中輸入的文字傳給默認的搜索引擎。大部分狀況下,在把文字傳遞給搜索引擎的時候,URL會帶有特定的一串字符,用來告訴搜索引擎此次搜索來自這個特定瀏覽器。
a-z
, A-Z
,0-9
, -
或者 .
的字符google.com
,因此沒有非ASCII的字符;若是有的話,瀏覽器會對主機名部分使用Punycode 編碼gethostbyname
庫函數(操做系統不一樣函數也不一樣)進行查詢。gethostbyname
函數在試圖進行DNS解析以前首先檢查域名是否在本地Hosts 裏,Hosts 的位置不一樣的操做系統有所不一樣gethostbyname
沒有這個域名的緩存記錄,也沒有在 hosts
裏找到,它將會向 DNS 服務器發送一條 DNS 查詢請求。DNS服務器是由網絡通訊棧提供的,一般是本地路由器或者 ISP 的緩存 DNS 服務器。要想發送 ARP(地址解析協議)廣播,咱們須要有一個目標 IP地址,同時還須要知道用於發送 ARP 廣播的接口的 MAC 地址。
若是緩存沒有命中:
ARP Request
:
Sender MAC: interface:mac:address:here
Sender IP: interface.ip.goes.here
Target MAC: FF:FF:FF:FF:FF:FF (Broadcast)
Target IP: target.ip.goes.here
複製代碼
根據鏈接主機和路由器的硬件類型不一樣,能夠分爲如下幾種狀況:
直連:
ARP Reply
(見下面)。集線器:
ARP Reply
。交換機:
ARP Reply
ARP Reply
:
Sender MAC: target:mac:address:here
Sender IP: target.ip.goes.here
Target MAC: interface:mac:address:here
Target IP: interface.ip.goes.here
複製代碼
如今咱們有了 DNS 服務器或者默認網關的 IP 地址,咱們能夠繼續 DNS 請求了:
當瀏覽器獲得了目標服務器的 IP 地址,以及 URL 中給出來端口號(http 協議默認端口號是 80, https 默認端口號是 443),它會調用系統庫函數socket
,請求一個 TCP流套接字,對應的參數是 AF_INET/AF_INET6
和SOCK_STREAM
。
到了如今,TCP 封包已經準備好了,可使用下面的方式進行傳輸:
對於大部分家庭網絡和小型企業網絡來講,封包會從本地計算機出發,通過本地網絡,再經過調制解調器把數字信號轉換成模擬信號,使其適於在電話線路,有線電視光纜和無線電話線路上傳輸。在傳輸線路的另外一端,是另一個調制解調器,它把模擬信號轉換回數字信號,交由下一個網絡節點處理。節點的目標地址和源地址將在後面討論。
大型企業和比較新的住宅一般使用光纖或直接以太網鏈接,這種狀況下信號一直是數字的,會被直接傳到下一個網絡節點進行處理。
最終封包會到達管理本地子網的路由器。在那裏出發,它會繼續通過自治區域(autonomous system, 縮寫 AS)的邊界路由器,其餘自治區域,最終到達目標服務器。一路上通過的這些路由器會從IP數據報頭部裏提取出目標地址,並將封包正確地路由到下一個目的地。IP數據報頭部time to live (TTL)域的值每通過一個路由器就減1,若是封包的TTL變爲0,或者路由器因爲網絡擁堵等緣由封包隊列滿了,那麼這個包會被路由器丟棄。
上面的發送和接受過程在 TCP 鏈接期間會發生不少次:
客戶端選擇一個初始序列號(ISN),將設置了 SYN 位的封包發送給服務器端,代表本身要創建鏈接並設置了初始序列號
服務器端接收到 SYN 包,若是它能夠創建鏈接: - 服務器端選擇它本身的初始序列號 - 服務器端設置 SYN 位,代表本身選擇了一個初始序列號 - 服務器端把 (客戶端ISN + 1) 複製到 ACK 域,而且設置 ACK 位,代表本身接收到了客戶端的第一個封包
客戶端經過發送下面一個封包來確認此次鏈接: - 本身的序列號+1 - 接收端 ACK+1 - 設置 ACK 位
數據經過下面的方式傳輸: - 當一方發送了N個 Bytes 的數據以後,將本身的 SEQ 序列號也增長N - 另外一方確認接收到這個數據包(或者一系列數據包)以後,它發送一個 ACK包,ACK的值設置爲接收到的數據包的最後一個序列號
關閉鏈接時: - 要關閉鏈接的一方發送一個 FIN 包 - 另外一方確認這個 FIN 包,而且發送本身的 FIN 包 - 要關閉的一方使用 ACK 包來確認接收到了 FIN
ClientHello
消息到服務器端,消息中同時包含了它的Transport Layer Security (TLS) 版本,可用的加密算法和壓縮算法。ServerHello
消息,消息中包含了服務器端的TLS版本,服務器所選擇的加密和壓縮算法,以及數字證書認證機構(Certificate Authority,縮寫CA)簽發的服務器公開證書,證書中包含了公鑰。客戶端會使用這個公鑰加密接下來的握手過程,直到協商生成一個新的對稱密鑰Finished
消息給服務器端,使用對稱密鑰加密此次通信的一個散列值Finished
消息,也使用協商好的對稱密鑰加密若是瀏覽器是 Google 出品的,它不會使用 HTTP 協議來獲取頁面信息,而是會與服務器端發送請求,商討使用 SPDY 協議。
若是瀏覽器使用 HTTP 協議而不支持 SPDY 協議,它會向服務器發送這樣的一個請求:
GET / HTTP/1.1
Host: google.com
Connection: close
[其餘頭部]
複製代碼
「其餘頭部」包含了一系列的由冒號分割開的鍵值對,它們的格式符合HTTP協議標準,它們之間由一個換行符分割開來。(這裏咱們假設瀏覽器沒有違反HTTP協議標準的bug,同時假設瀏覽器使用HTTP/1.1
協議,否則的話頭部可能不包含 Host
字段,同時 GET
請求中的版本號會變成 HTTP/1.0
或者 HTTP/0.9
。)
HTTP/1.1 定義了「關閉鏈接」的選項"close",發送者使用這個選項指示此次鏈接在響應結束以後會斷開。例如:
Connection:close
不支持持久鏈接的 HTTP/1.1 應用必須在每條消息中都包含 "close" 選項。
在發送完這些請求和頭部以後,瀏覽器發送一個換行符,表示要發送的內容已經結束了。
服務器端返回一個響應碼,指示此次請求的狀態,響應的形式是這樣的:
200 OK
[響應頭部]
複製代碼
而後是一個換行,接下來有效載荷(payload),也就是 www.google.com
的HTML內容。服務器下面可能會關閉鏈接,若是客戶端請求保持鏈接的話,服務器端會保持鏈接打開,以供以後的請求重用。
若是瀏覽器發送的HTTP頭部包含了足夠多的信息(例如包含了 Etag 頭部),以致於服務器能夠判斷出,瀏覽器緩存的文件版本自從上次獲取以後沒有再更改過,服務器可能會返回這樣的響應:
304 Not Modified
[響應頭部]
複製代碼
這個響應沒有有效載荷,瀏覽器會從本身的緩存中取出想要的內容。
在解析完 HTML以後,瀏覽器和客戶端會重複上面的過程,直到HTML頁面引入的全部資源(圖片,CSS,favicon.ico等等)所有都獲取完畢,區別只是頭部的GET / HTTP/1.1
會變成 GET /$(相對www.google.com的URL) HTTP/1.1
。
若是HTML引入了 www.google.com
域名以外的資源,瀏覽器會回到上面解析域名那一步,按照下面的步驟往下一步一步執行,請求中的Host
頭部會變成另外的域名。
HTTPD(HTTP Daemon)在服務器端處理請求/響應。最多見的 HTTPD 有 Linux上經常使用的 Apache 和 nginx,以及 Windows 上的 IIS。
HTTPD 接收請求
服務器把請求拆分爲如下幾個參數: - HTTP 請求方法(GET
, POST
, HEAD
, PUT
, DELETE
, CONNECT
, OPTIONS
, 或者 TRACE
)。直接在地址欄中輸入 URL 這種狀況下,使用的是 GET 方法 - 域名:google.com - 請求路徑/頁面:/ (咱們沒有請求google.com下的指定的頁面,所以 / 是默認的路徑)
服務器驗證其上已經配置了 google.com 的虛擬主機
服務器驗證 google.com 接受 GET 方法
服務器驗證該用戶可使用 GET 方法(根據 IP 地址,身份信息等)
若是服務器安裝了 URL 重寫模塊(例如 Apache 的 mod_rewrite 和 IIS 的URL Rewrite),服務器會嘗試匹配重寫規則,若是匹配上的話,服務器會按照規則重寫這個請求
服務器根據請求信息獲取相應的響應內容,這種狀況下因爲訪問路徑是 "/",會訪問首頁文件(你能夠重寫這個規則,可是這個是最經常使用的)。
服務器會使用指定的處理程序分析處理這個文件,假如 Google 使用PHP,服務器會使用 PHP 解析 index文件,並捕獲輸出,把 PHP的輸出結果返回給請求者
當服務器提供了資源以後(HTML,CSS,JS,圖片等),瀏覽器會執行下面的操做:
瀏覽器的功能是從服務器上取回你想要的資源,而後展現在瀏覽器窗口當中。資源一般是HTML 文件,也多是 PDF,圖片,或者其餘類型的內容。資源的位置經過用戶提供的 URI(Uniform Resource Identifier) 來肯定。
瀏覽器解釋和展現 HTML 文件的方法,在 HTML 和 CSS的標準中有詳細介紹。這些標準由 Web 標準組織 W3C(World Wide Web Consortium) 維護。
不一樣瀏覽器的用戶界面大都十分接近,有不少共同的 UI 元素:
瀏覽器高層架構
組成瀏覽器的組件有:
瀏覽器渲染引擎從網絡層取得請求的文檔,通常狀況下文檔會分紅8kB大小的分塊傳輸。
HTML 解析器的主要工做是對 HTML 文檔進行解析,生成解析樹。
解析樹是以 DOM 元素以及屬性爲節點的樹。DOM是文檔對象模型(Document Object Model)的縮寫,它是 HTML 文檔的對象表示,同時也是 HTML 元素面向外部(如Javascript)的接口。樹的根部是"Document"對象。整個 DOM 和HTML 文檔幾乎是一對一的關係。
解析算法
HTML不能使用常見的自頂向下或自底向上方法來進行分析。主要緣由有如下幾點:
因爲不能使用經常使用的解析技術,瀏覽器創造了專門用於解析 HTML的解析器。解析算法在 HTML5標準規範中有詳細介紹,算法主要包含了兩個階段:標記化(tokenization)和樹的構建。
解析結束以後
瀏覽器開始加載網頁的外部資源(CSS,圖像,Javascript 文件等)。
此時瀏覽器把文檔標記爲可交互的(interactive),瀏覽器開始解析處於「推遲(deferred)」模式的腳本,也就是那些須要在文檔解析完畢以後再執行的腳本。以後文檔的狀態會變爲「完成(complete)」,瀏覽器會觸發「加載(load)」事件。
注意解析 HTML 網頁時永遠不會出現「無效語法(Invalid Syntax)」錯誤,瀏覽器會修復全部錯誤內容,而後繼續解析。
<style>
標籤包含的內容以及 style 屬性的值StyleSheet object
),這個對象裏包含了帶有選擇器的CSS規則,和對應CSS語法的對象floated
,位置有 absolutely
或 relatively
屬性的時候,會有更多複雜的計算,詳見http://dev.w3.org/csswg/css2/ 和 www.w3.org/Style/CSS/c…CPU
,也可能使用圖形處理器 GPU
GPU
用於圖形渲染時,圖形驅動軟件會把任務分紅多個部分,這樣能夠充分利用GPU
強大的並行計算能力,用於在渲染過程當中進行大量的浮點計算。渲染結束後,瀏覽器根據某些時間機制運行JavaScript代碼(好比Google Doodle動畫)或與用戶交互(在搜索欄輸入關鍵字得到搜索建議)。相似Flash和Java的插件也會運行,儘管Google主頁裏沒有。這些腳本能夠觸發網絡請求,也可能改變網頁的內容和佈局,產生又一輪渲染與繪製。
我的微信公衆號:
我的github:
我的博客: