-
Notifications
You must be signed in to change notification settings - Fork 0
/
1-18-1.html
254 lines (253 loc) · 16.7 KB
/
1-18-1.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="./public/favicon.ico" />
<meta http-equiv="cache-control" content="no-cache" />
<title></title>
<link rel="stylesheet" href=" https://necolas.github.io/normalize.css/8.0.1/normalize.css" />
<link rel="stylesheet" href="./hightlight/default.min.css" />
<link rel="stylesheet" href="./css/main.css" />
<link rel="stylesheet" href="./css/copybutton.css" />
<link rel="stylesheet" href="./css/hightlight.css" />
<script src="./hightlight/hightlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.11/clipboard.min.js"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-BEVZJDBC7Z"></script>
<script src="./js/gtag.js"></script>
</head>
<body>
<header>
<nav>
<h1>
<span id="toggle-menu"></span>
<a href="index.html"></a>
</h1>
</nav>
</header>
<main>
<aside>
<nav></nav>
</aside>
<article>
<h2 id="1-18-1">1-18-1 HTTP</h2>
<p>
HTTP 和 JavaScript一樣,說到底還是需要瀏覽器的支援。每個瀏覽器或伺服器對
HTTP的每個方面的實現並不能保持完全一致,因此不同的瀏覽器或伺服器中的標準有可能不同,使用者對使用不同標準的應用的體驗也可能會有細微的差別。
下面來看一下 HTTP的現狀和痛點。
</p>
<h3>HTTP 的現狀和痛點</h3>
<p>
雖然 HTTP
2.0已於2015年發佈,但是考慮到目前該版本的落地情況,以及在各大廠商中的應用情況,這裡還是先以HTTP1.1為例進行分析。
</p>
<p>HTTP 1.1 是劃時代的,它解決了 HTTP 1.0 時代最重要的兩大問題:</p>
<ul>
<li>
TCP 連接無法重複使用,每次請求都需要重新建立 TCP
通道,這就需要重複進行三次驗證和四次揮手,也就是說每個 TCP 連接只能發送一個請求。
</li>
<li>
列首阻塞,每個請求都要過「獨木橋」,橋寬為一個請求的寬度,也就是說,即使多個請求平行發出,也只能一個接一個地進行排隊。
</li>
</ul>
<p>HTTP 1.1 對以上問題進行了「對症下藥」的改進,它引用了長連接和管線化。</p>
<ul>
<li>
長連接:HTTP 1.1 支援長連接(Persistent Connection),且會預設開啟
Connection:keep-alive,這樣在一個 TCP 連接上可以傳送多個 HTTP
請求和回應,減少了建立和關閉連接的消耗和延遲。業界在這方面的成熟方案有 Google 的
protobuf。
</li>
<li>
管線化:管線化在長連接的基礎上使多個請求可以用同一個 TCP 連接,這樣重複使用 TCP
連接就使得平行發出請求成為可能。當瀏覽器同時發出多個 HTTP
請求時,瀏覽器無須等待上一個請求傳回結果,即可處理其他請求。但是需要注意,管線化只是可以使瀏覽器平行發出請求,並沒有從根本上解決列首阻塞問題,因為對請求的回應仍然要遵循先進先出的原則,第一個請求的處理結果傳回後,第二個請求才會獲得回應。同時,瀏覽器供應商很難實現管線化,而且大多數瀏覽器預設禁用管線化特性,有的甚至完全刪除了它。
</li>
</ul>
<p>除此之外,HTTP 1.1 中還有一些創造性的改進,如下。</p>
<ul>
<li>增加了與快取相關的請求標頭。</li>
<li>進行了頻寛最佳化,並能夠使用 range 標頭等來支援中斷點續傳功能。</li>
<li>新增錯誤類型,並增強了錯誤和回應碼的語義特性。</li>
<li>新增了 Host 標頭處理,如果請求訊息中沒有 Host 標頭,則會顯示出錯。</li>
</ul>
<p>以改進後的 HTTP 1.1 為基礎,一些成熟的前後端互動方案(如下所示)也應運而出。</p>
<ul>
<li>HTTP long-polling</li>
<li>HTTP streaming</li>
<li>WebSocket</li>
</ul>
<p>它還是有一些缺陷的,例如下面這些。</p>
<ul>
<li>沒有真正解決列首堵塞問題。</li>
<li>明文傳輸,安全性有隱憂。</li>
<li>header 中攜帶的內容過多,增加了傳輸成本。</li>
<li>
預設開啟 keep-alive
可能會給伺服器端造成效能壓力,舉例來說,對於一次性的請求(如圖片CDN服務),在檔案被請求之後還保持了很長時間不必要的連接。
</li>
</ul>
<h3>HTTP 2.0的未來已經到來</h3>
<p>
2009年,Google 針對HTTP 1.1的一些問題發佈了 SPDY 協定。這個協定在 Chrome
瀏覽器上進行應用,在證明可行後,就成了HTTP 2.0 的基礎,主要特性都在 HTTP2.0中獲得繼承。
</p>
<p>
HTTP 2.0的目標是顯著改善效能,同時做到遷移透明。下面先來了解幾個HTTP 2.0 的相關基礎概念。
</p>
<ul>
<li>
幀:在 HTTP
2.0中,用戶端與伺服器端透過交換幀來通訊,幀是以這個新協定通訊為基礎的最小單位。
</li>
<li>訊息:是指邏輯上的 HTTP 訊息,如請求、回應等,由一幀或多幀組成。</li>
<li>流:流是連接中的虛擬通道,可以承載雙向訊息;每個流都有一個唯一的識別符號。</li>
</ul>
<p>HTTP 2.0最主要的特性有以下幾點。</p>
<h4>1. 二進位分幀</h4>
<p>
HTTP 2.0 的協定解析採用二進位格式,而非 HTTP 1.x
的文字格式,採用二進位格式進行協定解析更加高效,可以進一步提升效能。新的二進位協定被稱為二進位分幀層協定(Binary
Framing Layer),每一個請求都包含以下公共欄位。
</p>
<ul>
<li>Type:幀的類型,標識幀的用途。</li>
<li>Length:整個幀從開始到結束的長度。</li>
<li>Flags:指定幀的狀態資訊。</li>
<li>Steam Identifier:用於流量控制,可以追蹤邏輯流的幀成員關係。</li>
<li>Frame payload:請求正文。</li>
</ul>
<p>
二進位分幀層中的內容屬於協定中比較偏底層的內容,前端中會接觸得比較少,這裡只需要大家明白:二進位協定將通訊傳輸資訊分解為幀,這些幀交織在用戶端與伺服器端之間的雙向邏輯流中,使所有通訊都可以在單一TCP連接上執行,而且該連接在整個對話期間一直處於開啟狀態。
</p>
<h4>2. 請求/回應重複使用</h4>
<p>
上面提到,為每幀分配一個流識別符號,可以使它們在一個TCP
連接上進行獨立發送。此技術實現了完全雙向的請求和回應訊息重複使用,解決了列首阻塞的問題。換句話說,一個請求對應一個流(stream)並分配一個id,這樣一個連接上可以有多個流,所有流的幀都可以相互混雜在一起,接收方可以根據流的id
將幀分配到各自不同的請求中。
</p>
<p>
歸納一下就是,所有相同域名的請求都會透過同一個TCP連接並發送。同一 TCP
連接中可以發送多個請求,對端可以透過幀中的標識知道該幀屬於哪一個請求。透過這個技術,便可以避免HTTP
舊版本中的列首阻塞問題,相當大地加強傳輸效能。這是真正意義上的多工。
</p>
<h4>3. 表頭壓縮</h4>
<p>
表頭壓縮的實現方式要求用戶端和伺服器端都維護之前看見的表頭欄位清單。在發出第一個請求後,瀏覽器僅需發送與前一個表頭的不同之處,而對於相同之處,伺服器可以從表頭的列表中取得。
</p>
<h4>4. 流優先化</h4>
<p>
訊息幀透過流進行發送。我們提到了為給每個流都分配一個id,那麼也同樣可以為它們分配優先順序。這樣一來,伺服器端就可以根據優先順序確定它的處理順序。
</p>
<h4>5.伺服器端發送</h4>
<p>
當一個用戶端主動請求資源K時,如果伺服器端知道它很可能也需要資源
M,那麼伺服器端就會主動將資源M 發送給用戶端。當用戶端真的請求M時,便可以從快取中讀取。
</p>
<p>
這裡有一個問題是:伺服器端如何按照一種機制,在發送資源時確保用戶端不會發生超載的情況呢?
事實上,針對伺服器端希望發送的每個資源,伺服器端都會發送一個PUSH_PROMISE
幀,但用戶端可以透過發送 RST_STREAM 幀作為回應來拒絕發送。
</p>
<h4>6. 流量控制</h4>
<p>
流量控制允許接收者主動示意停止發送或減少發送的資料量。舉例來說,在一個視訊應用上觀看一個視訊時,伺服器端會同時向用戶端發送資料。如果視訊暫停,則用戶端會通知伺服器端停止發送視訊資料,以免耗盡本身的快取。
</p>
<h3>從即時通訊系統看 HTTP 發展</h3>
<p>
從上面的知識我們看出,傳統的瀏覽器和 HTTP
早期只能透過用戶端主動發送請求及伺服器端回應並回覆請求來實現資料互動。但是,在一些監控、Web
線上通訊、即時報價系統、線上遊戲等場景中,都需要將後台發生的變化主動地、即時地傳送到瀏覽器端,而不需要使用者手動地更新頁面。為了達到這個目的,應運而生了很多方案。
</p>
<h4>1. 輪詢</h4>
<p>
輪詢是最簡單無腦的方案。用戶端定期發送AJAX請求,伺服器端在受理請求後會立刻傳回資料。這種方式確保了資料的相對即時性,具有很好的瀏覽器相容性和簡單性。但是,其缺點也很明顯,舉例來說,資料延遲取決於輪詢頻率,如果頻率過高,就會產生大量無效請求;如果頻率過低,資料的即時性就會較差,同時,伺服器端的壓力也會比較大,進一步浪費頻寬流量。
</p>
<h4>2. 長輪詢</h4>
<p>
長輪詢(long-polling)的實現想法是:用戶端透過
AJAX發起請求,伺服器端在接到請求後不馬上傳回,而是保持這個連接,等待資料更新。當有資料需要發送給用戶端時,伺服器端才將目標資料發送給用戶端,傳回請求。用戶端收到回應後,馬上再發起一個新的請求給伺服器端,周而復始。
這樣的長輪詢能夠有效減少輪詢次數,而且大幅降低延遲,但伺服器端需要保持大量連接,會產生一定的消耗。
</p>
<h4>3. Comet streaming</h4>
<p>
Comet streaming 技術又被稱為 Forever
iframe,這種技術聽上去更加巧妙,需要我們動態載入一個隱藏的 iframe 標籤,iframe 標籤的 src
會指向請求的伺服器位址。同時,用戶端會準備好一個處理資料的函數,在伺服器端透過 iframe
標籤和用戶端通訊時,伺服器端便會傳回類似 script 標籤的文字,用戶端會將其解析為
JavaScript指令稿,並呼叫預先準備好的函數,將資料傳遞給 parent window ,類似 JSONP
的實現原理,程式如下。
</p>
<pre><code class="language-js">
<script>parent.getData("data from server") </script>
</code></pre>
<p>這樣的實現並不複雜,但說到底是一種奇怪的實現方式。</p>
<h4>4. AJAX multipart streaming</h4>
<p>
AJAX multipart streaming 用到了 HTTP 1.1中的 multipart
特性:用戶端發送請求,伺服器端保持這個連接,利用 HTTP 1.1 的 chunked encoding
機制(分段傳輸編碼)將資料傳遞給用戶端,直到逾時或用戶端手動中斷才停止傳輸。
這種方法屬於遵循官方標準的方法,但是就像前面所介紹的那樣,HTTP
1.1的multipart特性並沒有更廣泛地被瀏覽器支援並實現。
</p>
<h4>5. WebSocket</h4>
<p>
WebSocket 是從 HTML5
開始提供的一種在瀏覽器與伺服器之間進行全雙工通訊的網路技術。依靠這種技術可以實現用戶端和伺服器端的長連接,進行雙向即時通訊。
</p>
<p>
我們該如何了解 WebSocket 和HTTP 呢? HTTP 和 WebSocket 都是應用層協定,且都是以
TCP為基礎來傳輸資料的。
</p>
<p>
WebSocket 依賴一種升級的 HTTP 進行一次驗證,驗證成功後,資料就可以直接在
TCP通道中進行傳輸了。 這樣一來,連接的發起端還是用戶端,但是一旦 WebSocket
連接建立,用戶端和伺服器端就都可以向對方發送資料。 WebSocket
無疑是強大的,但是它也錯過了瀏覽器為 HTTP 提供的一些服務,需要開發者在使用時自己實現,因此
WebSocket 並不能取代 HTTP。 由此可以看出,HTTP
的發展不是封開的,而是吸取了「民間方案」和各種應用技術的優點。尤其是 HTTP 2.0
更是對之前協定的相當大補充和最佳化。
</p>
<h3>相關深度面試題目</h3>
<ul>
<li>
題目一:HTTP連接分為長連接和短連接,而現在常用的都是 HTTP
1.1,因此我們用的都是長連接。這種說法正確嗎? 其實是因為我們現在大多數應用都是以 HTTP
1.1 實現為基礎的,因此用的都是長連接。這種說法勉強算對,因為HTTP 1.1 中的 Connection
預設為 keep-alive。但是,HTTP並沒有長連接、短連接之分,所謂的長短連接都是在說 TCP
連接,TCP 連接是一個雙向通道,它可以保持一段時間不關閉,因此 TCP
連接才有真正的長連接和短連接。這個問題可以回到網路分層的話題上討論。HTTP
說到底是應用層的協定,而 TCP 才是真正的傳輸層協定,只有負責傳輸的這一層才需要建立連接。
</li>
<li>
題目二:長連接是一種永久連接嗎?
事實上,長連接並不是一種永久連接。在長連接建立後,如果一段時間內沒有發出HTTP
請求,那麼這個長連接就會中斷。這個逾時的時間可以在 header 中進行設定。
</li>
<li>
題目三:現代瀏覽器在與伺服器建立了一個 TCP 連接後是否會在一個HTTP
請求完成後中斷?什麼情況下會中斷? 在 HTTP1.0 中,一個伺服器在發送完一個 HTTP
回應後會中斷 TCP 連接。但在 HTTP 1.1 中會預設開啟
Connection:keep-alive,所以瀏覽器和伺服器之間會維持一段時間的 TCP
連接,不會在一個請求結束後就斷掉,除非顯性地宣告 Connection:close 。
</li>
<li>
題目四:一個 TCP 連接可以對應幾個 HTTP 請求,這些 HTTP 請求是否可以一起發送? 不管是
HTTP1.0 還是 HTTP1.1,單一 TCP
連接在同一時刻都只能處理一個請求,意思是說:兩個請求的生命週期不能重疊。
</li>
</ul>
<p>
雖然 HTTP 1.1 標準中規定了可以用 Pipelining
來解決這個問題,但是這個功能在瀏覽器中預設是關閉的。 因此,在 HTTP 1.1
中,一個支援持久連接的用戶端可以在一個連接中發送多個請求(不需要等待任意請求的回應),收到請求的伺服器端必須按照請求收到的順序發送回應。在
HTTP 2.0 中,由於多工特點的存在,多個 HTTP 請求是可以在同一個 TCP 連接中平行傳輸的。
</p>
</article>
</main>
</body>
<script type="module" src="./js/main.js"></script>
</html>