整理下最近面試出現的頻率比較高的一個問題,也就是:在瀏覽器中輸入URL到整個頁面顯示在用戶面前時這個過程當中到底發生了什麼? 老生常談的問題,可是,這個問題確實涉及很是多的東西,因而,再從新整理一遍:php
URL中文名叫作統一資源定位符,統一資源定位符是對能夠從互聯網上獲得的資源的位置和訪問方法的一種簡潔的表示,是互聯網上標準資源的地址。互聯網上的每一個文件都有一個惟一的URL,它包含的信息指出文件的位置以及瀏覽器應該怎麼處理它。html
主要組成部分:protocol :// hostname[:port] / path / [;parameters][?query]#fragment前端
當咱們開始在瀏覽器中輸入網址的時候,瀏覽器其實就已經在智能的匹配可能得 url 了,他會從歷史記錄,書籤等地方,找到已經輸入的字符串可能對應的 url,而後給出智能提示,讓你能夠補全url地址。對於 google的chrome 的瀏覽器,他甚至會直接從緩存中把網頁展現出來,就是說,你尚未按下 enter,頁面就出來了。nginx
DNS解析的過程就是尋找哪臺機器上有你須要資源的過程。當你在瀏覽器中輸入一個地址時,例如www.baidu.com,其實不是百度網站真正意義上的地址。互聯網上每一臺計算機的惟一標識是它的IP地址,可是IP地址並不方便記憶。用戶更喜歡用方便記憶的網址去尋找互聯網上的其它計算機,也就是上面提到的百度的網址。因此互聯網設計者須要在用戶的方便性與可用性方面作一個權衡,這個權衡就是一個網址到IP地址的轉換,這個過程就是DNS解析。它實際上充當了一個翻譯的角色,實現了網址到IP地址的轉換。網址到IP地址轉換的過程是如何進行的?git
查找順序: 瀏覽器緩存--> 操做系統緩存--> 本地host文件 --> 路由器緩存--> ISP DNS緩存 --> 頂級DNS服務器/根DNS服務器github
一、瀏覽器緩存 :首先會向瀏覽器的緩存中讀取上一次訪問的記錄,在chrome能夠經過地址欄中輸入chrome://net-internals/#dns查看緩存的當前狀態 。面試
二、操做系統緩存: 查找存儲在系統運行內存中的緩存。在mac中能夠經過下面的命令清除系統中的DNS緩存。chrome
dscacheutil -flushcache
複製代碼
三、 **hosts 文件:**查看本地硬盤的 hosts 文件,看看其中有沒有和這個域名對應的規則,若是有的話就直接使用 hosts 文件裏面的 ip 地址。數據庫
四、路由器緩存: 有些路由器也有DNS緩存的功能,訪問過的域名會存在路由器上。segmentfault
五、ISP DNS緩存:互聯網服務提供商(如中國電信)也會提供DNS服務,好比比較著名的 114.114.114.114,在本地查找不到的狀況下,就會向ISP進行查詢,ISP會在當前服務器的緩存內查找是否有記錄,若是有,則返回這個IP,若沒有,則會開始向根域名服務器請求查詢。
六、頂級DNS服務器/根DNS服務器:根域名收到請求後,會判別這個域名(.com)是受權給哪臺服務器管理,並返回這個頂級DNS服務器的IP。請求者收到這臺頂級DNS的服務器IP後,會向該服務器發起查詢,若是該服務器沒法解析,該服務器就會返回下一級的DNS服務器IP(nicefilm.com),本機繼續查找,直到服務器找到(www.nicefilm.com)的主機。
六、最後,本地DNS服務器向域名的解析服務器發出請求,這時就能收到一個域名和IP地址對應關係,本地DNS服務器不只要把IP地址返回給用戶電腦,還要把這個對應關係保存在緩存中,以備下次別的用戶查詢時,能夠直接返回結果,加快網絡訪問。
下面這張圖很完美的解釋了這一過程:
上述圖片是查找www.google.com的IP地址過程。首先在本地域名服務器中查詢IP地址,若是沒有找到的狀況下,本地域名服務器會向根域名服務器發送一個請求,若是根域名服務器也不存在該域名時,本地域名會向com頂級域名服務器發送一個請求,依次類推下去。直到最後本地域名服務器獲得google的IP地址並把它緩存到本地,供下次查詢使用。從上述過程當中,能夠看出網址的解析是一個從右向左的過程: com -> google.com -> www.google.com。可是你是否發現少了點什麼,根域名服務器的解析過程呢?事實上,真正的網址是www.google.com.,並非我多打了一個.,這個.對應的就是根域名服務器,默認狀況下全部的網址的最後一位都是.,既然是默認狀況下,爲了方便用戶,一般都會省略,瀏覽器在請求DNS的時候會自動加上,全部網址真正的解析過程爲: . -> .com -> google.com. -> www.google.com.。
補充:
DNS(Domain Name System,域名系統),因特網上做爲域名和IP地址相互映射的一個分佈式數據庫,可以使用戶更方便的訪問互聯網,而不用去記住可以被機器直接讀取的IP數串。經過主機名,最終獲得該主機名對應的IP地址的過程叫作域名解析(或主機名解析)。
通俗的講,咱們更習慣於記住一個網站的名字,好比www.baidu.com,而不是記住它的ip地址,好比:167.23.10.2。而計算機更擅長記住網站的ip地址,而不是像www.baidu.com等連接。由於,DNS就至關於一個電話本,好比你要找www.baidu.com這個域名,那我翻一翻個人電話本,我就知道,哦,它的電話(ip)是167.23.10.2。
2.一、遞歸解析
當局部DNS服務器本身不能回答客戶機的DNS查詢時,它就須要向其餘DNS服務器進行查詢。此時有兩種方式,如圖所示的是遞歸方式。局部DNS服務器本身負責向其餘DNS服務器進行查詢,通常是先向該域名的根域服務器查詢,再由根域名服務器一級級向下查詢。最後獲得的查詢結果返回給局部DNS服務器,再由局部DNS服務器返回給客戶端。
2.二、迭代解析
當局部DNS服務器本身不能回答客戶機的DNS查詢時,也能夠經過迭代查詢的方式進行解析,如圖所示。局部DNS服務器不是本身向其餘DNS服務器進行查詢,而是把能解析該域名的其餘DNS服務器的IP地址返回給客戶端DNS程序,客戶端DNS程序再繼續向這些DNS服務器進行查詢,直到獲得查詢結果爲止。也就是說,迭代解析只是幫你找到相關的服務器而已,而不會幫你去查。好比說:baidu.com的服務器ip地址在192.168.4.5這裏,你本身去查吧,本人比較忙,只能幫你到這裏了。
咱們在前面有說到根DNS服務器,域DNS服務器,這些都是DNS域名稱空間的組織方式。按其功能命名空間中用來描述 DNS 域名稱的五個類別的介紹詳見下表中,以及與每一個名稱類型的示例
瞭解了DNS的過程,能夠爲咱們帶來哪些?上文中請求到google的IP地址時,經歷了8個步驟,這個過程當中存在多個請求(同時存在UDP和TCP請求,爲何有兩種請求方式,請自行查找)。若是每次都通過這麼多步驟,是否太耗時間?如何減小該過程的步驟呢?那就是DNS緩存。
4.1 DNS緩存
DNS存在着多級緩存,從離瀏覽器的距離排序的話,有如下幾種: 瀏覽器緩存,系統緩存,路由器緩存,IPS服務器緩存,根域名服務器緩存,頂級域名服務器緩存,主域名服務器緩存。
4.2 DNS負載均衡
當一個網站有足夠多的用戶的時候,假如每次請求的資源都位於同一臺機器上面,那麼這臺機器隨時可能會蹦掉。處理辦法就是用DNS負載均衡技術,它的原理是在DNS服務器中爲同一個主機名配置多個IP地址,在應答DNS查詢時,DNS服務器對每一個查詢將以DNS文件中主機記錄的IP地址按順序返回不一樣的解析結果,將客戶端的訪問引導到不一樣的機器上去,使得不一樣的客戶端訪問不一樣的服務器,從而達到負載均衡的目的。例如能夠根據每臺機器的負載量,該機器離用戶地理位置的距離等等。
拿到域名對應的IP地址以後,瀏覽器會以一個隨機端口(1024<端口<65535)向服務器的WEB程序(經常使用的有httpd,nginx等)80端口發起TCP的鏈接請求。這個鏈接請求到達服務器端後(這中間經過各類路由設備,局域網內除外),進入到網卡,而後是進入到內核的TCP/IP協議棧(用於識別該鏈接請求,解封包,一層一層的剝開),還有可能要通過Netfilter防火牆(屬於內核的模塊)的過濾,最終到達WEB程序,最終創建了TCP/IP的鏈接。
TCP鏈接如圖所示:
第一次握手:創建鏈接時,客戶端發送syn包(syn=j)到服務器,並進入SYN_SENT狀態,等待服務器確認;SYN:同步序列編號(Synchronize Sequence Numbers)。
第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時本身也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED(TCP鏈接成功)狀態,完成三次握手。
補充:
爲什須要三次握手?
《計算機網絡》第四版中講「三次握手」的目的是「爲了防止已失效的鏈接請求報文段忽然又傳送到了服務端,於是產生錯誤」,書中的例子是這樣的,「已失效的鏈接請求報文段」的產生在這樣一種狀況下:client發出的第一個鏈接請求報文段並無丟失,而是在某個網絡結點長時間的滯留了,以至延誤到鏈接釋放之後的某個時間纔到達server。原本這是一個早已失效的報文段。但server收到此失效的鏈接請求報文段後,就誤認爲是client再次發出的一個新的鏈接請求。因而就向client發出確認報文段,贊成創建鏈接。假設不採用「三次握手」,那麼只要server發出確認,新的鏈接就創建了。因爲如今client並無發出創建鏈接的請求,所以不會理睬server的確認,也不會向server發送數據。但server卻覺得新的運輸鏈接已經創建,並一直等待client發來數據。這樣,server的不少資源就白白浪費掉了。採用「三次握手」的辦法能夠防止上述現象發生。例如剛纔那種狀況,client不會向server的確認發出確認。server因爲收不到確認,就知道client並無要求創建鏈接。」。主要目的防止server端一直等待,浪費資源。
創建了TCP鏈接以後,發起一個http請求。一個典型的 http request header 通常須要包括請求的方法,例如 GET 或者 POST 等,不經常使用的還有 PUT 和 DELETE 、HEAD、OPTION以及 TRACE 方法,通常的瀏覽器只能發起 GET 或者 POST 請求。
客戶端向服務器發起http請求的時候,會有一些請求信息,請求信息包含三個部分:
下面是一個完整的HTTP請求例子:
GET/sample.jspHTTP/1.1
Accept:image/gif.image/jpeg,*/*
Accept-Language:zh-cn
Connection:Keep-Alive
Host:localhost
User-Agent:Mozila/4.0(compatible;MSIE5.01;Window NT5.0)
Accept-Encoding:gzip,deflate
username=jinqiao&password=1234
複製代碼
注意:最後一個請求頭以後是一個空行,發送回車符和換行符,通知服務器如下再也不有請求頭。
(1)請求的第一行是「方法URL議/版本」:GET/sample.jsp HTTP/1.1
(2)請求頭(Request Header) 請求頭包含許多有關的客戶端環境和請求正文的有用信息。例如,請求頭能夠聲明瀏覽器所用的語言,請求正文的長度等。
Accept:image/gif.image/jpeg.*/*
Accept-Language:zh-cn
Connection:Keep-Alive
Host:localhost
User-Agent:Mozila/4.0(compatible:MSIE5.01:Windows NT5.0)
Accept-Encoding:gzip,deflate.
複製代碼
(3)請求正文 請求頭和請求正文之間是一個空行,這個行很是重要,它表示請求頭已經結束,接下來的是請求正文。請求正文中能夠包含客戶提交的查詢字符串信息:
username=jinqiao&password=1234
複製代碼
服務器給瀏覽器響應一個301永久重定向響應,這樣瀏覽器就會訪問http://www.google.com/
而非http://google.com/
。
爲何服務器必定要重定向而不是直接發送用戶想看的網頁內容呢?其中一個緣由跟搜索引擎排名有關。若是一個頁面有兩個地址,就像http://www.yy.com/
和http://yy.com/
,搜索引擎會認爲它們是兩個網站,結果形成每一個搜索連接都減小從而下降排名。而搜索引擎知道301永久重定向是什麼意思,這樣就會把訪問帶www的和不帶www的地址歸到同一個網站排名下。還有就是用不一樣的地址會形成緩存友好性變差,當一個頁面有好幾個名字時,它可能會在緩存裏出現好幾回。
補充:
一、301和302的區別:
301和302狀態碼都表示重定向,就是說瀏覽器在拿到服務器返回的這個狀態碼後會自動跳轉到一個新的URL地址,這個地址能夠從響應的Location首部中獲取(用戶看到的效果就是他輸入的地址A瞬間變成了另外一個地址B)——這是它們的共同點。 他們的不一樣在於。301表示舊地址A的資源已經被永久地移除了(這個資源不可訪問了),搜索引擎在抓取新內容的同時也將舊的網址交換爲重定向以後的網址; 302表示舊地址A的資源還在(仍然能夠訪問),這個重定向只是臨時地從舊地址A跳轉到地址B,搜索引擎會抓取新的內容而保存舊的網址。 SEO302好於301
二、重定向緣由:
(1)網站調整(如改變網頁目錄結構); (2)網頁被移到一個新地址; (3)網頁擴展名改變(如應用須要把.php改爲.Html或.shtml)。 這種狀況下,若是不作重定向,則用戶收藏夾或搜索引擎數據庫中舊地址只能讓訪問客戶獲得一個404頁面錯誤信息,訪問流量白白喪失;再者某些註冊了多個域名的網站,也須要經過重定向讓訪問這些域名的用戶自動跳轉到主站點等。
三、何時進行301或者302跳轉呢?
當一個網站或者網頁24—48小時內臨時移動到一個新的位置,這時候就要進行302跳轉,而使用301跳轉的場景就是以前的網站由於某種緣由須要移除掉,而後要到新的地址訪問,是永久性的。 清晰明確而言:使用301跳轉的大概場景以下:
通過前面的重重步驟,咱們終於將咱們的http請求發送到了服務器這裏,其實前面的重定向已是到達服務器了,那麼,服務器是如何處理咱們的請求的呢?
後端從在固定的端口接收到TCP報文開始,它會對TCP鏈接進行處理,對HTTP協議進行解析,並按照報文格式進一步封裝成HTTP Request對象,供上層使用。
一些大一點的網站會將你的請求到反向代理服務器中,由於當網站訪問量很是大,網站愈來愈慢,一臺服務器已經不夠用了。因而將同一個應用部署在多臺服務器上,將大量用戶的請求分配給多臺機器處理。此時,客戶端不是直接經過HTTP協議訪問某網站應用服務器,而是先請求到Nginx,Nginx再請求應用服務器,而後將結果返回給客戶端,這裏Nginx的做用是反向代理服務器。同時也帶來了一個好處,其中一臺服務器萬一掛了,只要還有其餘服務器正常運行,就不會影響用戶使用。
如圖所示:
補充:
一、什麼是反向代理?
客戶端原本能夠直接經過HTTP協議訪問某網站應用服務器,網站管理員能夠在中間加上一個Nginx,客戶端請求Nginx,Nginx請求應用服務器,而後將結果返回給客戶端,此時Nginx就是反向代理服務器。
通過前面的6個步驟,服務器收到了咱們的請求,也處理咱們的請求,到這一步,它會把它的處理結果返回,也就是返回一個HTPP響應。 HTTP響應與HTTP請求類似,HTTP響應也由3個部分構成,分別是:
HTTP/1.1 200 OK
Date: Sat, 31 Dec 2005 23:59:59 GMT
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 122
<html>
<head>
<title>http</title>
</head>
<body>
<!-- body goes here -->
</body>
</html>
複製代碼
狀態行: 狀態行由協議版本
、數字形式的狀態代碼
、及相應的狀態描述
,各元素之間以空格分隔。 格式: HTTP-Version Status-Code Reason-Phrase CRLF
例如: HTTP/1.1 200 OK \r\n
| -協議版本:是用http1.0仍是其餘版本 | -狀態描述:狀態描述給出了關於狀態代碼的簡短的文字描述。好比狀態代碼爲200時的描述爲 ok | -態代碼:狀態代碼由三位數字組成,第一個數字定義了響應的類別,且有五種可能取值。以下
1xx:
信息性狀態碼,表示服務器已接收了客戶端請求,客戶端可繼續發送請求。
2xx:
成功狀態碼,表示服務器已成功接收到請求並進行處理。
3xx:
重定向狀態碼,表示服務器要求客戶端重定向。
4xx:
客戶端錯誤狀態碼,表示客戶端的請求有非法內容。
5xx
:服務器錯誤狀態碼,表示服務器未能正常處理客戶端的請求而出現意外錯誤。
響應頭:
響應頭部:由關鍵字/值對組成,每行一對,關鍵字和值用英文冒號」:」分隔,典型的響應頭有:
響應正文 :
包含着咱們須要的一些具體信息,好比cookie,html,image,後端返回的請求數據等等。這裏須要注意,響應正文和響應頭之間有一行空格,表示響應頭的信息到空格爲止,下圖是fiddler抓到的請求正文,紅色框中的:響應正文:
瀏覽器在收到HTML,CSS,JS文件後,它是如何把頁面呈現到屏幕上的?下圖對應的就是WebKit渲染的過程。
構建dom樹 -> 構建render樹 -> 佈局render樹 -> 繪製render樹
渲染過程:
瀏覽器是一個邊解析邊渲染的過程。首先瀏覽器解析HTML文件構建DOM樹,而後解析CSS文件構建渲染樹,等到渲染樹構建完成後,瀏覽器開始佈局渲染樹並將其繪製到屏幕上。這個過程比較複雜,涉及到兩個概念: reflow(迴流)和repain(重繪)。DOM節點中的各個元素都是以盒模型的形式存在,這些都須要瀏覽器去計算其位置和大小等,這個過程稱爲relow;當盒模型的位置,大小以及其餘屬性,如顏色,字體,等肯定下來以後,瀏覽器便開始繪製內容,這個過程稱爲repain。頁面在首次加載時必然會經歷reflow和repain。reflow和repain過程是很是消耗性能的,尤爲是在移動設備上,它會破壞用戶體驗,有時會形成頁面卡頓。因此咱們應該儘量少的減小reflow和repain。
當文檔加載過程當中遇到js文件,html文檔會掛起渲染(加載解析渲染同步)的線程,不只要等待文檔中js文件加載完畢,還要等待解析執行完畢,才能夠恢復html文檔的渲染線程。由於JS有可能會修改DOM,最爲經典的document.write,這意味着,在JS執行完成前,後續全部資源的下載多是沒有必要的,這是js阻塞後續資源下載的根本緣由。因此我明平時的代碼中,js是放在html文檔末尾的。
JS的解析是由瀏覽器中的JS解析引擎完成的。JS是單線程運行,也就是說,在同一個時間內只能作一件事,全部的任務都須要排隊,前一個任務結束,後一個任務才能開始。可是又存在某些任務比較耗時,如IO讀寫等,因此須要一種機制能夠先執行排在後面的任務,這就是:同步任務(synchronous)和異步任務(asynchronous)。
JS的執行機制就能夠看作是一個主線程加上一個任務隊列(task queue)。同步任務就是放在主線程上執行的任務,異步任務是放在任務隊列中的任務。全部的同步任務在主線程上執行,造成一個執行棧;異步任務有了運行結果就會在任務隊列中放置一個事件;腳本運行時先依次運行執行棧,而後會從任務隊列裏提取事件,運行任務隊列中的任務,這個過程是不斷重複的,因此又叫作事件循環(Event loop)。
如今的頁面爲了優化請求的耗時,默認都會開啓持久鏈接(keep-alive),那麼一個TCP鏈接確切關閉的時機,是這個tab標籤頁關閉的時候。這個關閉的過程就是著名的四次揮手。關閉是一個全雙工的過程,發包的順序的不必定的。通常來講是客戶端主動發起的關閉,過程以下。
對於一個已經創建的鏈接,TCP使用改進的三次握手來釋放鏈接(使用一個帶有FIN附加標記的報文段)。TCP關閉鏈接的步驟以下:
第一步,當主機A的應用程序通知TCP數據已經發送完畢時,TCP向主機B發送一個帶有FIN附加標記的報文段(FIN表示英文finish)。
第二步,主機B收到這個FIN報文段以後,並不當即用FIN報文段回覆主機A,而是先向主機A發送一個確認序號ACK,同時通知本身相應的應用程序:對方要求關閉鏈接(先發送ACK的目的是爲了防止在這段時間內,對方重傳FIN報文段)。
第三步,主機B的應用程序告訴TCP:我要完全的關閉鏈接,TCP向主機A送一個FIN報文段。
第四步,主機A收到這個FIN報文段後,向主機B發送一個ACK表示鏈接完全釋放。
補充:
爲何鏈接的時候是三次握手,關閉的時候倒是四次握手?
答:由於當Server端收到Client端的SYN鏈接請求報文後,能夠直接發送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。可是關閉鏈接時,當Server端收到FIN報文時,極可能並不會當即關閉SOCKET,因此只能先回復一個ACK報文,告訴Client端,"你發的FIN報文我收到了"。只有等到我Server端全部的報文都發送完了,我才能發送FIN報文,所以不能一塊兒發送。故須要四步握手。
至此,整篇文章暫時到這裏,文章內容大部分爲參考網上信息,還有不少細節點須要去整理概括,若有不足,但願多多指出!
參考文獻: