當咱們在瀏覽器中輸入一個URL訪問地址,而後瀏覽器返回給咱們一個響應頁面,這內部過程究竟是怎樣的呢?下面我將從如下幾個方面闡述一個 WEB請求過程究竟是怎樣:html
這裏將瀏覽器機制放在第一步是考慮若是瀏覽器中有了緩存數據,瀏覽器再次向目標URL發送請求時,在數據不過時的狀況下,會直接使用瀏覽器緩存的數據,而不須要向服務端請求。下面的分析參考了完全理解瀏覽器的緩存機制的介紹,表示感謝。git
瀏覽器和服務器的通訊方式是應答方式,即 瀏覽器發起HTTP請求 - 服務器響應該請求 。瀏覽器第一次向服務器發起該請求後拿到請求結果,會根據響應報文中的HTTP頭的緩存標識,決定是否緩存結果,是則將請求結果和緩存標識存入瀏覽器緩存中,過程以下: github
咱們根據是否須要向服務器從新發起HTTP請求將緩存結果分爲兩個部分,分別是強制緩存(也叫本地緩存)和協商緩存,固然這只是兩種叫法,有些人把緩存分爲Expires/Cache-control和Last-Modifed/Etag這兩種表現,本質上是同樣的。至於瀏覽器的緩存究竟是放在哪裏,咱們後面揭曉web
強制緩存就是向瀏覽器緩存查找緩存結果和緩存標識,並根據該結果的緩存規則來決定是否使用該緩存結果的過程。強制緩存又分爲如下幾個方面:shell
當瀏覽器向服務器發送請求時,服務器會將緩存規則放入HTTP響應報文的HTTP頭中和請求結果一塊兒返回給瀏覽器,控制強制緩存的字段分別是Expires和Cache-Control,其中Cache-Control比Expires優先級高,兩個同時存在時,Cache-Control優先級高瀏覽器
Expires: Wed, 21 Aug 2018 08:26:05 GMT
複製代碼
Expires 是 HTTP/1.0控制網頁緩存的字段,其值爲服務器返回該請求結果緩存的到期時間,這裏是絕對日期,若是客戶端的時間小於 Expires 的值時,直接使用緩存結果緩存
Expires是HTTP/1.0的字段,如今瀏覽器默認使用HTTP/1.1,Expires已經被Cache-Control替代安全
在HTTP/1.1中,Cache-Control主要用於控制網頁緩存,其主要取值爲:bash
協商緩存就是強制緩存失效後,瀏覽器攜帶緩存標識向服務器發起請求,由服務器根據緩存標識決定是否繼續使用緩存的過程。主要分爲如下兩種狀況:服務器
第一種:協商緩存生效,返回304
注: 304狀態碼錶示客戶端發送附帶條件 (附帶條件包括If-Match,If-Modified-Since,If-None-Match等字段) 的請求時,服務器端容許請求訪問資源,但未知足條件的狀況。304狀態碼返回時,不包含任何響應的主體部分。
第二種:協商緩存失效,返回200和請求結果結果
協商緩存的控制字段有 Last-Modified /If-Modified-Since 和 Etag/If-None-Match。 Etag/If-None-Match的優先級比Last-Modified /If-Modified-Since 高
表示一個服務器上的資源的最後修改時間,資源能夠是靜態的或者動態的內容。通常在第一次請求時服務器發給客戶端的緩存標識
last-modified: Fri, 20 Apr 2018 10:10:50 GMT
複製代碼
客戶端再次發送該請求時,攜帶上次請求返回的Last-Modified值,服務器收到該請求,發現 If-Modified-Since字段,若是服務器資源的最後修改時間大於If-Modified-Since的值,則從新返回新資源,狀態碼爲200,此時則代表協商緩存失效,以前的緩存再也不使用。不然,返回304,資源沒有被更新,協商緩存生效,繼續使用瀏覽器緩存。
if-Modified-Since: Fri, 20 Apr 2018 10:10:50 GMT
複製代碼
Etag是服務器響應請求時,返回當前資源文件的惟一的編號
客戶端再次發起該請求時,攜帶上次請求返回的惟一編號,服務器收到請求後,將If-None-Match的字段值與該資源在服務器上的Etag值作對比,一致則返回304,說明資源無更新,協商緩存生效,繼續使用緩存文件,不一致說明資源已更新,從新返回資源文件,狀態碼爲200
瀏覽器緩存分爲內存緩存(from memory cache) 和硬盤緩存(from disk cache):
強制緩存優先於協商緩存執行,下面這張圖闡述了瀏覽器緩存的流程:
注意: 當咱們第一次訪問一個請求時,瀏覽器中是沒有緩存的,這時候就須要到服務器中去獲取,而與服務器鏈接並獲取資源的這個過程,涉及到下面將的 DNS域名解析,TCP鏈接,HTTP請求等內容。這裏把瀏覽器緩存機制放在第一位,是考慮若是客戶屢次訪問同一個請求,是先去檢驗瀏覽器的緩存的
咱們知道互聯網都是經過URL來發布和請求資源的,而URL中的域名須要解析成IP地址才能與遠程主機創建鏈接,如何將域名解析成IP地址就屬於DNS解析的工做範疇。
DNS(Domain Name System)是域名系統的英文縮寫,是一種組織成域層次結構的計算機和網絡服務命名系統,它用於TCP/IP網絡,它所提供的服務是將主機名和域名轉換爲IP地址的工做
根據域名服務器所起的做用,能夠把域名服務器分爲如下幾種類型:
根域名服務器是最高層次的域名服務器,也是最重要的域名服務器。全部的根域名服務器都知道全部的頂級域名服務器的域名和IP地址。
這些域名服務器負載管理在該頂級域名服務器註冊的全部二級域名。
例如:阿里萬網提供的dns9.hichina.com,dns10.hichina.com等,騰訊DNSPod提供的 f1g1ns1.dnspod.net,f1g1ns2.dnspod.net等,本身申請搭建的權威域名服務器等
實際上本地域名服務器不屬於域名服務器的層次結構,可是它對域名系統很是重要,當一臺主機發出DNS查詢請求時,這個查詢請求報文首先是發送給本地域名服務器。每一個ISP(當地網絡接入商)都有一個本地域名服務器
DNS域名解析的請求過程分爲如下幾步:
第一步: 檢查瀏覽器緩存中是否有這個域名對應的解析過的IP地址
瀏覽器會檢查緩存中有沒有這個域名對應的解析過的IP地址,若是緩存中有,這個解析過程就將結束。瀏覽器緩存域名也是有限制的,不只瀏覽器緩存大小有限制,並且緩存時間也有限制,一般狀況下爲幾分鐘到幾小時不等,域名被緩存的時間限制能夠經過TTL屬性來設置,時間要設置的合理。
第二步: 檢查操做系統緩存中是否有這個域名對應的解析結果
若是用戶的瀏覽器緩存沒有,瀏覽器會查找操做系統緩存中是否有這個域名對應的DNS解析結果。其實操做系統也會有一個域名解析的過程,在Windows中能夠經過
C:\Windows\System32\drivers\etc\hosts
文件來設置,在Linux中這個配置是/etc/hosts
。在這兩個文件中,你能夠將任何域名解析到任何可以訪問的IP地址
第三步: 向本地服務器查詢(LDNS)
若是前面兩個過程都沒法解析,操做系統會把這個域名發送給這裏設置的LDNS,也就是本地區的的域名服務器。這個DNS一般都提供給你本地互聯網接入的一個DNS解析服務。好比你在學校接入互聯網,那麼本地DNS服務器就在你的學校。實際上大約80%的域名服務解析都在這裏就完成了,LDNS承擔了主要的域名解析工做
第四步: LDNS沒有命中,直接到Root Server域名服務器請求解析
根域名服務器是最高層次的域名服務器,也是最重要的域名服務器。全部的根域名服務器都知道全部的頂級域名服務器的域名和IP地址
第五步:根域名服務器返回給本地域名服務器一個所查詢域的主域名服務器(gTLD Server)地址
gTLD就是前面說的國際頂級域名服務器,如.com,.cn等。
第六步: 本地域名服務器再向上一步返回的gTLD服務器發送請求
第七步:接受請求的頂級域名服務器查詢並返回此域名對應的權威服務器(Name Server)
這個權威服務器就是你註冊的域名服務器,好比阿里萬網的域名服務器,你這此申請的域名,那麼域名的解析任務就由這個域名提供商的服務器來完成。
第八步:權威服務器會查詢存儲的域名和IP的映射關係表並返回
正常狀況下都根據域名獲得目標IP記錄,連同一個TTL值返回給DNS Server域名服務器。對於返回的IP和TTL,本地域名服務器會緩存這個域名和IP的對應關係,緩存的時間由TTL值控制。
第九步:把解析的結果返回給用戶,用戶根據TTL值緩存在本地系統緩存中,域名解析過程結束
主機向本地域名服務器的查詢通常都是採用遞歸查詢,所爲遞歸查詢就是:若是主機所訪問的本地域名服務器不知道被查詢域名的IP地址,那麼本地域名就以DNS客戶的身份,替代主機,向其餘根域名服務器繼續發出查詢請求報文,而不是讓主機本身進行下一步的查詢。
本地域名服務器向根域名服務器的查詢一般是採用迭代查詢。迭代查詢就是:當根域名服務器收到本地域名服務器發出的迭代查詢請求報文時,要麼給出所要查詢的IP地址,要麼告訴本地域名服務器下一步應當向哪個域名服務器查詢,而後讓本地域名服務器進行後續的查詢。
DNS域名解析過程後會緩存解析結果,其中主要在兩個地方緩存結果,一個是本地域名服務器,一個是用戶的本地機器,這兩個緩存都是TTL值和本機緩存大小控制的。
都說 「一圖勝千言」,下面這張圖具體闡述了DNS域名解析過程
在瀏覽器發送HTTP請求以前,須要在瀏覽器和服務器之間創建一條TCP/IP鏈接。每一條TCP鏈接惟一地被通訊兩端的兩個端點(即兩個套接字)所肯定,TCP鏈接的端點叫作套接字(socket)。根據RFC 793的定義,端口號拼接到IP地址即構成了套接字。而一次TCP鏈接主要分爲創建鏈接(三次握手),數據傳輸,斷開鏈接(四次揮手),下圖給出了TCP的通訊過程
接下來的分析參考結合了《TCP/IP 詳解 卷1》和《計算機網絡-謝希仁版》關於TCP的分析
在具體講TCP的三次握手和四次揮手以前,咱們先來重點看下與TCP鏈接有關的報文段首部的幾個控制位與初始序列號,它將有助於咱們後續的分析,至於有關TCP報文段首部更詳細的介紹請參閱《TCP/IP 詳解 卷1》或者相關文章。
確認號
確認號是指望收到對方下一個報文段的第一個數據字節的序號。例如,B正確收到A發送過來的一個報文段,其序號字段值爲501,而數據長度是200字節(序號501 ~ 700),這代表B正確收到了A發送的到序號爲 700 爲止的數據。因此,B指望收到A的下一個數據序號是 701 ,因而B將確認號置爲 701,表示爲 ACK = 701。總之,**若確認號 = N,則代表 N - 1 爲止的全部數據都已正確收到
確認ACK
ACK標誌位用於確認收到
同步SYN
用來初始化一個鏈接的同步序列號,當創建一個新鏈接時,從客戶機發送到服務器的第一個報文段的SYN位字段被啓用。
終止FIN
用來釋放一個鏈接,當FIN = 1時,代表此報文段的發送方的數據已發送完畢,並要求釋放運輸鏈接。
序列號
TCP是面向字節流的,在一個TCP鏈接中傳送的字節流中的每個字節都按順序編號,整個要傳送的字節流的起始序號必須在鏈接創建時設置,這裏的起始序號就是TCP三次握手過程當中的初始序列號(Initial Sequence Number,ISN)。在《TCP/IP詳解 卷1:協議》中談到,在一個鏈接中,TCP報文段在通過網絡路後可能會存在延遲抵達與排序混亂的狀況,爲了解決這一問題,須要仔細選擇初始序列號,現代系統一般採用半隨機的方法選擇初始序列號,保證必定的安全性
這裏強調一點,確認號是針對接收方而言,接收方但願收到發送方下一個報文段的第一個數據字節的序號,而SYN所初始化的序列號是針對發送方,在創建鏈接階段,發送方和接收方都要發送本身的初始序列號。畢竟TCP協議能夠實現客戶端和服務端全雙工通訊。
第一次握手
客戶端在打算創建TCP鏈接時,向服務端發出 鏈接請求報文段(也就是SYN報文段,其SYN標誌被置位) 設置首部中的同步位 SYN = 1 ,同時隨機選擇一個初始序號 seq = x。TCP規定,SYN 報文段(即 SYN = 1 的報文段) 不能攜帶數據,但要消耗掉一個序號,這時,TCP客戶進程進入 SYN-SENT (同步已發送) 狀態
第二次握手
服務器收到鏈接請求報文段後,如贊成創建鏈接,則向客戶端發送確認,在確認報文段(SYN+ACK段)中應把 SYN 位和 ACK 位都置1,確認號是 ack = x + 1,表示服務端但願從客戶端這邊接收數據的序列號。同時也爲本身隨機選擇一個初始序號 seq = y。請注意,這個報文段也不能攜帶數據,但一樣要消耗掉一個序號。這時 TCP 服務器進程進入 SYN-RCVD(同步收到)狀態
第三次握手
客戶端收到服務器發送的確認後,還要再次向服務端給出確認。確認報文段的ACK置1,確認號 ack = y + 1.而本身的序號 seq = x + 1,TCP的標準規定,ACK報文段能夠攜帶數據,但若是不攜帶數據則不消息序號,在這種狀況下,下一個數據報文段的序號還是seq = x + 1。這是TCP鏈接已經創建,客戶端進入ESTABLISHED(已創建鏈接)狀態,服務端收到確認後,也進入 ESTABLISHED 狀態。
TCP鏈接爲何是三次,而不是二次或者四次
知乎上對於這個問題點贊最多,並且比較通俗易懂的說法以下:
三次握手:
A:「喂,你聽獲得嗎?」
B:「我聽獲得呀,你聽獲得我嗎?」
A:「我能聽到你,今天balabala……」
兩次握手:
A:「喂,你聽獲得嗎?」
B:「我聽獲得呀」
A:「喂喂,你聽獲得嗎?」
B:「草,我聽獲得呀!!!!」
A:「你TM能不能聽到我講話啊!!喂!」
B:「……」
四次握手:
A:「喂,你聽獲得嗎?」
B:「我聽獲得呀,你聽獲得我嗎?」
A:「我能聽到你,你能聽到我嗎?」
B:「……不想跟傻逼說話」
複製代碼
三次握手的目的是不只在於通訊雙方瞭解一個鏈接正在創建,還在於利用數據包的選項來承載特殊的信息,確保通訊雙方信道是可靠的,須要三次握手,更多關於此問題的討論參閱知乎連接: www.zhihu.com/question/24…
第一次揮手
客戶端向服務端發出鏈接釋放報文段(即FIN標誌置爲1的報文段),並中止再發送數據,客戶端把鏈接釋放的報文段首部的終止控制位FIN置1,其序號爲 seq = u ,它等於前面已傳送過的數據的最後一個字節的序號加1.這時客戶端進入FIN-WAIT-1 (終止等待 1)狀態,等待 B 的確認,請注意,TCP 規定,FIN 報文段即便不攜帶數據,它也消耗掉一個序號。
第二次揮手
服務器端收到鏈接釋放報文段後即發出確認,確認號是 ack = u + 1,而這個報文段本身的序號是 v ,等於服務端前面已傳送過的數據的最後一個字節的序號加1.而後服務端進入CLOSE-WAIT(關閉等待)狀態。這時的TCP鏈接處於 半關閉狀態,即客戶端已經沒有數據要發送了,但服務端若發送數據,客戶端仍要接收。服務端到客戶端這個方向的鏈接並無關閉,這個狀態可能要持續一段時間。
客戶端收到來自服務端的確認後,就進入 FIN-WAIT-2(終止等待 2) 狀態,等待 服務端發出的鏈接釋放報文段
第三次揮手
若是服務端已經沒有向客戶端發送的數據了,其應用進程就通知服務端釋放鏈接。這時服務端發出的鏈接釋放報文段必須使 FIN = 1。現假定服務端的序號爲 w (在半關閉狀態 B 可能又發送了一些數據)。服務端還必須重複上次已經發送過的確認號 ack = u + 1。這時 B 就進入 LAST-ACK (最後確認) 狀態,等待 A 的確認。
第四次揮手
客戶端收到服務端的鏈接釋放報文後,必須對此發出確認。在確認報文段中把 ACK 置爲 1,確認號 ack = w + 1。而本身的序號是 seq = u + 1 (根據TCP標準,前面已經發送過的 FIN 報文段要消耗一個序號)。而後進入TIME-WAIT(時間等待)狀態。這時,TCP鏈接尚未釋放掉,等待時間計數器設置的時間 2MSL後,客戶端才進入到CLOSED狀態
爲何是四次揮手
A: hi,我要關閉鏈接了
B: 好的,我收到了,我不接受你的數據了
B: hi,我也想關閉鏈接了
A: 好的,我不接收你的數據了。
複製代碼
客戶端先向服務端發送關閉請求,表示客戶端再也不發送數據了,服務端確認,可是,此時服務端還能夠接着向客戶端發送消息,待服務端沒有數據傳送時,此時服務端也向客戶端發送關閉請求,而後客戶端確認
TCP的半關閉
就像上面所說,TCP存在半關閉的狀態,雖然一些應用須要此功能,但它並不常見。TCP的半關閉就是指當一方已經完成了數據的發送工做,併發送了一個FIN給對方,可是它仍然但願接收來自對方的數據,直到對方發送一個FIN給它,這種狀況下它就處於TCP的半關閉狀態。
爲何客戶端要在TIME-WAIT狀態必須等待2MSL的狀態
TIME_WAIT狀態也稱爲 2MSL 等待狀態。在該狀態下,TCP 將會等待兩倍於最大段生存期(MSL)的時間,有時也被稱做加倍等待。這樣就可以讓TCP從新發送最終的ACK 以免出現丟失的狀況,從新發送最終的 ACK 並非由於TCP重傳了 ACK (它們不消耗序列號,也不會被TCP重傳),而是由於通訊另外一方重傳了它的 FIN (它消耗一個序列號)
下面利用wireshell 簡要抓包分析,這裏只是簡單給出三次握手和四次揮手的過程,更多關於抓包的知識請參閱相關文章。以 IP地址 192.168.43.187
和 54.190.14.101
爲例分析。
三次握手過程
四次揮手過程
瀏覽器與服務器創建TCP鏈接之後,而後就能夠開始發起HTTP請求了,客戶端按照指定格式開始向服務器發送HTTP請求,服務端接收請求後,解析HTTP請求,處理完業務邏輯,返回一個HTTP響應給客戶端,以下圖所示:
HTTP的請求報文與響應報文結構同樣,都是分爲報文首部和報文主體部分,請求報文中的主體部分主要發送給服務端的數據,而響應報文中的主體則是客戶端須要的響應數據
而請求報文和響應報文的首部內容又由如下內容組成:
PS:更多關於HTTP首部字段的內容請參閱 《圖解HTTP》
HTTP中用的比較多的請求方法主要包括:GET , POST,PUT,DELETE這四種
如下列舉一些常見的狀態碼:
上面闡述了瀏覽器中的URL請求過程,從 瀏覽器緩存——>DNS域名解析——>TCP鏈接——>HTTP請求與響應 這4個階段進行了分析,一個web請求大體就分爲這麼幾個過程,固然其中確定有不少細節問題沒注意到,有問題歡迎指出。