歡迎光臨
每天分享高質量文章

小白科普:從輸入網址到最後瀏覽器呈現頁面內容,中間發生了什麼?

來自:碼農翻身(微訊號:coderising)

1前言

這篇文章是應網友之邀所寫,主要描述一下我們訪問網站時, 從輸入網址到最後瀏覽器呈現內容,中間發生了什麼。

之前寫過兩篇文章《我是一個網絡卡》,《我是一個路由器》描述了一個電腦如何透過DHCP、ARP、NAT等上式獲取IP、然後訪問網路的過程,主要專註在傳輸層和網路層

今天的文章主要專註於應用層,我拿了一個很簡單的網路結構來講。假定本機已經獲取了IP地址,各種網路基礎設施已經準備好了。

由於知識點太多,我肯定會漏掉部分內容,歡迎在留言中補充, 以後我會根據大家建議再寫文章擴充套件。

2準備

當你在瀏覽器中輸入網址(例如www.coder.com)並且敲了回車以後, 瀏覽器首先要做的事情就是獲得coder.com的IP地址,具體的做法就是傳送一個UDP的包給DNS伺服器,DNS伺服器會傳回coder.com的IP, 這時候瀏覽器通常會把IP地址給快取起來,這樣下次訪問就會加快。

比如Chrome, 你可以透過chrome://net-internals/#dns來檢視。

有了伺服器的IP, 瀏覽器就要可以發起HTTP請求了,但是HTTP Request/Response必須在TCP這個“虛擬的連線”上來傳送和接收。

想要建立“虛擬的”TCP連線,TCP郵差需要知道4個東西:(本機IP, 本機埠,伺服器IP, 伺服器埠),現在只知道了本機IP,伺服器IP, 兩個埠怎麼辦?

本機埠很簡單,作業系統可以給瀏覽器隨機分配一個, 伺服器埠更簡單,用的是一個“眾所周知”的埠,HTTP服務就是80, 我們直接告訴TCP郵差就行。

經過三次握手以後,客戶端和伺服器端的TCP連線就建立起來了! 終於可以傳送HTTP請求了。

之所以把TCP連線畫成虛線,是因為這個連線是虛擬的, 詳情可參見之前的文章《TCP/IP之大明郵差》,《張大胖的Socket

3Web伺服器

一個HTTP GET請求經過千山萬水,歷經多個路由器的轉發,終於到達伺服器端(HTTP資料包可能被下層進行分片傳輸,略去不表)。

Web伺服器需要著手處理了,它有三種方式來處理:

(1) 可以用一個執行緒來處理所有請求,同一時刻只能處理一個,這種結構易於實現,但是這樣會造成嚴重的效能問題。

(2) 可以為每個請求分配一個行程/執行緒,但是當連線太多的時候,伺服器端的行程/執行緒會耗費大量記憶體資源,行程/執行緒的切換也會讓CPU不堪重負。

(3) 復用I/O的方式,很多Web伺服器都採用了復用結構,例如透過epoll的方式監視所有的連線,當連線的狀態發生變化(如有資料可讀), 才用一個行程/執行緒對那個連線進行處理,處理完以後繼續監視,等待下次狀態變化。 用這種方式可以用少量的行程/執行緒應對成千上萬的連線請求。

(碼農翻身註:詳情參見《Http Server:一個差生的逆襲》)

我們使用Nginx這個非常流行的Web伺服器來繼續下麵的故事。

對於HTTP GET請求,Nginx利用epoll的方式給讀取了出來, Nginx接下來要判斷,這是個靜態的請求還是個動態的請求啊?

如果是靜態的請求(HTML檔案,JavaScript檔案,CSS檔案,圖片等),也許自己就能搞定了(當然依賴於Nginx配置,可能轉發到別的快取伺服器去),讀取本機硬碟上的相關檔案,直接傳回。

如果是動態的請求,需要後端伺服器(如Tomcat)處理以後才能傳回,那就需要向Tomcat轉發,如果後端的Tomcat還不止一個,那就需要按照某種策略選取一個。

例如Ngnix支援這麼幾種:

  • 輪詢:按照次序挨個向後端伺服器轉發

  • 權重:給每個後端伺服器指定一個權重,相當於向後端伺服器轉發的機率。

  • ip_hash: 根據ip做一個hash操作,然後找個伺服器轉發,這樣的話同一個客戶端ip總是會轉發到同一個後端伺服器。

  • fair:根據後端伺服器的響應時間來分配請求,響應時間段的優先分配。


不管用哪種演演算法,某個後端伺服器最終被選中,然後Nginx需要把HTTP Request轉發給後端的Tomcat,並且把Tomcat輸出的HttpResponse再轉發給瀏覽器。

由此可見,Nginx在這種場景下,是一個代理人的角色。

5應用伺服器

Http Request終於來到了Tomcat,這是一個由Java寫的、可以處理Servlet/JSP的容器,我們的程式碼就執行在這個容器之中。

如同Web伺服器一樣, Tomcat也可能為每個請求分配一個執行緒去處理,即通常所說的BIO樣式(Blocking I/O 樣式)。

也可能使用I/O多路復用技術,僅僅使用若干執行緒來處理所有請求,即NIO樣式。

不管用哪種方式,Http Request 都會被交給某個Servlet處理,這個Servlet又會把Http Request做轉換,變成框架所使用的引數格式,然後分發給某個Controller(如果你是在用Spring)或者Action(如果你是在Struts)。

剩下的故事就比較簡單了(不,對碼農來說,其實是最複雜的部分),就是執行碼農經常寫的增刪改查邏輯,在這個過程中很有可能和快取、資料庫等後端元件打交道,最終傳回HTTP Response,由於細節依賴業務邏輯,略去不表。

根據我們的例子,這個HTTP Response應該是一個HTML頁面。

6歸途

Tomcat很高興地把Http Response發給了Ngnix 。

Ngnix也很高興地把Http Response 發給了瀏覽器。


發完以後TCP連線能關閉嗎?

如果使用的是HTTP1.1, 這個連線預設是keep-alive,也就是說不能關閉;

如果是HTTP1.0,要看看之前的HTTP Request Header中有沒有Connetion:keep-alive,如果有,那也不能關閉。

7瀏覽器再次工作

瀏覽器收到了Http Response,從其中讀取了HTML頁面,開始準備顯示這個頁面。

但是這個HTML頁面中可能取用了大量其他資源,例如js檔案,CSS檔案,圖片等,這些資源也位於伺服器端,並且可能位於另外一個域名下麵,例如static.coder.com。

瀏覽器沒有辦法,只好一個個地下載,從使用DNS獲取IP開始,之前做過的事情還要再來一遍。不同之處在於不會再有應用伺服器如Tomcat的介入了。

如果需要下載的外部資源太多,瀏覽器會建立多個TCP連線,並行地去下載。

但是同一時間對同一域名下的請求數量也不能太多,要不然伺服器訪問量太大,受不了。所以瀏覽器要限制一下, 例如Chrome在Http1.1下只能並行地下載6個資源。

當伺服器給瀏覽器傳送JS,CSS這些檔案時,會告訴瀏覽器這些檔案什麼時候過期(使用Cache-Control或者Expire),瀏覽器可以把檔案快取到本地,當第二次請求同樣的檔案時,如果不過期,直接從本地取就可以了。

如果過期了,瀏覽器就可以詢問伺服器端,檔案有沒有修改過?(依據是上一次伺服器傳送的Last-Modified和ETag),如果沒有修改過(304 Not Modified),還可以使用快取。否則的話伺服器就會被最新的檔案發回到瀏覽器。

當然如果你按了Ctrl+F5,會強制地發出GET請求,完全無視快取。

註:在Chrome下,可以透過 chrome://view-http-cache/ 命令來檢視快取。

現在瀏覽器得到了三個重要的東西:

1.HTML ,瀏覽器把它變成DOM Tree

2. CSS,  瀏覽器把它變成CSS Rule Tree

3. JavaScript, 它可以修改DOM Tree

瀏覽器會透過DOM Tree和CSS Rule Tree生成所謂“Render Tree”,計算每個元素的位置/大小,進行佈局,然後呼叫作業系統的API進行繪製,這是一個非常複雜的過程,略去不表。

到目前為止,我們終於在瀏覽器中看到了www.coder.com的內容。

(完)


●本文編號329,以後想閱讀這篇文章直接輸入329即可

●輸入m獲取文章目錄

推薦↓↓↓

 

Web開發

更多推薦18個技術類微信公眾號

涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

贊(0)

分享創造快樂