網絡協議原理解析


TCP 和 UDP 的區別

概括:TCP是一個面向連接的、可靠的、基於字節流的傳輸層協議。

  • TCP是安全可靠的傳輸協議,提現出來一個是有狀態,另一個是可控制。TCP 會精准記錄哪些數據發送了,哪些數據被對方接收了,哪些沒有被接收到,而且保證數據包按序到達,不允許半點差錯。這是有狀態。當意識到丟包了或者網絡環境不佳,TCP 會根據具體情況調整自己的行為,控制自己的發送速度或者重發。這是可控制
  • 面向連接。所謂的連接,指的是客戶端和服務器的連接,在雙方互相通信之前,TCP 需要三次握手建立連接,而 UDP 沒有相應建立連接的過程
  • 面向字節流。UDP 的數據傳輸是基於數據報的,這是因為僅僅只是繼承了 IP 層的特性,而 TCP 為了維護狀態,將一個個 IP 包變成了字節流
  • UDP 不止支持一對一的傳輸方式,同樣支持一對多,多對多,多對一的方式,也就是說 UDP 提供了單播,多播,廣播的功能。

TCP

什么是三次握手?

三次握手其實指的是三次信息交換過程,確認通信是否具備通信的能力(發送消息和接收消息),而且三次信息交換都需要加上編號

從最開始雙方都處於++CLOSED++狀態。然后服務端開始監聽某個端口,進入了++LISTEN++狀態。
然后客戶端主動發起連接,發送 ++SYN++ , 自己變成了++SYN-SENT++狀態。服務端接收到,返回++SYN++和++ACK++(對應客戶端發來的SYN),自己變成了++SYN-REVD++。

之后客戶端再發送++ACK++給服務端,自己變成了++ESTABLISHED++狀態;服務端收到++ACK++之后,也變成了++ESTABLISHED++狀態。

從圖中可以看出: ++SYN++ 是需要消耗一個序列號的,下次發送對應的 ++ACK++ 序列號要加1

為什么需要建立三次握手?

如果我們假設一種場景,如果是建立二次握手,客戶端在接受了 ++ACK+SYN++,建立好了連接。然后客戶端接着斷開了,這就帶來了連接資源的浪費

三次握手過程中可以攜帶數據么?

第三次握手的時候,可以攜帶。前兩次握手不能攜帶數據。

如果前兩次握手能夠攜帶數據,那么一旦有人想攻擊服務器,那么他只需要在第一次握手中的 SYN 報文中放大量數據,那么服務器勢必會消耗更多的時間和內存空間去處理這些數據,增大了服務器被攻擊的風險。

第三次握手的時候,客戶端已經處於++ESTABLISHED++狀態,並且已經能夠確認服務器的接收、發送能力正常,這個時候相對安全了,可以攜帶數據。

如果服務端和客戶端同時打開會怎樣?

  1. 客戶端發送 ++SYN++,服務端也發送 ++SYN++
  2. 客戶端和服務端同時發送 ++SYN+ACK++
  3. 最后雙方同時進入 ++ESTABLISHED++

什么是四次揮手?

第一次揮手:客戶端發送FIN=1,用來關閉客戶端到服務端的數據傳送,隨機產生一個seq=u,將該數據包發送給服務端,客戶端進入FIN_WAIT_1狀態。

第二次揮手:服務端收到結束標志FIN=1后,發送確認標志ACK=1給客戶端,確認號ack=u+1,隨機產生一個seq=v,將該數據包發送給客戶端,服務端進入CLOSE_WAIT狀態,第二次揮手主要是告訴客戶端收到了FIN 狀態的數據包。

第三次揮手:當服務器端發送完所有的請求后,服務端發送結束標志FIN=1,用來關閉服務端到客戶端的數據傳送,並發送確認標志ACK=1,隨機產生一個seq=w,ack=u+1,將該數據包發送給客戶端,服務端進入LAST_ACK狀態。

第四次揮手:客戶端收到FIN后,客戶端進入TIME_WAIT狀態,接着發送確認標志ACK=1給服務端,確認號seq=u+1,服務端進入CLOSED狀態。在這個過程中會等待2MSL(2個最長報文存活時間)
完成四次揮手,成功斷開連接。

TCP四次揮手為什么要等待2MSL?

  1. 防止客戶端最后一次發給服務器的確認在網絡中丟失以至於客戶端關閉,而服務端並未關閉,導致資源的浪費。
  2. 等待最大的2msl可以讓本次連接的所有的網絡包在鏈路上消失,以防造成不必要的干擾。

如果client直接closed,然后又向server發起了一個新連接,我們不能保證這個新連接和剛關閉的連接的端口號是不同的。假設新連接和已經關閉的老端口號是一樣的,如果前一次滯留的某些數據仍然在網絡中,這些延遲數據會在新連接建立后到達Server,所以socket就認為那個延遲的數據是屬於新連接的,數據包就會發生混淆。所以client要在TIME_WAIT狀態等待2倍的MSL,這樣保證本次連接的所有數據都從網絡中消失。

半連接隊列和 SYN Flood 洪水攻擊的關系

三次握手前,服務端的狀態從CLOSED變為LISTEN, 同時在內部創建了兩個隊列:半連接隊列和全連接隊列,即SYN隊列和ACCEPT隊列

半連接隊列

當客戶端發送++SYN++到 服務端端,服務器端發送 ++ACK+SYN++,狀態由++LISTEN++進入到++SYN-REVD++,此時這個連接就被推入了SYN隊列,也就是半連接隊列

全連接隊列

當客戶端返回ACK, 服務端接收后,三次握手完成。這個時候連接等待被具體的應用取走,在被取走之前,它會被推入另外一個 TCP 維護的隊列,也就是全連接隊列(Accept Queue)

SYN Flood 攻擊原理

SYN Flood 屬於典型的 DoS/DDoS 攻擊。其攻擊的原理很簡單,就是用客戶端在短時間內偽造大量不存在的 IP 地址,並向服務端瘋狂發送SYN。對於服務端而言,會產生兩個危險的后果:

  • 處理大量的SYN包並返回對應ACK, 勢必有大量連接處於SYN_RCVD狀態,從而占滿整個半連接隊列,無法處理正常的請求。
  • 由於是不存在的 IP,服務端長時間收不到客戶端的ACK,會導致服務端不斷重發數據,直到耗盡服務端的資源。

如何應對 SYN Flood 攻擊?

  • 增加半連接隊列容量
  • 減少SYN+ACK重復次數
  • 利用 SYN Cookie 技術,在服務端接收到SYN后不立即分配連接資源,而是根據這個SYN計算出一個Cookie,連同第二次握手回復給客戶端,在客戶端回復ACK的時候帶上這個Cookie值,服務端驗證 Cookie 合法之后才分配連接資源
  • Syn Cache技術,這種技術是在收到SYN數據報文時不急於去分配TCB,而是先回應一個SYN ACK報文,並在一個專用HASH表(Cache)中保存這種半開連接信息,直到收到正確的回應ACK報文再分配TCB。在FreeBSD系統中這種Cache每個半開連接只需使用160字節,遠小於TCB所需的736個字節
  • 使用SYN Proxy防火牆,防火牆中都提供一種SYN代理的功能,其主要原理是對試圖穿越的SYN請求進行驗證后才放行

TCP 的 keep-alive?

TCP 的 keep-alive 用來探測對端的連接有沒有失效

TCP 報文頭部的字段

一個TCP連接都是四元組,由源IP、源端口、目標IP、目標端口唯一確定一個連接。其中源IP和目標IP在IP協議中處理

序列號

即Sequence number, 指的是本報文段第一個字節的序列號。

從圖中可以看出,序列號是一個長為 4 個字節,也就是 32 位的無符號整數,表示范圍為 0 ~ 2^32 - 1。如果到達最大值了后就循環到0

ISN(初始序列號)

每 4s 加 1,溢出則回到 0,如果 ISN 被攻擊者預測到,要知道源 IP 和源端口號都是很容易偽造的,當攻擊者猜測 ISN 之后,直接偽造一個 RST 后,就可以強制連接關閉的,這是非常危險的

RST 攻擊

A和服務器B之間建立了TCP連接,此時C偽造了一個TCP包發給B,使B異常的斷開了與A之間的TCP連接,就是RST攻擊了

窗口大小

占用兩個字節,也就是 16 位,但實際上是不夠用的。因此 TCP 引入了窗口縮放的選項,作為窗口縮放的比例因子,這個比例因子的范圍在 0 ~ 14,比例因子可以將窗口的值擴大為原來的 2 ^ n 次方。

校驗和

占用兩個字節,防止傳輸過程中數據包有損壞,如果遇到校驗和有差錯的報文,TCP 直接丟棄之,等待重傳。

TCP流量控制和擁塞控制

TCP 采用大小可控制的流量窗口來控制,發送窗口在連接建立時由雙方商定。但在通信的過程中,接收方可根據自己的資源情況,隨時動態地調整對方的發送窗口上限值

  • 接收端窗口 rwnd(也稱為通知窗口):接收端緩沖區大小,表示接收方的接收能力。接收端將此窗口值放在 TCP 報文的首部中的窗口字段,傳送給發送端。
  • 擁塞窗口cwnd (congestion window):發送端緩沖區大小
  • 發送窗口swnd:發送窗口的上限值 = Min [rwnd, cwnd]
  • 當 rwnd < cwnd 時,是接收端的接收能力限制發送窗口的最大值

流量控制

流量控制:就是讓發送方的發送速率不要太快,讓接收方來得及接收。

擁塞

擁塞是指:數據發送速度超出網絡所能承受的極限,經常造成路由器丟包的現象

慢啟動(慢開始)

剛開始的時候,不知道網絡的情況,如果做的太激進,發包太急,那么瘋狂丟包,造成雪崩式的網絡災難

在每收到一個對新的報文段的確認后,將擁塞窗口增加至多一個 MSS(最大報文長度的值) 的數值(一開始窗口大小為1,發送一個,接收一個,增長為2;發送兩個,得到兩個確認,增長為4......因此,窗口是指數增長的)

慢啟動閾值

因為擁塞窗口是指數增長的,為防止后期增長過快,需要另外一個變量---慢開始門限(閾值),它的閾值叫做慢啟動閾值,到底閥值后,以前一個 RTT 下來,cwnd翻倍,現在cwnd只是增加 1 而已

快速重傳

在 TCP 傳輸的過程中,如果發生了丟包,即接收端發現數據段不是按序到達的時候,接收端的處理是重復發送之前的 ACK,在收到發送端的報文后,接收端回復一個 ACK 報文,那么在這個報文首部的可選項中,就可以加上SACK這個屬性,通過left edge和right edge告知發送端已經收到了哪些區間的數據報。因此,即使第 5 個包丟包了,當收到第 6、7 個包之后,接收端依然會告訴發送端,這兩個包到了。剩下第 5 個包沒到,就重傳這個包。這個過程也叫做選擇性重傳(SACK,Selective Acknowledgment)

快速恢復

發送端收到三次重復 ACK 之后,發現丟包,覺得現在的網絡已經有些擁塞了,自己會進入快速恢復階段

  1. 擁塞閾值降低為 cwnd 的一半
  2. cwnd 的大小變為擁塞閾值
  3. cwnd 線性增加

TCP之Nagle算法&&延遲ACK

Nagle算法

為了解決小分組的數量,從而減小網絡擁塞的出現,其中小分組的定義是小於MSS(最大報文長度)的任何分組

算法規則

  • 當第一次發送數據時不用等待,就算是 1byte 的小包也立即發送
  • 后面發送滿足下面條件之一就可以發了:
  • 數據包大小達到最大段大小(Max Segment Size, 即 MSS)
  • 之前所有包的 ACK 都已接收到

延遲ACK

如果tcp對每個數據包都發送一個ack確認,那么只是一個單獨的數據包為了發送一個ack代價比較高,所以tcp會延遲一段時間,如果這段時間內有數據發送到對端,則捎帶發送ack,總結來說就是延遲合並發送ack。

以下場景不適合延遲

  • 需要調整窗口大小
  • 亂序需要重發的時候

如果同時使用Nagle和延遲ACK

使用Nagle,兩次寫數據長度小於MSS,當第一次寫數據到達對端后,對端延遲ack,不發送ack,而本端因為要發送的數據長度小於MSS,所以nagle算法起作用,數據並不會立即發送,而是等待對端發送的第一次數據確認ack;這樣的情況下,需要等待對端超時發送ack,然后本段才能發送第二次寫的數據,從而造成延遲,產生性能問題

HTTP(超文本傳輸協議)

HTTP 報文

HTTP 狀態碼

狀態碼 描述
101 Switching Protocols(交換協議),切換到更高級的協議,例如WebSocket 通信
200 請求成功。一般用於GET與POST請求
201 已創建。成功請求並創建了新的資源
204 無內容。服務器成功處理,但未返回內容。在未更新網頁的情況下,可確保瀏覽器繼續顯示當前文檔
205 重置內容。服務器處理成功,用戶終端(例如:瀏覽器)應重置文檔視圖。可通過此返回碼清除瀏覽器的表單域
206 部分內容。服務器成功處理了部分GET請求
301 永久移動。請求的資源已被永久的移動到新URI,返回信息會包括新的URI,瀏覽器會自動定向到新URI。今后任何新的請求都應使用新的URI代替
302 臨時移動。與301類似。但資源只是臨時被移動。客戶端應繼續使用原有URI
304 未修改,命中協商緩存。所請求的資源未修改,服務器返回此狀態碼時,不會返回任何資源。客戶端通常會緩存訪問過的資源,通過提供一個頭信息指出客戶端希望只返回在指定日期之后修改的資源
401 請求要求用戶的身份認證
403 服務器理解請求客戶端的請求,但是拒絕執行此請求
404 服務器無法根據客戶端的請求找到資源(網頁)
500 服務器內部錯誤,無法完成請求
501 服務器不支持請求的功能,無法完成請求
502 作為網關或者代理工作的服務器嘗試執行請求時,從遠程服務器接收到了一個無效的響應

GET 和 POST 有什么區別?

首先最直觀的是語義上的區別。
而后又有這樣一些具體的差別:

  • 從緩存的角度,GET 請求會被瀏覽器主動緩存下來,留下歷史記錄,而 POST 默認不會。
  • 從編碼的角度,GET 只能進行 URL 編碼,只能接收 ASCII 字符,而 POST 沒有限制。
  • 從參數的角度,GET 一般放在 URL 中,因此不安全,POST 放在請求體中,更適合傳輸敏感信息。
  • 從冪等性的角度,GET是冪等的,而POST不是。(冪等表示執行相同的操作,結果也是相同的)
  • 從TCP的角度,GET 請求會把請求報文一次性發出去,而 POST 會分為兩個 TCP 數據包,首先發 header 部分,如果服務器響應 100(continue), 然后發 body 部分。(火狐瀏覽器除外,它的 POST 請求只發一個 TCP 包)

HTTPS 詳解

簡介

HTTPS 協議之所以是安全的是因為 HTTPS 協議會對傳輸的數據進行加密,而加密過程是使用了非對稱加密實現。但其實,HTTPS 在內容傳輸的加密上使用的是對稱加密,非對稱加密只作用在證書驗證階段

首先需要理解對稱加密和非對稱加密的概念,然后討論兩者應用后的效果如何。

對稱加密是最簡單的方式,指的是加密和解密用的是同樣的密鑰。

而對於非對稱加密,如果有 A、 B 兩把密鑰,如果用 A 加密過的數據包只能用 B 解密,反之,如果用 B 加密過的數據包只能用 A 解密.

對稱加解密過程

接着我們來談談瀏覽器和服務器進行協商加解密的過程。

首先,瀏覽器會給服務器發送一個隨機數 client_random 和一個加密的方法列表。

服務器接收后給瀏覽器返回另一個隨機數 server_random 和加密方法。

現在,兩者擁有三樣相同的憑證: client_random、server_random 和加密方法。

接着用這個加密方法將兩個隨機數混合起來生成密鑰,這個密鑰就是瀏覽器和服務端通信的暗號

影響

如果只是采用對稱加密的方式,如果遇到中間人攻擊,那么第三方可以在中間獲取到client_random、server_random和加密方法,由於這個加密方法同時可以解密,那么第三方就可以很成功的拿到數據,對其進行加解密

思考?

是否可以采用非對稱加密的方式進行加解密呢?實際上非對稱加密需要的計算量非常大,對於稍微大一點的數據即使用最快的處理器也非常耗時,這樣數據傳輸的時間大大加長,造成網絡的阻塞

是否需要證書?

如果只是采用的非對稱加密算法,黑客如果采用 DNS 劫持,將目標地址替換成黑客服務器的地址,然后黑客自己造一份公鑰和私鑰,照樣能進行數據傳輸.

為了獲取這個證書,服務器運營者需要向第三方認證機構獲取授權,這個第三方機構也叫CA(Certificate Authority), 認證通過后 CA 會給服務器頒發數字證書, 假設不存在認證機構,任何人都可以制作證書,這帶來的安全風險便是經典的“中間人攻擊”問題。

HTTPS 是怎么做的呢?

HTTPS 的整體過程分為證書驗證和數據傳輸階段

證書驗證階段

瀏覽器向服務器發送client_random和加密方法列表。

服務器接收到,返回server_random、加密方法以及公鑰。

++瀏覽器接收,接着生成另一個隨機數pre_random, 並且通過證書中的公鑰對隨機數進行加密傳輸到服務端,傳給服務器。++

服務器用公鑰解密這個被加密后的pre_random

現在瀏覽器和服務器有三樣相同的憑證:client_random、server_random和pre_random。然后兩者用相同的加密方法混合這三個隨機數,生成最終的密鑰。

CA證書認證過程

首先會讀取證書中的明文內容。CA 進行數字證書的簽名時會保存一個 Hash 函數,來這個函數來計算明文內容得到信息A,然后用公鑰解密明文內容得到信息B,兩份信息做比對,一致則表示認證合法

當然有時候對於瀏覽器而言,它不知道哪些 CA 是值得信任的,因此會繼續對CA鏈進行查找,查找 CA 的上級 CA,以同樣的信息比對方式驗證上級 CA 的合法性。一般根級的 CA 會內置在操作系統當中,當然如果向上找沒有找到根級的 CA,那么將被視為不合法

數據傳輸階段

瀏覽器和服務器盡管用一樣的密鑰進行通信,即使用對稱加密。

結論

回頭比較一下和單純的使用非對稱加密, 這種方式做了什么改進呢?本質上是防止了私鑰加密的數據外傳。單獨使用非對稱加密,最大的漏洞在於服務器傳數據給瀏覽器只能用私鑰加密,這是危險產生的根源。利用對稱和非對稱加密結合的方式,就防止了這一點,從而保證了安全

HTTPS 它在 HTTP 和 TCP 的傳輸中建立了一個 SSL 安全層,利用對稱加密和非對稱機密結合數字證書認證的方式,讓傳輸過程的安全性大大提高

HTTP1.O

HTTP 1.0規定瀏覽器與服務器只保持短暫的連接,瀏覽器的每次請求都需要與服務器建立一個TCP連接,服務器完成請求處理后立即斷開TCP連接,服務器不跟蹤每個客戶也不記錄過去的請求。但是,這也造成了一些性能上的缺陷

HTTP 1.1

在一個TCP連接上可以傳送多個HTTP請求和響應,減少了建立和關閉連接的消耗和延遲。一個包含有許多圖像的網頁文件的多個請求和應答可以在一個連接中傳輸,但每個單獨的網頁文件的請求和應答仍然需要使用各自的連接

HTTP1.1也擴充了請求頭來改進和擴充1.1的功能.例如,HTTP 1.0不支持Host請求頭字段,WEB瀏覽器無法使用主機頭名來明確表示要訪問服務器上的哪個WEB站點,這樣就無法使用WEB服務器在同一個IP地址和端口號上配置多個虛擬WEB站點。可以設置Connection請求頭的值為Keep-Alive時,客戶端通知服務器返回本次請求結果后保持連接;Connection請求頭的值為close時,客戶端通知服務器返回本次請求結果后關閉連接。HTTP 1.1還提供了與身份認證、狀態管理和Cache緩存等機制相關的請求頭和響應頭。HTTP/1.0不支持文件斷點續傳

HTTP1.1 如何解決 HTTP 的隊頭阻塞問題?

什么是 HTTP 隊頭阻塞?
從前面的小節可以知道,HTTP 傳輸是基於請求-應答的模式進行的,報文必須是一發一收,但值得注意的是,里面的任務被放在一個任務隊列中串行執行,一旦隊首的請求處理太慢,就會阻塞后面請求的處理。這就是著名的HTTP隊頭阻塞問題。

並發連接

對於一個域名允許分配多個長連接,那么相當於增加了任務隊列,不至於一個隊伍的任務阻塞其它所有任務。在RFC2616規定過客戶端最多並發 2 個連接,不過事實上在現在的瀏覽器標准中,這個上限要多很多,Chrome 中是 6 個

域名分片

一個域名不是可以並發 6 個長連接嗎?那我就多分幾個域名。
比如 content1.sanyuan.com 、content2.sanyuan.com。
這樣一個sanyuan.com域名下可以分出非常多的二級域名,而它們都指向同樣的一台服務器,能夠並發的長連接數更多了,事實上也更好地解決了隊頭阻塞的問題。

HTTP2.O

在性能提升方面,添加了新的功能:

  • 頭部壓縮
  • 多路復用

另外還有:

  • 設置請求優先級
  • 服務器推送

頭部壓縮

頭部壓縮主要采用了 HPACK 算法,在HTTP通信的過程中,每一次請求都會發送相同的頭部,以及相同的鍵值信息,這個時候還是存在非常大的優化空間的

Hpack算法

HTTP1.x的header中的字段很多時候都是重復的,例如method:get、status:200等等,隨着網頁增長到需要數十到數百個請求,這些請求中的冗余標頭字段不必要地消耗帶寬,從而顯著增加了延遲

思想原理

通信的雙方各擁有一本字典,記錄着某些字符對應的文本內容

過程

消息發送端和消息接受端共同維護一份靜態表和一份動態表(這兩個合起來充當字典的角色),
每次請求時,發送方根據字典的內容以及一些特定指定編碼壓縮消息頭部,
接收方根據字典進行解碼,並且根據指令來判斷是否需要更新動態表

靜態表

  • name和value都可以完全確定,這種已知鍵值對的方式,直接用一個字符來表示
  • 只能夠確定name:比如:authority、cookie,首先將name部分先用一個字符(比如cookie)來表示,同時,根據情況判斷是否告知服務端,將 cookie: xxxxxxx 添加到動態表中(我們這里默認假定是從客戶端向服務端發送消息)

動態表

動態表最初是一個空表,當每次解壓頭部的時候,有可能會添加條目(比如前面提到的cookie,當解壓過一次cookie時,cookie: xxxxxxx就有可能被添加到動態表了,至於是否添加要根據后面提到的指令判斷)
動態表允許包含重復的條目,也就是可能出現完全相同的鍵值對
為了限制解碼器的需求,動態表大小有嚴格限制的

索引地址空間

靜態表和動態表一起組成一個索引地址空間。設靜態表長度為s,動態表長度為k,那么最終的索引空間如下

  • 索引1-s是靜態表,s-s+k是動態表,
  • 新的條目從在動態表的開頭插入,從動態表末尾移除
  • 字段的name使用索引值表示,字段的value直接使用原有字面的值的八位字節序列或者使用靜態哈夫曼編碼表示
  • 字符串編碼過的數據,如果標志位H為"0",則編碼數據是字符串文字的原始八位字節;如果H是"1",則編碼數據是字符串文字的huffman編碼,剩下7位表示數據長度Length,以及8位字節序列表示的原有字面字符串

優勢

可以在傳輸的過程,簡化消息內容,從而降低消息的大小

頭部壓縮可以減小請求的頭部大小
二次壓縮的壓縮率會更高(壓縮體積越減越小)

多路復用

而 HTTP/2 便從 HTTP 協議本身解決了隊頭阻塞問題。注意,這里並不是指的TCP隊頭阻塞,而是HTTP隊頭阻塞,兩者並不是一回事。TCP 的隊頭阻塞是在數據包層面,單位是數據包,前一個報文沒有收到便不會將后面收到的報文上傳給 HTTP,而HTTP 的隊頭阻塞是在 HTTP 請求-響應層面,前一個請求沒處理完,后面的請求就要阻塞住。兩者所在的層次不一樣。

之前解決HTTP隊頭堵塞問題一般采用並發連接,或者是多域名的方式,這也會引發相互競爭資源的問題,而HTTP2從協議本身去解決這個問題。

原來Headers + Body的報文格式如今被拆分成了一個個二進制的幀,用Headers幀存放頭部字段,Data幀存放請求體數據。分幀之后,服務器看到的不再是一個個完整的 HTTP 請求報文,而是一堆亂序的二進制幀。這些二進制幀不存在先后關系,因此也就不會排隊等待。

服務器推送

在 HTTP/2 當中,服務器已經不再是完全被動地接收請求,響應請求,它也能新建 stream 來給客戶端發送消息,當 TCP 連接建立之后,比如瀏覽器請求一個 HTML 文件,服務器就可以在返回 HTML 的基礎上,將 HTML 中引用到的其他資源文件一起返回給客戶端,減少客戶端的等待。

前端緩存到底是什么?

強緩存

瀏覽器中的緩存作用分為兩種情況,一種是需要發送HTTP請求,一種是不需要發送。

首先是檢查強緩存,這個階段不需要發送HTTP請求。

如何來檢查呢?通過相應的字段來進行,但是說起這個字段就有點門道了。

在HTTP/1.0和HTTP/1.1當中,這個字段是不一樣的。在早期,也就是HTTP/1.0時期,使用的是Expires,而HTTP/1.1使用的是Cache-Control。讓我們首先來看看Expires:

Expires:Thu,31 Dec 2037 23:59:59 GMT。這個時間代表着這個資源的失效時間,也就是說在2037年12月31日23點59分59秒之前都是有效的,即命中緩存。這種方式有一個明顯的缺點,由於失效時間是一個絕對時間,用這個時間和客戶端比較來判斷是否過期失效,所以當客戶端本地時間被修改以后,服務器與客戶端時間偏差變大以后,就會導致緩存混亂

Cache-Control:Cache-Control使用的是相對時間, Cache-Control:max-age=31536000,也就是說緩存有效期為(31536000 / 24 / 60 * 60)天,過了這個時間就會失效,相當於是一個倒計時的過程。

禁用緩存並且沒有超過有效時間的情況下,再次訪問這個資源就命中了緩存,不會向服務器請求資源而是直接從瀏覽器緩存中取。

  • s-maxage 同 max-age,覆蓋 max-age、Expires,但僅適用於共享緩存,在私有緩存中被忽略。
  • public 表明響應可以被任何對象(發送請求的客戶端、代理服務器等等)緩存。
  • private 表明響應只能被單個用戶(可能是操作系統用戶、瀏覽器用戶)緩存,是非共享的,不能被代理服務器緩存。
  • no-cache 強制所有緩存了該響應的用戶,在使用已緩存的數據前,發送帶驗證器的請求到服務器,相當於是走協商緩存。
  • no-store 禁止緩存,每次請求都要向服務器重新獲取數據。

協商緩存

如果沒有命中強制緩存,發送http請求到服務器,服務器根據http頭信息中的Last-Modify/If-Modify-Since或Etag/If-None-Match來判斷是否命中協商緩存

當瀏覽器再次請求該資源時,發送的請求頭中會包含If-Modify-Since,該值為緩存之前返回的Last-Modify。服務器收到If-Modify-Since后,根據資源的最后修改時間判斷是否命中緩存,同樣的,當瀏覽器再次請求該資源時,發送的請求頭中會也包含If-None-Match,該值為緩存之前返回的Etag。服務器收到If-None-Match后,根據資源的ETag值是否發生變化來判斷是否命中緩存。

Etag 的生成

  • 文件的i-node編號,此i-node非彼iNode。是Linux/Unix用來識別文件的編號
  • 文件最后修改時間
  • 文件大小

Etag 是通過上面的因子,使用抗碰撞散列函數來生成,理論上ETag也是會重復的,只是概率小到可以忽略。

Last-Modified與ETag是可以一起使用的,服務器會優先驗證ETag,一致的情況下,才會繼續比對Last-Modified,最后才決定是否返回304

為什么 Etag 會優於 Last-Modified?

  • 在精准度上,ETag優於Last-Modified。優於 ETag 是按照內容給資源上標識,因此能准確感知資源的變化。而 Last-Modified 就不一樣了,它在一些特殊的情況並不能准確感知資源變化,主要有兩種情況:
  • 編輯了資源文件,但是文件內容並沒有更改,這樣也會造成緩存失效。
  • Last-Modified 能夠感知的單位時間是秒,如果文件在 1 秒內改變了多次,那么這時候的 Last-Modified 並沒有體現出修改了

Memory Cache 和 Disk Cache

我們可以看到,在命中強制緩存的情況下,狀態碼是200,但會從內存中,或者是從本地磁盤中讀取。

Memory Cache指的是內存緩存,從效率上講它是最快的。但是從存活時間來講又是最短的,當渲染進程結束后,內存緩存也就不存在了。

Disk Cache就是存儲在磁盤中的緩存,從存取效率上講是比內存緩存慢的,但是他的優勢在於存儲容量和存儲時長。

讀取規則涉及到三級緩存原理

  1. 先查找內存,如果內存中存在,從內存中加載;
  2. 如果內存中未查找到,選擇硬盤獲取,如果硬盤中有,從硬盤中加載;
  3. 如果硬盤中未查找到,那就進行網絡請求;
  4. 加載到的資源緩存到硬盤和內存;

存放的規則根據LRU(最近最少使用)的方法幫經常使用的資源存在內存中,不經常使用的存在磁盤中,當達到存儲的閥值,定期清除磁盤中的資源

HTTP2 push cache

詳細內容查看 : 閱讀理解:HTTP/2 push is tougher than I thought


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM