回顧HTTP2.0
HTTP1.1在應用層以純文本的形式進行通信,每次通信都要帶完整的HTTP的頭,而且不考慮pipeli模式的化,每次的過程總是像上面描述的那樣一去一回。那樣在實時性、並發想上都存在問題
頭部壓縮:HTTP2.0會對HTTP的頭進行一定的壓縮,將原來每次都要攜帶的大量key value在兩端建立一個索引表,對相同的頭只發送索引表中的索引
HTTP2.0協議將一個TCP的連接中,切分成多個流。每個流都有自己的ID,而且流可以是客戶端發服務端,也可以是服務端發客戶端,它其實只是一個虛擬的通道。流是有優先級的
HTTP2.0還將所有的傳輸信息分割為更小的信息和幀,並對它們采用二進制格式編碼。常見的幀有Header幀,用於傳輸Header內容,並且會開啟一個新的流,再就是Data幀,用來傳輸正文實體。多個Data幀屬於一個流
通過這兩種機制,http2.0的客戶端可以將對個請求不同的流中,然后將請求內容拆成幀,進行二進制傳輸。這些真可以打散亂序發送,然后根據每個幀首部的流標識符重新組裝,並且可以根據優先級,決定先處理那個流的數據
二進制傳輸就是以上
例子:
假設一個頁面要發送三個獨立的請求,一個獲取css,一個獲取js,一個獲取圖片jpg。如果使用HTTP1.1就是串行的,但是如果使用HTTP2.0,就可以在一個連接里,客戶端和服務端都可以同時發送多個請求或回應,而且不用按照順序一對一對應
http2.0成功解決了http1.1的隊首阻塞問題,同時,也不需要通過http1.x的pipeline機制用多條tcp連接來實現並行請求和響應;減少了tcp連接數對服務器性能的影響,同時將頁面的多個數據css,js,jpg等通過一個數據鏈接進行傳輸,能夠加快頁面組件的傳輸速度。
QUIC協議
HTTP2.0 也是基於TCP協議的,tcp協議在處理包時是有嚴格順序的
當其中一個數據包遇到問題,TCP連接需要等待找個包完成重傳之后才能繼續進行,雖然HTTP2.0通過多個stream,使得邏輯上一個tcp連接上的並行內容,進行多路數據的傳輸,然而這中間沒有關聯的數據,一前一后,前面stream2的幀沒有收到,后面stream1的幀也會因此堵塞
於是google的 QUIC協議從TCP切換到UDP
- 機制一:自定義連接機制
一條tcp連接是由四元組標識的,分別是源ip、源端口、目的端口,一旦一個元素發生變化時,就會斷開重連,重新連接。在次進行三次握手,導致一定的延時
在TCP是沒有辦法的,但是基於UDP,就可以在QUIC自己的邏輯里面維護連接的機制,不再以四元組標識,而是以一個64
位的隨機數作為ID來標識,而且UDP是無連接的,所以當ip或者端口變化的時候,只要ID不變,就不需要重新建立連接
- 機制二:自定義重傳機制
tcp為了保證可靠性,通過使用序號和應答機制,來解決順序問題和丟包問題
任何一個序號的包發過去,都要在一定的時間內得到應答,否則一旦超時,就會重發這個序號的包,通過自適應重傳算法(通過采樣往返時間RTT不斷調整)
但是,在TCP里面超時的采樣存在不准確的問題。例如發送一個包,序號100,發現沒有返回,於是在發送一個100,過一陣返回ACK101.客戶端收到了,但是往返的時間是多少,沒法計算。是ACK到達的時候減去第一還是第二。
QUIC也有個序列號,是遞增的,任何宇哥序列號的包只發送一次,下次就要加1,那樣就計算可以准確了
但是有一個問題,就是怎么知道包100和包101發送的是同樣的內容呢?quic定義了一個offset概念。QUIC既然是面向連接的,也就像TCP一樣,是一個數據流,發送的數據在這個數據流里面有個偏移量offset,可以通過offset查看數據發送到了那里,這樣只有這個offset的包沒有來,就要重發。如果來了,按照offset拼接,還是能夠拼成一個流。
- 機制三: 無阻塞的多路復用
有了自定義的連接和重傳機制,就可以解決上面HTTP2.0的多路復用問題
同HTTP2.0一樣,同一條 QUIC連接上可以創建多個stream,來發送多個HTTP請求,但是,QUIC是基於UDP的,一個連接上的多個stream之間沒有依賴。這樣,假如stream2丟了一個UDP包,后面跟着stream3的一個UDP包,雖然stream2的那個包需要重新傳,但是stream3的包無需等待,就可以發給用戶。
- 機制四:自定義流量控制
TCP的流量控制是通過滑動窗口協議。QUIC的流量控制也是通過window_update,來告訴對端它可以接受的字節數。但是QUIC的窗口是適應自己的多路復用機制的,不但在一個連接上控制窗口,還在一個連接中的每個steam控制窗口。
在TCP協議中,接收端的窗口的起始點是下一個要接收並且ACK的包,即便后來的包都到了,放在緩存里面,窗口也不能右移,因為TCP的ACK機制是基於序列號的累計應答,一旦ACK了一個序列號,就說明前面的都到了,所以是要前面的沒到,后面的到了也不能ACK,就會導致后面的到了,也有可能超時重傳,浪費帶寬
QUIC的ACK是基於offset的,每個offset的包來了,進了緩存,就可以應答,應答后就不會重發,中間的空檔會等待到來或者重發,而窗口的起始位置為當前收到的最大offset,從這個offset到當前的stream所能容納的最大緩存,是真正的窗口的大小,顯然,那樣更加准確。