一個(gè)古老的面試問(wèn)題:當(dāng)你在瀏覽器中輸入google.com并且按下回車之后發(fā)生了什么?

不過(guò)我們不再局限于平常的回答,而是想辦法回答地盡可能具體,不遺漏任何細(xì)節(jié)。

回車鍵按下

為了從頭開(kāi)始,我們選擇鍵盤上的回車鍵被按到最低處作為起點(diǎn)。在這個(gè)時(shí)刻,一個(gè)專用于回車鍵的電流回路被直接或者通過(guò)電容器閉合了,使得少量的電流進(jìn)入了鍵盤的邏輯電路系統(tǒng)。這個(gè)系統(tǒng)會(huì)掃描每個(gè)鍵的狀態(tài),對(duì)于按鍵開(kāi)關(guān)的電位彈跳變化進(jìn)行噪音消除(debounce),并將其轉(zhuǎn)化為鍵盤碼值。在這里,回車的碼值是13。鍵盤控制器在得到碼值之后,將其編碼,用于之后的傳輸。現(xiàn)在這個(gè)傳輸過(guò)程幾乎都是通過(guò)通用串行總線(USB)或者藍(lán)牙(Bluetooth)來(lái)進(jìn)行的,以前是通過(guò)PS/2或者ADB連接進(jìn)行。

USB鍵盤:

虛擬鍵盤(觸屏設(shè)備):

產(chǎn)生中斷[非USB鍵盤]

鍵盤在它的中斷請(qǐng)求線(IRQ)上發(fā)送信號(hào),信號(hào)會(huì)被中斷控制器映射到一個(gè)中斷向量,實(shí)際上就是一個(gè)整型數(shù) 。CPU使用中斷描述符表(IDT)把中斷向量映射到對(duì)應(yīng)函數(shù),這些函數(shù)被稱為中斷處理器,它們由操作系統(tǒng)內(nèi)核提供。當(dāng)一個(gè)中斷到達(dá)時(shí),CPU根據(jù)IDT和中斷向量索引到對(duì)應(yīng)的中端處理器,然后操作系統(tǒng)內(nèi)核出場(chǎng)了。

(Windows)一個(gè) WM_KEYDOWN 消息被發(fā)往應(yīng)用程序

HID把鍵盤按下的事件傳送給 KBDHID.sys 驅(qū)動(dòng),把HID的信號(hào)轉(zhuǎn)換成一個(gè)掃描碼(Scancode),這里回車的掃描碼是 VK_RETURN(0x0d)。 KBDHID.sys 驅(qū)動(dòng)和 KBDCLASS.sys (鍵盤類驅(qū)動(dòng),keyboard class driver)進(jìn)行交互,這個(gè)驅(qū)動(dòng)負(fù)責(zé)安全地處理所有鍵盤和小鍵盤的輸入事件。之后它又去調(diào)用 Win32K.sys ,在這之前有可能把消息傳遞給安裝的第三方鍵盤過(guò)濾器。這些都是發(fā)生在內(nèi)核模式。

Win32K.sys 通過(guò) GetForegroundWindow() API函數(shù)找到當(dāng)前哪個(gè)窗口是活躍的。這個(gè)API函數(shù)提供了當(dāng)前瀏覽器的地址欄的句柄。Windows系統(tǒng)的"message pump"機(jī)制調(diào)用 SendMessage(hWnd, WM_KEYDOWN, VK_RETURN, lParam) 函數(shù), lParam 是一個(gè)用來(lái)指示這個(gè)按鍵的更多信息的掩碼,這些信息包括按鍵重復(fù)次數(shù)(這里是0),實(shí)際掃描碼(可能依賴于OEM廠商,不過(guò)通常不會(huì)是 VK_RETURN ),功能鍵(alt, shift, ctrl)是否被按下(在這里沒(méi)有),以及一些其他狀態(tài)。

Windows的 SendMessage API直接將消息添加到特定窗口句柄 hWnd 的消息隊(duì)列中,之后賦給 hWnd 的主要消息處理函數(shù) WindowProc 將會(huì)被調(diào)用,用于處理隊(duì)列中的消息。

當(dāng)前活躍的句柄 hWnd 實(shí)際上是一個(gè)edit control控件,這種情況下,WindowProc 有一個(gè)用于處理 WM_KEYDOWN 消息的處理器,這段代碼會(huì)查看 SendMessage 傳入的第三個(gè)參數(shù) wParam ,因?yàn)檫@個(gè)參數(shù)是 VK_RETURN ,于是它知道用戶按下了回車鍵。

(Mac OS X)一個(gè) KeyDown NSEvent被發(fā)往應(yīng)用程序

中斷信號(hào)引發(fā)了I/O Kit Kext鍵盤驅(qū)動(dòng)的中斷處理事件,驅(qū)動(dòng)把信號(hào)翻譯成鍵碼值,然后傳給OS X的 WindowServer 進(jìn)程。然后, WindowServer 將這個(gè)事件通過(guò)Mach端口分發(fā)給合適的(活躍的,或者正在監(jiān)聽(tīng)的)應(yīng)用程序,這個(gè)信號(hào)會(huì)被放到應(yīng)用程序的消息隊(duì)列里。隊(duì)列中的消息可以被擁有足夠高權(quán)限的線程使用 mach_ipc_dispatch 函數(shù)讀取到。這個(gè)過(guò)程通常是由 NSApplication 主事件循環(huán)產(chǎn)生并且處理的,通過(guò) NSEventType 為 KeyDown 的 NSEvent 。

(GNU/Linux)Xorg 服務(wù)器監(jiān)聽(tīng)鍵碼值

當(dāng)使用圖形化的 X Server 時(shí),X Server會(huì)按照特定的規(guī)則把鍵碼值再一次映射,映射成掃描碼。當(dāng)這個(gè)映射過(guò)程完成之后, X Server 把這個(gè)按鍵字符發(fā)送給窗口管理器(DWM,metacity, i3等等),窗口管理器再把字符發(fā)送給當(dāng)前窗口。當(dāng)前窗口使用有關(guān)圖形API把文字打印在輸入框內(nèi)。

解析URL

瀏覽器通過(guò)URL能夠知道下面的信息:

輸入的是URL還是搜索的關(guān)鍵字?

當(dāng)協(xié)議或主機(jī)名不合法時(shí),瀏覽器會(huì)將地址欄中輸入的文字傳給默認(rèn)的搜索引擎。大部分情況下,在把文字傳遞給搜索引擎的時(shí)候,URL會(huì)帶有特定的一串字符,用來(lái)告訴搜索引擎這次搜索來(lái)自這個(gè)特定瀏覽器。

檢查HSTS列表···

轉(zhuǎn)換非ASCII的Unicode字符

DNS查詢···

ARP

要想發(fā)送ARP廣播,我們需要有一個(gè)目標(biāo)IP地址,同時(shí)還需要知道用于發(fā)送ARP廣播的接口的Mac地址。

如果緩存沒(méi)有命中:

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

根據(jù)連接主機(jī)和路由器的硬件類型不同,可以分為以下幾種情況:

直連:

集線器:

交換機(jī):

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

現(xiàn)在我們有了DNS服務(wù)器或者默認(rèn)網(wǎng)關(guān)的IP地址,我們可以繼續(xù)DNS請(qǐng)求了:

使用套接字

當(dāng)瀏覽器得到了目標(biāo)服務(wù)器的IP地址,以及URL中給出來(lái)端口號(hào)(http協(xié)議默認(rèn)端口號(hào)是80, https默認(rèn)端口號(hào)是443),它會(huì)調(diào)用系統(tǒng)庫(kù)函數(shù) socket ,請(qǐng)求一個(gè) TCP流套接字,對(duì)應(yīng)的參數(shù)是 AF_INET 和 SOCK_STREAM 。

到了現(xiàn)在,TCP封包已經(jīng)準(zhǔn)備好了,可是使用下面的方式進(jìn)行傳輸:

對(duì)于大部分家庭網(wǎng)絡(luò)和小型企業(yè)網(wǎng)絡(luò)來(lái)說(shuō),封包會(huì)從本地計(jì)算機(jī)出發(fā),經(jīng)過(guò)本地網(wǎng)絡(luò),再通過(guò)調(diào)制解調(diào)器把數(shù)字信號(hào)轉(zhuǎn)換成模擬信號(hào),使其適于在電話線路,有線電視光纜和無(wú)線電話線路上傳輸。在傳輸線路的另一端,是另外一個(gè)調(diào)制解調(diào)器,它把模擬信號(hào)轉(zhuǎn)換回?cái)?shù)字信號(hào),交由下一個(gè) 網(wǎng)絡(luò)節(jié)點(diǎn) 處理。節(jié)點(diǎn)的目標(biāo)地址和源地址將在后面討論。

大型企業(yè)和比較新的住宅通常使用光纖或直接以太網(wǎng)連接,這種情況下信號(hào)一直是數(shù)字的,會(huì)被直接傳到下一個(gè) 網(wǎng)絡(luò)節(jié)點(diǎn) 進(jìn)行處理。

最終封包會(huì)到達(dá)管理本地子網(wǎng)的路由器。在那里出發(fā),它會(huì)繼續(xù)經(jīng)過(guò)自治區(qū)域的邊界路由器,其他自治區(qū)域,最終到達(dá)目標(biāo)服務(wù)器。一路上經(jīng)過(guò)的這些路由器會(huì)從IP數(shù)據(jù)報(bào)頭部里提取出目標(biāo)地址,并將封包正確地路由到下一個(gè)目的地。IP數(shù)據(jù)報(bào)頭部TTL域的值每經(jīng)過(guò)一個(gè)路由器就減1,如果封包的TTL變?yōu)?,或者路由器由于網(wǎng)絡(luò)擁堵等原因封包隊(duì)列滿了,那么這個(gè)包會(huì)被路由器丟棄。

上面的發(fā)送和接受過(guò)程在TCP連接期間會(huì)發(fā)生很多次:

UDP 數(shù)據(jù)包

TLS 握手

TCP 數(shù)據(jù)包

HTTP 協(xié)議···

如果瀏覽器是Google出品的,它不會(huì)使用HTTP協(xié)議來(lái)獲取頁(yè)面信息,而是會(huì)與服務(wù)器端發(fā)送請(qǐng)求,商討使用SPDY協(xié)議。

如果瀏覽器使用HTTP協(xié)議,它會(huì)向服務(wù)器發(fā)送這樣的一個(gè)請(qǐng)求:

GET / HTTP/1.1
Host: google.com
[其他頭部]

“其他頭部”包含了一系列的由冒號(hào)分割開(kāi)的鍵值對(duì),它們的格式符合HTTP協(xié)議標(biāo)準(zhǔn),它們之間由一個(gè)換行符分割開(kāi)來(lái)。這里我們假設(shè)瀏覽器沒(méi)有違反HTTP協(xié)議標(biāo)準(zhǔn)的bug,同時(shí)瀏覽器使用 HTTP/1.1 協(xié)議,不然的話頭部可能不包含 Host 字段,同時(shí) GET 請(qǐng)求中的版本號(hào)會(huì)變成 HTTP/1.0 或者 HTTP/0.9 。

HTTP/1.1 定義了“關(guān)閉連接”的選項(xiàng) "close",發(fā)送者使用這個(gè)選項(xiàng)指示這次連接在響應(yīng)結(jié)束之后會(huì)斷開(kāi):

Connection:close

不支持持久連接的 HTTP/1.1 必須在每條消息中都包含 "close" 選項(xiàng)。

在發(fā)送完這些請(qǐng)求和頭部之后,瀏覽器發(fā)送一個(gè)換行符,表示要發(fā)送的內(nèi)容已經(jīng)結(jié)束了。

服務(wù)器端返回一個(gè)響應(yīng)碼,指示這次請(qǐng)求的狀態(tài),響應(yīng)的形式是這樣的:

200 OK
[response headers]

然后是一個(gè)換行,接下來(lái)有效載荷(payload),也就是 www.google.com 的HTML內(nèi)容。服務(wù)器下面可能會(huì)關(guān)閉連接,如果客戶端請(qǐng)求保持連接的話,服務(wù)器端會(huì)保持連接打開(kāi),以供以后的請(qǐng)求重用。

如果瀏覽器發(fā)送的HTTP頭部包含了足夠多的信息(例如包含了 Etag 頭部,以至于服務(wù)器可以判斷出,瀏覽器緩存的文件版本自從上次獲取之后沒(méi)有再更改過(guò),服務(wù)器可能會(huì)返回這樣的響應(yīng):

304 Not Modified
[response headers]

這個(gè)響應(yīng)沒(méi)有有效載荷,瀏覽器會(huì)從自己的緩存中取出想要的內(nèi)容。

在解析完HTML之后,瀏覽器和客戶端會(huì)重復(fù)上面的過(guò)程,直到HTML頁(yè)面引入的所有資源(圖片,CSS,favicon.ico等等)全部都獲取完畢,區(qū)別只是頭部的 GET / HTTP/1.1 會(huì)變成 GET /$(相對(duì)www.google.com的URL) HTTP/1.1 。

如果HTML引入了 www.google.com 域名之外的資源,瀏覽器會(huì)回到上面解析域名那一步,按照下面的步驟往下一步一步執(zhí)行,請(qǐng)求中的 Host 頭部會(huì)變成另外的域名。

HTTP服務(wù)器請(qǐng)求處理

HTTPD(HTTP Daemon)在服務(wù)器端處理請(qǐng)求/相應(yīng)。最常見(jiàn)的 HTTPD 有 Linux 上常用的 Apache 和 nginx,與 Windows 上的 IIS。

瀏覽器背后的故事

當(dāng)服務(wù)器提供了資源之后(HTML,CSS,JS,圖片等),瀏覽器會(huì)執(zhí)行下面的操作:

瀏覽器

瀏覽器的功能是從服務(wù)器上取回你想要的資源,然后展示在瀏覽器窗口當(dāng)中。資源通常是 HTML 文件,也可能是 PDF,圖片,或者其他類型的內(nèi)容。資源的位置通過(guò)用戶提供的 URI(Uniform Resource Identifier) 來(lái)確定。

瀏覽器解釋和展示 HTML 文件的方法,在 HTML 和 CSS 的標(biāo)準(zhǔn)中有詳細(xì)介紹。這些標(biāo)準(zhǔn)由 Web 標(biāo)準(zhǔn)組織 W3C(World Wide Web Consortium) 維護(hù)。

不同瀏覽器的用戶界面大都十分接近,有很多共同的 UI 元素:

瀏覽器高層架構(gòu)

組成瀏覽器的組件有:

HTML 解析

瀏覽器渲染引擎從網(wǎng)絡(luò)層取得請(qǐng)求的文檔,一般情況下文檔會(huì)分成8kB大小的分塊傳輸。

HTML解析器的主要工作是對(duì)HTML文檔進(jìn)行解析,生成解析樹。

解析樹是以DOM元素以及屬性為節(jié)點(diǎn)的樹。DOM是文檔對(duì)象模型(Document Object Model)的縮寫,它是HTML文檔的對(duì)象表示,同時(shí)也是HTML元素面向外部(如Javascript)的接口。樹的根部是"Document"對(duì)象。整個(gè)DOM和HTML文檔幾乎是一對(duì)一的關(guān)系。

解析算法

HTML不能使用常見(jiàn)的自頂向下或自底向上方法來(lái)進(jìn)行分析。主要原因有以下幾點(diǎn):

由于不能使用常用的解析技術(shù),瀏覽器創(chuàng)造了專門用于解析HTML的解析器。解析算法在 HTML5 標(biāo)準(zhǔn)規(guī)范中有詳細(xì)介紹,算法主要包含了兩個(gè)階段:標(biāo)記化(tokenization)和樹的構(gòu)建。

解析結(jié)束之后

瀏覽器開(kāi)始加載網(wǎng)頁(yè)的外部資源(CSS,圖像,Javascript 文件等)。

此時(shí)瀏覽器把文檔標(biāo)記為“可交互的”,瀏覽器開(kāi)始解析處于“推遲”模式的腳本,也就是那些需要在文檔解析完畢之后再執(zhí)行的腳本。之后文檔的狀態(tài)會(huì)變?yōu)椤巴瓿伞?,瀏覽器會(huì)進(jìn)行“加載”事件。

注意解析 HTML 網(wǎng)頁(yè)時(shí)永遠(yuǎn)不會(huì)出現(xiàn)“語(yǔ)法錯(cuò)誤”,瀏覽器會(huì)修復(fù)所有錯(cuò)誤,然后繼續(xù)解析。

執(zhí)行同步 Javascript 代碼。

CSS 解析

頁(yè)面渲染

GPU 渲染

Window Server

后期渲染與用戶引發(fā)的處理

渲染結(jié)束后,瀏覽器根據(jù)某些時(shí)間機(jī)制運(yùn)行JavaScript代碼(比如Google Doodle動(dòng)畫)或與用戶交互(在搜索欄輸入關(guān)鍵字獲得搜索建議)。類似Flash和Java的插件也會(huì)運(yùn)行,盡管Google主頁(yè)里沒(méi)有。這些腳本可以觸發(fā)網(wǎng)絡(luò)請(qǐng)求,也可能改變網(wǎng)頁(yè)的內(nèi)容和布局,產(chǎn)生又一輪渲染與繪制。

  哈爾濱品用軟件有限公司致力于為哈爾濱的中小企業(yè)制作大氣、美觀的優(yōu)秀網(wǎng)站,并且能夠搭建符合百度排名規(guī)范的網(wǎng)站基底,使您的網(wǎng)站無(wú)需額外費(fèi)用,即可穩(wěn)步提升排名至首頁(yè)。歡迎體驗(yàn)最佳的哈爾濱網(wǎng)站建設(shè)。