從輸入 URL 到頁面加載完成的過程當中都發生了什麼事情?

不過寫這篇文章並非爲了幫你們準備面試,而是想借這道題來介紹計算機和互聯網的基礎知識,讓讀者瞭解它們之間是如何關聯起來的。javascript

爲了便於理解,我將整個過程分爲了六個問題來展開。php

第一個問題:從輸入 URL 到瀏覽器接收的過程當中發生了什麼事情?

從觸屏到 CPU

首先是「輸入 URL」,大部分人的第一反應會是鍵盤,不過爲了與時俱進,這裏將介紹觸摸屏設備的交互。css

觸摸屏一種傳感器,目前大可能是基於電容(Capacitive)來實現的,之前都是直接覆蓋在顯示屏上的,不過最近出現了 3 種嵌入到顯示屏中的技術,第一種是 iPhone 5 的 In-cell,它能減少了 0.5 毫米的厚度,第二種是三星使用的 On-cell 技術,第三種是國內廠商喜歡用的 OGS 全貼合技術,具體細節能夠閱讀這篇文章html

當手指在這個傳感器上觸摸時,有些電子會傳遞到手上,從而致使該區域的電壓變化,觸摸屏控制器芯片根據這個變化就能計算出所觸摸的位置,而後經過總線接口將信號傳到 CPU 的引腳上。前端

以 Nexus 5 爲例,它所使用的觸屏控制器是 Synaptics S3350B,總線接口爲 I²C,如下是 Synaptics 觸摸屏和處理器鏈接的示例:vue

clearpad-i2c

左邊是處理器,右邊是觸摸屏控制器,中間的 SDA 和 SCL 連線就是 I²C 總線接口。html5

CPU 內部的處理

移動設備中的 CPU 並非一個單獨的芯片,而是和 GPU 等芯片集成在一塊兒,被稱爲 SoC(片上系統)。java

前面提到了觸屏和 CPU 的鏈接,這個鏈接和大部分計算機內部的鏈接同樣,都是經過電氣信號來進行通訊的,也就是電壓高低的變化,以下面的時序圖:node

i2c-wave

在時鐘的控制下,這些電流會通過 MOSFET 晶體管,晶體管中包含 N 型半導體和 P 型半導體,經過電壓就能控制線路開閉,而後這些 MOSFET 構成了 CMOS,接着再由 CMOS 實現「與」「或」「非」等邏輯電路門,最後由邏輯電路門上就能實現加法、位移等計算,總體以下圖所示(來自《計算機體系結構》):linux

cpu

除了計算,在 CPU 中還須要存儲單元來加載和存儲數據,這個存儲單元通常經過觸發器(Flip-flop)來實現,稱爲寄存器。

以上這些概念都比較抽象,推薦閱讀「How to Build an 8-Bit Computer」這篇文章,做者基於晶體管、二極管、電容等原件製做了一個 8 位的計算機,支持簡單彙編指令和結果輸出,雖然現代 CPU 的實現要比這個複雜得多,但基本原理仍是同樣的。

另外其實我也是剛開始學習 CPU 芯片的實現,因此就不在這誤人子弟了,感興趣的讀者請閱讀本節後面推薦的書籍。

從 CPU 到操做系統內核

前面說到觸屏控制器將電氣信號發送到 CPU 對應的引腳上,接着就會觸發 CPU 的中斷機制,以 Linux 爲例,每一個外部設備都有一標識符,稱爲中斷請求(IRQ)號,能夠經過 /proc/interrupts 文件來查看系統中全部設備的中斷請求號,如下是 Nexus 7 (2013) 的部分結果:

 
 

shell@flo:/ $ cat /proc/interrupts

CPU0

 17:          0       GIC  dg_timer

294:    1973609   msmgpio  elan-ktf3k

314:        679   msmgpio  KEY_POWER

由於 Nexus 7 使用了 ELAN 的觸屏控制器,因此結果中的 elan-ktf3k 就是觸屏的中斷請求信息,其中 294 是中斷號,1973609 是觸發的次數(手指單擊時會產生兩次中斷,但滑動時會產生上百次中斷)。

爲了簡化這裏不考慮優先級問題,以 ARMv7 架構的處理器爲例,當中斷髮生時,CPU 會停下當前運行的程序,保存當前執行狀態(如 PC 值),進入 IRQ 狀態),而後跳轉到對應的中斷處理程序執行,這個程序通常由第三方內核驅動來實現,好比前面提到的 Nexus 7 的驅動源碼在這裏touchscreen/ektf3k.c

這個驅動程序將讀取 I²C 總線中傳來的位置數據,而後經過內核的 input_report_abs 等方法記錄觸屏按下座標等信息,最後由內核中的input 子模塊將這些信息都寫進 /dev/input/event0 這個設備文件中,好比下面展現了一次觸摸事件所產生的信息:

130|shell@flo:/ $ getevent -lt /dev/input/event0

[  414624.658986] EV_ABS       ABS_MT_TRACKING_ID   0000835c

[  414624.659017] EV_ABS       ABS_MT_TOUCH_MAJOR   0000000b

[  414624.659047] EV_ABS       ABS_MT_PRESSURE      0000001d

[  414624.659047] EV_ABS       ABS_MT_POSITION_X    000003f0

[  414624.659078] EV_ABS       ABS_MT_POSITION_Y    00000588

[  414624.659078] EV_SYN       SYN_REPORT           00000000

[  414624.699239] EV_ABS       ABS_MT_TRACKING_ID   ffffffff

[  414624.699270] EV_SYN       SYN_REPORT           00000000

從操做系統 GUI 到瀏覽器

前面提到 Linux 內核已經完成了對硬件的抽象,其它程序只須要經過監聽 /dev/input/event0 文件的變化就能知道用戶進行了哪些觸摸操做,不過若是每一個程序都這麼作實在太麻煩了,因此在圖像操做系統中都會包含 GUI 框架來方便應用程序開發,好比 Linux 下著名的X

但 Android 並無使用 X,而是本身實現了一套 GUI 框架,其中有個 EventHub 的服務會經過epoll 方式監聽 /dev/input/ 目錄下的文件,而後將這些信息傳遞到 Android 的窗口管理服務(WindowManagerService)中,它會根據位置信息來查找相應的 app,而後調用其中的監聽函數(如 onTouch 等)。

就這樣,咱們解答了第一個問題,不過因爲時間有限,這裏省略了不少細節,想進一步學習的讀者推薦閱讀如下書籍。

擴展學習

第二個問題:瀏覽器如何向網卡發送數據?

從瀏覽器到瀏覽器內核

前面提到操做系統 GUI 將輸入事件傳遞到了瀏覽器中,在這過程當中,瀏覽器可能會作一些預處理,好比 Chrome 會根據歷史統計來預估所輸入字符對應的網站,好比輸入了「ba」,根據以前的歷史發現 90% 的機率會訪問「www.baidu.com 」,所以就會在輸入回車前就立刻開始創建 TCP 連接甚至渲染了,這裏面還有不少其它策略,感興趣的讀者推薦閱讀 High Performance Networking in Chrome

接着是輸入 URL 後的「回車」,這時瀏覽器會對 URL 進行檢查,首先判斷協議,若是是 http 就按照 Web 來處理,另外還會對這個 URL 進行安全檢查,而後直接調用瀏覽器內核中的對應方法,好比 WebView 中的 loadUrl 方法。

在瀏覽器內核中會先查看緩存,而後設置 UA 等 HTTP 信息,接着調用不一樣平臺下網絡請求的方法。

須要注意瀏覽器和瀏覽器內核是不一樣的概念,瀏覽器指的是 Chrome、Firefox,而瀏覽器內核則是 Blink、Gecko,瀏覽器內核只負責渲染,GUI 及網絡鏈接等跨平臺工做則是瀏覽器實現的

HTTP 請求的發送

由於網絡的底層實現是和內核相關的,因此這一部分須要針對不一樣平臺進行處理,從應用層角度看主要作兩件事情:經過 DNS 查詢 IP、經過 Socket 發送數據,接下來就分別介紹這兩方面的內容。

DNS 查詢

應用程序能夠直接調用 Libc 提供的 getaddrinfo() 方法來實現 DNS 查詢。

DNS 查詢實際上是基於 UDP 來實現的,這裏咱們經過一個具體例子來了解它的查找過程,如下是使用 dig +trace fex.baidu.com 命令獲得的結果(省略了一些):

 
 

; <<>> DiG 9.8.3-P1 <<>> +trace fex.baidu.com

;; global options: +cmd

.           11157   IN  NS  g.root-servers.net.

.           11157   IN  NS  i.root-servers.net.

.           11157   IN  NS  j.root-servers.net.

.           11157   IN  NS  a.root-servers.net.

.           11157   IN  NS  l.root-servers.net.

;; Received 228 bytes from 8.8.8.8#53(8.8.8.8) in 220 ms

 

com.            172800  IN  NS  a.gtld-servers.net.

com.            172800  IN  NS  c.gtld-servers.net.

com.            172800  IN  NS  m.gtld-servers.net.

com.            172800  IN  NS  h.gtld-servers.net.

com.            172800  IN  NS  e.gtld-servers.net.

;; Received 503 bytes from 192.36.148.17#53(192.36.148.17) in 185 ms

 

baidu.com.      172800  IN  NS  dns.baidu.com.

baidu.com.      172800  IN  NS  ns2.baidu.com.

baidu.com.      172800  IN  NS  ns3.baidu.com.

baidu.com.      172800  IN  NS  ns4.baidu.com.

baidu.com.      172800  IN  NS  ns7.baidu.com.

;; Received 201 bytes from 192.48.79.30#53(192.48.79.30) in 1237 ms

 

fex.baidu.com.      7200    IN  CNAME   fexteam.duapp.com.

fexteam.duapp.com.  300 IN  CNAME   duapp.n.shifen.com.

n.shifen.com.       86400   IN  NS  ns1.n.shifen.com.

n.shifen.com.       86400   IN  NS  ns4.n.shifen.com.

n.shifen.com.       86400   IN  NS  ns2.n.shifen.com.

n.shifen.com.       86400   IN  NS  ns5.n.shifen.com.

n.shifen.com.       86400   IN  NS  ns3.n.shifen.com.

;; Received 258 bytes from 61.135.165.235#53(61.135.165.235) in 2 ms

能夠看到這是一個逐步縮小範圍的查找過程,首先由本機所設置的 DNS 服務器(8.8.8.8)向 DNS 根節點查詢負責 .com 區域的域務器,而後經過其中一個負責 .com 的服務器查詢負責 baidu.com 的服務器,最後由其中一個 baidu.com 的域名服務器查詢 fex.baidu.com 域名的地址。

可能你在查詢某些域名的時會發現和上面不同,最底將看到有個奇怪的服務器搶先返回結果。。。

這裏爲了方便描述,忽略了不少不一樣的狀況,好比 127.0.0.1 其實走的是 loopback,和網卡設備不要緊;好比 Chrome 會在瀏覽器啓動的時預先查詢 10 個你有可能訪問的域名;還有 Hosts 文件、緩存時間 TTL(Time to live)的影響等。

經過 Socket 發送數據

有了 IP 地址,就能夠經過 Socket API 來發送數據了,這時能夠選擇 TCP 或 UDP 協議,具體使用方法這裏就不介紹了,推薦閱讀 Beej's Guide to Network Programming

HTTP 經常使用的是 TCP 協議,因爲 TCP 協議的具體細節處處都能看到,因此本文就不介紹了,這裏談一下 TCP 的 Head-of-line blocking 問題:假設客戶端的發送了 3 個 TCP 片斷(segments),編號分別是 一、二、3,若是編號爲 1 的包傳輸時丟了,即使編號 2 和 3 已經到達也只能等待,由於 TCP 協議須要保證順序,這個問題在 HTTP pipelining 下更嚴重,由於 HTTP pipelining 可讓多個 HTTP 請求經過一個 TCP 發送,好比發送兩張圖片,可能第二張圖片的數據已經全收到了,但還得等第一張圖片的數據傳到。

爲了解決 TCP 協議的性能問題,Chrome 團隊去年提出了 QUIC 協議,它是基於 UDP 實現的可靠傳輸,比起 TCP,它能減小不少來回(round trip)時間,還有前向糾錯碼(Forward Error Correction)等功能。目前 Google Plus、 Gmail、Google Search、blogspot、Youtube 等幾乎大部分 Google 產品都在使用 QUIC,能夠經過 chrome://net-internals/#spdy 頁面來發現。

雖然目前除了 Google 還沒人用 QUIC,但我以爲挺有前景的,由於優化 TCP 須要升級系統內核(好比 Fast Open)。

瀏覽器對同一個域名有鏈接數限制,大部分是 6,我之前認爲將這個鏈接數改大後會提高性能,但實際上並非這樣的,Chrome 團隊有作過實驗,發現從 6 改爲 10 後性能反而降低了,形成這個現象的因素有不少,如創建鏈接的開銷、擁塞控制等問題,而像 SPDY、HTTP 2.0 協議儘管只使用一個 TCP 鏈接來傳輸數據,但性能反而更好,並且還能實現請求優先級。

另外,由於 HTTP 請求是純文本格式的,因此在 TCP 的數據段中能夠直接分析 HTTP 的文本,若是發現。。。

Socket 在內核中的實現

前面說到瀏覽器的跨平臺庫經過調用 Socket API 來發送數據,那麼 Socket API 是如何實現的呢?

以 Linux 爲例,它的實如今這裏 socket.c,目前我還不太瞭解,推薦讀者看看 Linux kernel map,它標註出了關鍵路徑的函數,方便學習從協議棧到網卡驅動的實現。

底層網絡協議的具體例子

接下來若是繼續介紹 IP 協議和 MAC 協議可能不少讀者會暈,因此本節將使用 Wireshark 來經過具體例子講解,如下是我請求百度首頁時抓取到的網絡數據:

wireshark

最底下是實際的二進制數據,中間是解析出來的各個字段值,能夠看到其中最底部爲 HTTP 協議(Hypertext Transfer Protocol),在 HTTP 以前有 54 字節(0x36),這就是底層網絡協議所帶來的開銷,咱們接下來對這些協議進行分析。

在 HTTP 之上是 TCP 協議(Transmission Control Protocol),它的具體內容以下圖所示:

wireshark

經過底部的二進制數據,能夠看到 TCP 協議是加在 HTTP 文本前面的,它有 20 個字節,其中定義了本地端口(Source port)和目標端口(Destination port)、順序序號(Sequence Number)、窗口長度等信息,如下是 TCP 協議各個部分數據的完整介紹:

 
 

0                   1                   2                   3

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|          Source Port          |       Destination Port        |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|                        Sequence Number                        |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|                    Acknowledgment Number                      |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|  Data |           |U|A|E|R|S|F|                               |

| Offset| Reserved  |R|C|O|S|Y|I|            Window             |

|       |           |G|K|L|T|N|N|                               |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|           Checksum            |         Urgent Pointer        |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|                    Options                    |    Padding    |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|                             data                              |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

具體各個字段的做用這裏就不介紹了,感興趣的讀者能夠閱讀 RFC 793,並結合抓包分析來理解。

須要注意的是,在 TCP 協議中並無 IP 地址信息,由於這是在上一層的 IP 協議中定義的,以下圖所示:

wireshark

IP 協議一樣是在 TCP 前面的,它也有 20 字節,在這裏指明瞭版本號(Version)爲 4,源(Source) IP 爲 192.168.1.106,目標(Destination) IP 爲 119.75.217.56,所以 IP 協議最重要的做用就是肯定 IP 地址。

由於 IP 協議中能夠查看到目標 IP 地址,因此若是發現某些特定的 IP 地址,某些路由器就會。。。

可是,光靠 IP 地址是沒法進行通訊的,由於 IP 地址並不和某臺設備綁定,好比你的筆記本的 IP 在家中是 192.168.1.1,但到公司就變成 172.22.22.22 了,因此在底層通訊時須要使用一個固定的地址,這就是 MAC(media access control) 地址,每一個網卡出廠時的 MAC 地址都是固定且惟一的。

所以再往上就是 MAC 協議,它有 14 字節,以下所示:

wireshark

當一臺電腦加入網絡時,須要經過 ARP 協議告訴其它網絡設備它的 IP 及對應的 MAC 地址是什麼,這樣其它設備就能經過 IP 地址來查找對應的設備了。

最頂上的 Frame 是表明 Wireshark 的抓包序號,並非網絡協議

就這樣,咱們解答了第二個問題,不過其實這裏面還有不少不少細節沒介紹,建議你們經過下面的書籍進一步學習。

擴展學習

第三個問題:數據如何從本機網卡發送到服務器?

從內核到網絡適配器(Network Interface Card)

前面說到調用 Socket API 後內核會對數據進行底層協議棧的封裝,接下來啓動 DMA 控制器,它將從內存中讀取數據寫入網卡。

以 Nexus 5 爲例,它使用的是博通 BCM4339 芯片通訊,接口採用了 SD 卡同樣的 SDIO,但這個芯片的細節並無公開資料,因此這裏就不討論了。

鏈接 Wi-Fi 路由

Wi-Fi 網卡須要經過 Wi-Fi 路由來與外部通訊,原理是基於無線電,經過電流變化來產生無線電,這個過程也叫「調製」,而反過來無線電能夠引發電磁場變化,從而產生電流變化,利用這個原理就能將無線電中的信息解讀出來就叫「解調」,其中單位時間內變化的次數就稱爲頻率,目前在 Wi-Fi 中所採用的頻率分爲 2.4 GHz 和 5 GHz 兩種。

在同一個 Wi-Fi 路由下,由於採用的頻率相同,同時使用時會發生衝突,爲了解決這個問題,Wi-Fi 採用了被稱爲 CSMA/CA 的方法,簡單來講就是在傳輸前先確認信道是否已被使用,沒有才發送數據。

而一樣基於無線電原理的 2G/3G/LTE 也會遇到相似的問題,但它並無採用 Wi-Fi 那樣的獨佔方案,而是經過頻分(FDMA)、時分(TDMA)和碼分(CDMA)來進行復用,具體細節這裏就不展開了。

以小米路由爲例,它使用的芯片是 BCM 4709,這個芯片由 ARM Cortex-A9 處理器及流量(Flow)硬件加速組成,使用硬件芯片能夠避免通過操做系統中斷、上下文切換等操做,從而提高了性能。

路由器中的操做系統能夠基於 OpenWrt 或 DD-WRT 來開發的,具體細節我不太瞭解,因此就不展開了。

由於內網設備的 IP 都是相似 192.168.1.x 這樣的內網地址,外網沒法直接向這個地址發送數據,因此網絡數據在通過路由時,路由會修改相關地址和端口,這個操做稱爲 NAT 映射。

最後家庭路由通常會經過雙絞線鏈接到運營商網絡的。

運營商網絡內的路由

數據過雙絞線發送到運營商網絡後,還會通過不少箇中間路由轉發,讀者能夠經過 traceroute 命令或者在線可視化工具來查看這些路由的 ip 和位置。

當數據傳遞到這些路由器後,路由器會取出包中目的地址的前綴,經過內部的轉發表查找對應的輸出鏈路,而這個轉發表是如何獲得的呢?這就是路由器中最重要的選路算法了,可選的有不少,我對這方面並不太瞭解,看起來維基百科上的詞條列得很全。

主幹網間的傳輸

對於長線的數據傳輸,一般使用光纖做爲介質,光纖是基於光的全反射來實現的,使用光纖須要專門的發射器經過電致發光(好比 LED)將電信號轉成光,比起前面介紹的無線電和雙絞線,光纖信號的抗干擾性要強得多,並且能耗也小不少。

既然是基於光來傳輸數據,數據傳輸速度也就取決於光的速度,在真空中的光速接近於 30 萬公里/秒,因爲光纖包層(cladding)中的折射率(refractive index)爲 1.52,因此實際光速是 20 萬公里/秒左右,從首都機場飛往廣州白雲機場的距離是 1967 公里,按照這個距離來算須要花費 10 毫秒才能抵達。這意味着若是你在北京,服務器在廣州,等你發出數據到服務器返回數據至少得等 20 毫秒,實際狀況預計是 2- 3 倍,由於這其中還有各個節點路由處理的耗時,好比我測試了一個廣州的 IP 發現平均延遲爲 60 毫秒。

這個延遲是現有科技沒法解決的(除非找到超過光速的方法),只能經過 CDN 來讓傳輸距離變短,或儘可能減小串行的來回請求(好比 TCP 創建鏈接所需的 3 次握手)。

IDC 內網

數據經過光纖最終會來到服務器所在的 IDC 機房,進入 IDC 內網,這時能夠先經過分光器將流量鏡像一份出來方便進行安全檢查等分析,還能用來進行。。。

這裏的帶寬成本很高,是按照峯值來結算的,以每個月每 Gbps(注意這裏指的是 bit,而不是 Byte)爲單位,北京這邊價格在十萬人民幣以上,通常網站使用 1G 到 10G 不等。

接下來光纖中的數據將進入集羣(Cluster)交換機,而後再轉發到機架(Rack)頂部的交換機,最後經過這個交換機的端口將數據發往機架中的服務器,能夠參考下圖(來自 Open Compute):

rack

上圖左邊是正面,右邊是側面,能夠看到頂部爲交換機所留的位置。

之前這些交換機的內部實現是封閉的,相關廠商(如思科、Juniper 等)會使用特定的處理器和操做系統,外界難以進行靈活控制,甚至有時候須要手工配置,但這幾年隨着 OpenFlow 技術的流行,也出現了開放交換機硬件(Open Switch Hardware),好比 Intel 的網絡平臺,推薦感興趣的讀者建議看看它的視頻,比文字描述清晰多了。

須要注意的是,通常網絡書中提到的交換機都只具有二層(MAC 協議)的功能,但在 IDC 中的交換器基本上都具有三層(IP 協議)的功能,因此不須要有專門的路由了。

最後,由於 CPU 處理的是電氣信號,因此光纖中的光線須要先使用相關設備經過光電效應將光信號轉成電信號,而後進入服務器網卡。

服務器 CPU

前面說到數據已經到達服務器網卡了,接着網卡會將數據拷貝到內存中(DMA),而後經過中斷來通知 CPU,目前服務器端的 CPU 基本上都是 Intel Xeon,不過這幾年出現了一些新的架構,好比在存儲領域,百度使用 ARM 架構來提高存儲密度,由於 ARM 的功耗比 Xeon 低得多。而在高性能領域,Google 最近在嘗試基於 POWER 架構的 CPU 來開發的服務器,最新的 POWER8 處理器能夠並行執行 96 個線程,因此對高併發的應用應該頗有幫助。

擴展學習

第四個問題:服務器接收到數據後會進行哪些處理?

爲了不重複,這裏將再也不介紹操做系統,而是直接進入後端服務進程,因爲這方面有太多技術選型,因此我只挑幾個常見的公共部分來介紹。

負載均衡

請求在進入到真正的應用服務器前,可能還會先通過負責負載均衡的機器,它的做用是將請求合理地分配到多個服務器上,同時具有具有防攻擊等功能。

負載均衡具體實現有不少種,有直接基於硬件的 F5,有操做系統傳輸層(TCP)上的 LVS,也有在應用層(HTTP)實現的反向代理(也叫七層代理),接下來將介紹 LVS 及反向代理。

負載均衡的策略也有不少,若是後面的多個服務器性能均衡,最簡單的方法就是挨個循環一遍(Round-Robin),其它策略就不一一介紹了,能夠參考 LVS 中的算法

LVS

LVS 的做用是從對外看來只有一個 IP,而實際上這個 IP 後面對應是多臺機器,所以也被成爲 Virtual IP。

前面提到的 NAT 也是一種 LVS 中的工做模式,除此以外還有 DR 和 TUNNEL,具體細節這裏就不展開了,它們的缺點是沒法跨網段,因此百度本身開發了 BVS 系統。

反向代理

方向代理是工做在 HTTP 上的,具體實現能夠基於 HAProxy 或 Nginx,由於反向代理能理解 HTTP 協議,因此能作很是多的事情,好比:

  • 進行不少統一處理,好比防攻擊策略、放抓取、SSL、gzip、自動性能優化等

  • 應用層的分流策略都能在這裏作,好比對 /xx 路徑的請求分到 a 服務器,對 /yy 路徑的請求分到 b 服務器,或者按照 cookie 進行小流量測試等

  • 緩存,並在後端服務掛掉的時候顯示友好的 404 頁面

  • 監控後端服務是否異常

  • ⋯⋯

Nginx 的代碼寫得很是優秀,從中能學到不少,對高性能服務端開發感興趣的讀者必定要看看。

Web Server 中的處理

請求通過前面的負載均衡後,將進入到對應服務器上的 Web Server,好比 Apache、Tomcat、Node.JS 等。

以 Apache 爲例,在接收到請求後會交給一個獨立的進程來處理,咱們能夠經過編寫 Apache 擴展來處理,但這樣開發起來太麻煩了,因此通常會調用 PHP 等腳本語言來進行處理,好比在 CGI 下就是將 HTTP 中的參數放到環境變量中,而後啓動 PHP 進程來執行,或者使用 FastCGI 來預先啓動進程。

(等後續有空再單獨介紹 Node.JS 中的處理)

進入後端語言

前面說到 Web Server 會調用後端語言進程來處理 HTTP 請求(這個說法不徹底正確,有不少其它可能),那麼接下來就是後端語言的處理了,目前大部分後端語言都是基於虛擬機的,如 PHP、Java、JavaScript、Python 等,但這個領域的話題很是大,難以講清楚,對 PHP 感興趣的讀者能夠閱讀我以前寫的 HHVM 介紹文章,其中提到了不少虛擬機的基礎知識。

Web 框架(Framework)

若是你的 PHP 只是用來作簡單的我的主頁「Personal Home Page」,倒不必使用 Web 框架,但若是隨着代碼的增長會變得愈來愈難以管理,因此通常網站都會會基於某個 Web 框架來開發,所以在後端語言執行時首先進入 Web 框架的代碼,而後由框架再去調用應用的實現代碼。

可選的 Web 框架很是多,這裏就不一一介紹了。

讀取數據

這部分不展開了,從簡單的讀寫文件到數據中間層,這裏面可選的方案實在太多。

擴展學習

第五個問題:服務器返回數據後瀏覽器如何處理?

前面說到服務端處理完請求後,結果將經過網絡發回客戶端的瀏覽器,從本節開始將介紹瀏覽器接收到數據後的處理,值得一提的是這方面以前有一篇不錯的文章 How Browsers Work,因此不少內容我不想再重複介紹,所以將重點放在那篇文章所忽略的部分。

從 01 到字符

HTTP 請求返回的 HTML 傳遞到瀏覽器後,若是有 gzip 會先解壓,而後接下來最重要的問題是要知道它的編碼是什麼,好比一樣一個「中」字,在 UTF-8 編碼下它的內容實際上是「11100100 10111000 10101101」也就是「E4 B8 AD」,而在 GBK 下則是「11010110 11010000」,也就是「D6 D0」,如何才能知道文件的編碼?能夠有不少判斷方法:

  • 用戶設置,在瀏覽器中能夠指定頁面編碼

  • HTTP 協議中

  • <meta> 中的 charset 屬性值

  • 對於 JS 和 CSS

  • 對於 iframe

若是在這些地方都沒指明,瀏覽器就很難處理,在它看來就是一堆「0」和「1」,好比「中文」,它在 UTF-8 下有 6 個字節,若是按照 GBK 能夠當成「涓枃」這 3 個漢字來解釋,瀏覽器怎麼知道究竟是「中文」仍是「涓枃」呢?

不過正常人一眼就能認出「涓枃」是錯的,由於這 3 個字太不常見了,因此有人就想到經過判斷常見字的方法來檢測編碼,典型的好比 Mozilla 的 UniversalCharsetDetection,不過這東東誤判率也很高,因此仍是指明編碼的好。

這樣後續對文本的操做就是基於「字符」(Character)的了,一個漢字就是一個字符,不用再關心它到底是 2 個字節仍是 3 個字節。

外鏈資源的加載

(待補充,這裏有調度策略)

JavaScript 的執行

(後續再單獨介紹,推薦你們看 R 大去年整理的這個帖子,裏面有很是多相關資料,另外我兩年前曾講過 JavaScript 引擎中的性能優化,雖然有些內容不太正確了,但也能夠看看)

從字符到圖片

二維渲染中最複雜的要數文字顯示了,雖然想一想彷佛很簡單,不就是將某個文字對應的字形(glyph)找出來麼?在中文和英文中這樣作是沒問題的,由於一個字符就對應一個字形(glyph),在字體文件中找到字形,而後畫上去就能夠了,但在阿拉伯語中是不行的,由於它有有連體形式。

(之後續再單獨介紹,這裏很是複雜)

跨平臺 2D 繪製庫

在不一樣操做系統中都提供了本身的圖形繪製 API,好比 Mac OS X 下的 Quartz,Windows 下的 GDI 以及 Linux 下的 Xlib,但它們相互不兼容,因此爲了方便支持跨平臺繪圖,在 Chrome 中使用了 Skia 庫。

(之後再單獨介紹,Skia 內部實現調用層級太多,直接講代碼可能不適合初學者)

GPU 合成

(之後續再單獨介紹,雖然簡單來講就是靠貼圖,但還得介紹 OpenGL 以及 GPU 芯片,內容太長)

擴展學習

這節內容是我最熟悉,結果反而由於這樣纔想花更多時間寫好,因此等到之後再發出來好了,你們先能夠先看看如下幾個站點:

第六個問題:瀏覽器如何將頁面展示出來?

前面提到瀏覽器已經將頁面渲染成一張圖片了,接下來的問題就是如何將這張圖片展現在屏幕上。

Framebuffer

以 Linux 爲例,在應用中控制屏幕最直接的方法是將圖像的 bitmap 寫入 /dev/fb0 文件中,這個文件實際上一個內存區域的映射,這段內存區域稱爲 Framebuffer。

須要注意的是在硬件加速下,如 OpenGL 是不通過 Framebuffer 的。

從內存到 LCD

在手機的 SoC 中一般都會有一個 LCD 控制器,當 Framebuffer 準備好後,CPU 會經過 AMBA內部總線通知 LCD 控制器,而後這個控制器讀取 Framebuffer 中的數據,進行格式轉換、伽馬校訂等操做,最終經過 DSI、HDMI 等接口發往 LCD 顯示器。

以 OMAP5432 爲例,下圖是它所支持的一種並行數據傳輸:

DISPC-color24

LCD 顯示

最後簡單介紹一下 LCD 的顯示原理。

首先,要想讓人眼能看見,就必須有光線進入,要麼經過反射、要麼有光源,好比 Kindle 所使用的 E-ink 屏幕自己是不發光的,因此必須在有光線的地方纔能閱讀,它的優勢是省電,但限制太大,因此幾乎全部 LCD 都會自帶光源。

目前 LCD 中一般使用 LED 做爲光源,LED 接上電源後,在電壓的做用下,內部的正負電子結合會釋放光子,從而產生光,這種物理現象叫電致發光(Electroluminescence),這在前面介紹光纖時也介紹過。

如下是 iPod Touch 2 拆開後的樣子:(來自 Wikipedia):

IPod_Touch_2G_Backlight.jpg

在上圖中能夠看到 6 盞 LED,這就是整個屏幕的光源,這些光源將經過反射的反射輸出到屏幕中。

有了光源還得有色彩,在 LED 中一般作法是使用彩色濾光片(Color filter)來將 LED 光源轉成不一樣顏色。

另外直接使用三種顏色的 LED 也是可行的,它能避免了濾光致使的光子浪費,下降耗電,很適用於智能手錶這樣的小屏幕,Apple 收購的 LuxVue 公司就採用的是這種方式,感興趣的話能夠去研究它的專利

LCD 屏幕上的每一個物理像素點其實是由紅、綠、藍 3 種色彩的點組成,每一個顏色點能單獨控制,下面是用顯微鏡放大後的狀況(來自Wikipedia):

Wikipedia 上的圖片解釋

從上圖能夠看到每 3 種顏色的濾光片都全亮的時候就是白色,都滅就是黑色,若是你仔細看還能看到有些點並非徹底黑,這是字體上的反鋸齒效果。

經過這 3 種顏色亮度的不一樣組合就能產生出各類色彩,若是每一個顏色點能產生 256 種亮度,就能生成 256 * 256 * 256 = 16777216 種色彩。

並非全部顯示器的亮度都能達到 256,在選擇顯示器時有個參數是 8-Bit 或 6-Bit 面板,其中 8-Bit 的面板能在物理上達到 256 種亮度,而 6-Bit 的則只有 64 種,它須要靠刷新率控制(Frame rate control)技術來達到 256 的效果。

如何控制這些顏色點的亮度?這就要靠液晶體了,液晶體的特性是當有電流經過時會發生旋轉,從而將部分光線擋住,因此只要經過電壓控制液晶體的轉動就能控制這個顏色點的亮度,目前手機屏幕中一般使用 TFT 控制器來對其進行控制,在 TFT 中最著名的要數 IPS 面板。

這些過濾後的光線大部分會直接進入眼睛,有些光還會在其它表面上通過漫(diffuse)反射或鏡面(specular)反射後再進入眼睛,加上環境光的影響,要真正算出有多少光到眼睛是一個積分問題,感興趣的讀者能夠研究基於物理的渲染

當光線進入眼睛後,接下來就是生物學的領域了,因此咱們到此結束。

擴展學習

本文所忽略的內容

爲了編寫方便,前面的介紹中將不少底層細節實現忽略了,好比:

  • 內存相關

    • 堆,這裏的分配策略有不少,好比 malloc 的實現

    • 棧,函數調用,已經有不少優秀的文章或書籍介紹了

    • 內存映射,動態庫加載等

    • 隊列幾乎無處不在,但這些細節和原理沒太大關係

     

  • 各類緩存

    • CPU 的緩存、操做系統的緩存、HTTP 緩存、後端緩存等等

     

  • 各類監控

    • 不少日誌會保存下來以便後續分析

     

FAQ

從微博反饋來看,有些問題被常常問到,我就在這裏統一回答吧,若是有其它問題請在評論中問。

Q:學那麼多有什麼用?根本用不着

A:計算機是人類最強大的工具,你不想了解它是如何運做的麼?

Q:什麼都瞭解一點,還不如精通一項吧?

A:很是認同,初期確定須要先在某個領域精通,而後再去了解周邊領域的知識,這樣還能讓你對以前那個領域有更深入的理解。

Q:曬出來培養一堆麪霸跟本身過不去?

A:本文其實寫得很淺,每一個部分都能再深刻展開。

Q:這題要把人累死啊,說幾天都說不完的

A:哈哈哈,大神你暴露了,題目只是手段,目的是將你這樣的大牛挖掘出來。

你們的討論

很是感謝各位大牛的參與討論,這裏蒐集了其中的一些回答。

@WOODHEAD笨笨:請求被送往本地路由,接入商路由,旁路分析是否違法地址,鏈接被中斷,瀏覽器無辜得顯示網頁不存在。嚴重的有人來查水錶

caoz: 這不是個人面試題麼! 還有一道題,用戶反應咱們網站卡,請問都有哪些可能性,以及排方法。

@caoz:寫的仍是不錯的,可是仍是有一些缺漏,好比arp欺騙? 著名的GFW的阻斷策略,以及,一個URL可不是隻有一個請求,多個請求的排隊和尋址?此外,cdn, 智能dns解析機制等。//@ZRJ-: http://t.cn/8smHpMF 從點擊到呈現 — 詳解一次HTTP請求 我大三的時候寫的。。 啊

@唐福林:與時俱進,如今應該問從打開app到刷新出內容,整個過程當中都發生了什麼,若是感受慢,怎麼定位問題,怎麼解決

@寒冬winter: 回覆@Ivony:這題勝在區分度高,知識點覆蓋均勻,再不懂的人,也能答出幾句,而高手能夠根據本身擅長的領域自由發揮,從URL規範、HTTP協議、DNS、CDN、到瀏覽器流式解析、CSS規則構建、layout、paint、onload/domready、JS執行、JS API綁定⋯⋯

@JS小組:[哈哈] 小編想起來了,貌似剛從業那會兒,前端界最美麗的姐@sherrie_wong 面試問太小編這道題.而後我當時把知道的全說了,從瀏覽器解析,發請求,7層網絡模型實際用的模型,TCP三次握手.經路由,交換機,DNS,到服務器.在是否須要與文件系統仍是數據庫打交道,再者分佈式運算hadoop啥的...聊了太多.

@萵怖熵崴箔:這種就是流氓問題,我還想問從你按了鍵盤到屏幕上出現字符,中間都發生了什麼事,提示一下:設想你是一個電子。哦,不對,電子又是什麼

@寒冬winter:http://t.cn/zH20bR1 http://t.cn/zH20bR1 以前寫了開頭兩篇,後面荒廢中⋯⋯

@ils傳言:不提電廠發電機轉了幾圈的也幹掉!//@Philonis高:不提交換機和路由器工做原理的全乾掉!//@南非蜘蛛:從7層協議的角度說會比較全面。這種問題只有全棧工程師才能回答。

@聳肩的阿特拉斯閣下:DNS解析URL出IP/Port,瀏覽器鏈接並向此地址發出GET請求,web服務端(nginx、apache)接收到請求後,經過CGI等接口協議調用動態語言(php等),動態語言再鏈接數據庫查詢相應數據並處理,而後反饋給瀏覽器,瀏覽器解析反饋頁面,經過html、javascript、css處理後呈現到屏幕⋯⋯每一個細節的話估計要800頁的書

@一棹凌煙:這種面試題在系統領域的招聘裏其實簡單好使。還有一個相似的:從在鍵盤上敲下一個字符鍵開始,到在虛擬機裏的terminal裏顯示出來,中間的過程是什麼?

@ICT_朱亞東:記得6年前上胡偉武的芯片設計課,老胡第一節課就說,上完這門課,我但願大家能搞清楚,我翻了一頁PPT,計算機內部都作了那些流水操做,固然啦,我是一點都不記得了。

@julyclyde:咱們運維通常問一個TCP segment in a IP packet in an ethernet frame通過一個路由器以後發生什麼變化

@西西福廝:從瀏覽器提及,操做系統相應鍵盤中斷,事件隊列處理,到互聯網路由,到服務器網卡中斷,到最後輸出緩衝。。。細說能說兩小時。

@Xscape:從鍵盤中斷提及?回車前的預解析都很靠後了..//@純白色燃燒: 從鍵盤到彈簧入萬有引力然後直達量子力學。

@Bosn:而後從硬件再到電子⋯⋯量子…薛定諤之貓…平行宇宙⋯⋯乃至萬能的哲學!!

@imPony:可深刻到PN結中的電子流動層面

@鞏小東-TX: 猜一下,瀏覽器組http報文sock發出,proxy過濾,收處處理頭,未過時cache返回,http svr處理校驗包,轉爲cgi協議給後端,後端map url,load code,與邏輯交互後生成html給svr,svr過濾cache給proxy,proxy給瀏覽器,拉去js完成html,瀏覽器渲染。

@yuange1975:我算對整個過程比較清楚,包含服務器的處理,web服務器和瀏覽器的處理以及安全問題,估計少有對二者的安全都研究過的。但面試時要清晰的比較完整的把大塊流程列出來講明白,也有難度。估計也很難有機會時間去整理文章了。

@ShopEx王磊:我也問這個問題題好多年, 或者變通一下:從輸入URL到展示, 都涉及到哪些緩存環節, 緩存的更新機制是怎樣的

@一棹凌煙:這種面試題在系統領域的招聘裏其實簡單好使。還有一個相似的:從在鍵盤上敲下一個字符鍵開始,到在虛擬機裏的terminal裏顯示出來,中間的過程是什麼?

@智慧笨蛋: 確實能夠維度不一樣的說,主要仍是看顆粒度,光網絡這段從wifi 解密,到NAT,到局間交換,ip包在以太網包映射等等就能夠寫一本書了

/@喬3少:放開了說全部互聯網相關的知識都能體現的,好比dns、瀏覽器緩存,tcp鏈接、http響應,web服務的工做原理,瀏覽器的響應和渲染等等,剛剛在本子上列了下想到的安全威脅,頗有意思!

 

轉自:http://blogread.cn/it/article/6966

相關文章
相關標籤/搜索