比較老的 HTTP/1.0+“keep-alive”連接,以及現(xiàn)代的 HTTP/1.1“persistent”連接。
第一種 HTTP/1.0+ keep-alive連接
大約從 1996 年開始,很多 HTTP/1.0 瀏覽器和服務(wù)器都進行了擴展,以 支持一種被稱為 keep-alive 連接的早期實驗型持久連接。這些早期的持 久連接受到了一些互操作性設(shè)計方面問題的困擾,這些問題在后期的 HTTP/1.1 版本中都得到了修正,但很多客戶端和服務(wù)器仍然在使用這 些早期的 keep-alive 連接。 下圖 顯示了 keep-alive 連接的一些性能優(yōu)點,圖中將在串行連接上實 現(xiàn) 4 個 HTTP 事務(wù)的時間線與在一條持久連接上實現(xiàn)同樣事務(wù)所需的時 間線進行了比較。 由于去除了進行連接和關(guān)閉連接的開銷,所以時間 線有所縮減。(由于去除了慢啟動階段,請求和響應(yīng)時間可能也有縮減。這種性能收益在圖中沒有顯示出 來)
[圖片上傳失敗...(image-ee5ac6-1607570892332)]
Keep-Alive 操作
keep-alive 已經(jīng)不再使用了,而且在當(dāng)前的 HTTP/1.1 規(guī)范中也沒有對它 的說明了。但瀏覽器和服務(wù)器對 keep-alive 握手的使用仍然相當(dāng)廣泛, 因此,HTTP 的實現(xiàn)者應(yīng)該做好與之進行交互操作的準(zhǔn)備?,F(xiàn)在我們來 快速瀏覽一下 keep-alive 的操作。對 keep-alive 握手更詳細(xì)的解釋請參 見較早的 HTTP/1.1 規(guī)范版本(比如 RFC 2068)。 實現(xiàn) HTTP/1.0 keep-alive 連接的客戶端可以通過包含 Connection: Keep-Alive 首部請求將一條連接保持在打開狀態(tài)。 如果服務(wù)器愿意為下一條請求將連接保持在打開狀態(tài),就在響應(yīng)中包含 相同的首部(參見圖 4-14)。如果響應(yīng)中沒有 Connection: KeepAlive 首部,客戶端就認(rèn)為服務(wù)器不支持 keep-alive,會在發(fā)回響應(yīng)報 文之后關(guān)閉連接。
[圖片上傳失敗...(image-d396bb-1607570892332)]
Keep-Alive 選項
注意,keep-Alive 首部只是請求將連接保持在活躍狀態(tài)。發(fā)出 keepalive 請求之后,客戶端和服務(wù)器并不一定會同意進行 keep-alive 會話。 它們可以在任意時刻關(guān)閉空閑的 keep-alive 連接,并可隨意限制 keepalive 連接所處理事務(wù)的數(shù)量。 可以用 Keep-Alive 通用首部中指定的、由逗號分隔的選項來調(diào)節(jié) keep-alive 的行為。 參數(shù) timeout 是在 Keep-Alive 響應(yīng)首部發(fā)送的。它估計了服務(wù) 器希望將連接保持在活躍狀態(tài)的時間。這并不是一個承諾值。 參數(shù) max 是在 Keep-Alive 響應(yīng)首部發(fā)送的。它估計了服務(wù)器還希 望為多少個事務(wù)保持此連接的活躍狀態(tài)。這并不是一個承諾值。 Keep-Alive 首部還可支持任意未經(jīng)處理的屬性,這些屬性主要用 于診斷和調(diào)試。語法為 name [=value]。 Keep-Alive 首部完全是可選的,但只有在提供 Connection: KeepAlive 時才能使用它。這里有個 Keep-Alive 響應(yīng)首部的例子,這個例 子說明服務(wù)器最多還會為另外 5 個事務(wù)保持連接的打開狀態(tài),或者將打 開狀態(tài)保持到連接空閑了 2 分鐘之后。
[圖片上傳失敗...(image-2e172d-1607570892332)]
Keep-Alive 連接的限制和規(guī)則
使用 keep-alive 連接時有一些限制和一些需要澄清的地方。 在 HTTP/1.0 中,keep-alive 并不是默認(rèn)使用的??蛻舳吮仨毎l(fā)送一 個 Connection: Keep-Alive 請求首部來激活 keep-alive 連接。 Connection: Keep-Alive 首部必須隨所有希望保持持久連接的 報文一起發(fā)送。如果客戶端沒有發(fā)送 Connection: Keep-Alive 首部,服務(wù)器就會在那條請求之后關(guān)閉連接。 通過檢測響應(yīng)中是否包含Connection: Keep-Alive響應(yīng)首部,客 戶端可以判斷服務(wù)器是否會在發(fā)出響應(yīng)之后關(guān)閉連接。 只有在無需檢測到連接的關(guān)閉即可確定報文實體主體部分長度的情 況下,才能將連接保持在打開狀態(tài)——也就是說實體的主體部分必 須有正確的 Content-Length,有多部件媒體類型,或者用分塊傳 輸編碼的方式進行了編碼。在一條 keep-alive 信道中回送錯誤的 Content-Length 是很糟糕的事,這樣的話,事務(wù)處理的另一端就 無法精確地檢測出一條報文的結(jié)束和另一條報文的開始了。 代理和網(wǎng)關(guān)必須執(zhí)行 Connection 首部的規(guī)則。代理或網(wǎng)關(guān)必須在 將報文轉(zhuǎn)發(fā)出去或?qū)⑵涓咚倬彺嬷?,刪除在 Connection 首部中 命名的所有首部字段以及 Connection 首部自身。 嚴(yán)格來說,不應(yīng)該與無法確定是否支持 Connection 首部的代理服 務(wù)器建立 keep-alive 連接,以防止出現(xiàn)下面要介紹的啞代理問題。 在實際應(yīng)用中不是總能做到這一點的。 從技術(shù)上來講,應(yīng)該忽略所有來自 HTTP/1.0 設(shè)備的 Connection 首部字段(包括 Connection: Keep-Alive),因為它們可能是 由比較老的代理服務(wù)器誤轉(zhuǎn)發(fā)的。但實際上,盡管可能會有在老代 理上掛起的危險,有些客戶端和服務(wù)器還是會違反這條規(guī)則。 除非重復(fù)發(fā)送請求會產(chǎn)生其他一些副作用,否則如果在客戶端收到 完整的響應(yīng)之前連接就關(guān)閉了,客戶端就一定要做好重試請求的準(zhǔn) 備。
Keep-Alive 和啞代理
我們來仔細(xì)看看 keep-alive 和啞代理中一些比較微妙的問題。Web 客戶 端的 Connection: Keep-Alive 首部應(yīng)該只會對這條離開客戶端的 TCP 鏈路產(chǎn)生影響。這就是將其稱作“連接”首部的原因。如果客戶端正 在與一臺 Web 服務(wù)器對話,客戶端可以發(fā)送一個 Connection: KeepAlive 首部來告知服務(wù)器它希望保持連接的活躍狀態(tài)。如果服務(wù)器支持 keep-alive,就回送一個 Connection: Keep-Alive 首部,否則就不回 送。
1. Connection 首部和盲中繼 問題出在代理上——尤其是那些不理解 Connection 首部,而且不 知道在沿著轉(zhuǎn)發(fā)鏈路將其發(fā)送出去之前,應(yīng)該將該首部刪除的代 理。很多老的或簡單的代理都是盲中繼(blind relay),它們只是 將字節(jié)從一個連接轉(zhuǎn)發(fā)到另一個連接中去,不對 Connection 首部 進行特殊的處理。 假設(shè)有一個 Web 客戶端正通過一個作為盲中繼使用的啞代理與 Web 服務(wù)器進行對話。下圖 顯示的就是這種情形。
[圖片上傳失敗...(image-4f85ad-1607570892332)]
這幅圖中發(fā)生的情況如下所示。
1. 在圖 4-15a 中,Web 客戶端向代理發(fā)送了一條報文,其中包含 了 Connection: Keep-Alive 首部,如果可能的話請求建立 一條 keep-alive 連接??蛻舳说却憫?yīng),以確定對方是否認(rèn)可 它對 keep-alive 信道的請求。
2. 啞代理收到了這條 HTTP 請求,但它并不理解 Connection 首 部(只是將其作為一個擴展首部對待)。代理不知道 keepalive 是什么意思,因此只是沿著轉(zhuǎn)發(fā)鏈路將報文一字不漏地發(fā) 送給服務(wù)器(圖 4-15b)。但 Connection 首部是個逐跳首 部,只適用于單條傳輸鏈路,不應(yīng)該沿著傳輸鏈路向下傳輸。 接下來,就要發(fā)生一些很糟糕的事情了。
3. 在圖 4-15b 中,經(jīng)過中繼的 HTTP 請求抵達了 Web 服務(wù)器。 當(dāng) Web 服務(wù)器收到經(jīng)過代理轉(zhuǎn)發(fā)的 Connection: KeepAlive 首部時,會誤以為代理(對服務(wù)器來說,這個代理看起 來就和所有其他客戶端一樣)希望進行 keep-alive 對話!對 Web 服務(wù)器來說這沒什么問題——它同意進行 keep-alive 對 話,并在圖 4-15c 中回送了一個 Connection: Keep-Alive 響應(yīng)首部。所以,此時 Web 服務(wù)器認(rèn)為它在與代理進行 keepalive 對話,會遵循 keep-alive 的規(guī)則。但代理卻對 keep-alive 一無所知。不妙。
4. 在圖 4-15d 中,啞代理將 Web 服務(wù)器的響應(yīng)報文回送給客戶 端,并將來自 Web 服務(wù)器的 Connection: Keep-Alive 首部 一起傳送過去。客戶端看到這個首部,就會認(rèn)為代理同意進行 keep-alive 對話。所以,此時客戶端和服務(wù)器都認(rèn)為它們在進 行 keep-alive 對話,但與它們進行對話的代理卻對 keep-alive 一無所知。
5. 由于代理對 keep-alive 一無所知,所以會將收到的所有數(shù)據(jù)都 回送給客戶端,然后等待源端服務(wù)器關(guān)閉連接。但源端服務(wù)器 會認(rèn)為代理已經(jīng)顯式地請求它將連接保持在打開狀態(tài)了,所以 不會去關(guān)閉連接。這樣,代理就會掛在那里等待連接的關(guān)閉。
6. 客戶端在圖 4-15d 中收到了回送的響應(yīng)報文時,會立即轉(zhuǎn)向下 一條請求,在 keep-alive 連接上向代理發(fā)送另一條請求(參見 圖 4-15e)。而代理并不認(rèn)為同一條連接上會有其他請求到 來,請求被忽略,瀏覽器就在這里轉(zhuǎn)圈,不會有任何進展了。
7. 這種錯誤的通信方式會使瀏覽器一直處于掛起狀態(tài),直到客戶 端或服務(wù)器將連接超時,并將其關(guān)閉為止。
2. 代理和逐跳首部 為避免此類代理通信問題的發(fā)生,現(xiàn)代的代理都絕不能轉(zhuǎn)發(fā) Connection 首部和所有名字出現(xiàn)在 Connection 值中的首部。因 此,如果一個代理收到了一個 Connection: Keep-Alive 首部, 是不應(yīng)該轉(zhuǎn)發(fā) Connection 首部,或所有名為 Keep-Alive 的首部 的。 另外,還有幾個不能作為 Connection 首部值列出,也不能被代理 轉(zhuǎn)發(fā)或作為緩存響應(yīng)使用的首部。其中包括 ProxyAuthenticate、Proxy-Connection、Transfer-Encoding 和 Upgrade。
插入Proxy-Connection
Netscape 的瀏覽器及代理實現(xiàn)者們提出了一個對盲中繼問題的變通做 法,這種做法并不要求所有的 Web 應(yīng)用程序支持高版本的 HTTP。這種 變通做法引入了一個名為 Proxy-Connection 的新首部,解決了在客 戶端后面緊跟著一個盲中繼所帶來的問題——但并沒有解決所有其他情 況下存在的問題。在顯式配置了代理的情況下,現(xiàn)代瀏覽器都實現(xiàn)了 Proxy-Connection,很多代理都能夠理解它。 問題是啞代理盲目地轉(zhuǎn)發(fā) Connection: Keep-Alive 之類的逐跳首部 惹出了麻煩。逐跳首部只與一條特定的連接有關(guān),不能被轉(zhuǎn)發(fā)。當(dāng)下游 服務(wù)器誤將轉(zhuǎn)發(fā)來的首部作為來自代理自身的請求解釋,用它來控制自 己的連接時,就會引發(fā)問題。 在網(wǎng)景的變通做法是,瀏覽器會向代理發(fā)送非標(biāo)準(zhǔn)的 ProxyConnection 擴展首部,而不是官方支持的著名的 Connection 首部。 如果代理是盲中繼,它會將無意義的 Proxy-Connection 首部轉(zhuǎn)發(fā)給 Web 服務(wù)器,服務(wù)器會忽略此首部,不會帶來任何問題。但如果代理是 個聰明的代理(能夠理解持久連接的握手動作),就用一個 Connection 首部取代無意義的 Proxy-Connection 首部,然后將其發(fā) 送給服務(wù)器,以收到預(yù)期的效果。
下圖 a ~ d顯示了盲中繼是如何向Wb服務(wù)器轉(zhuǎn)發(fā) ProxyConnection 首部,而不帶來任何問題的,Web 服務(wù)器忽略了這個首 部,這樣在客戶端和代理,或者代理和服務(wù)器之間就不會建立起 keepalive 連接了。下圖 e ~h 中那個聰明的代理知道 ProxyConnection 首部是對 keep-alive 對話的請求,它會發(fā)送自己的 Connection: Keep-Alive 首部來建立 keep-alive 連接。
[圖片上傳失敗...(image-b31141-1607570892332)]
在客戶端和服務(wù)器之間只有一個代理時可以用這種方案來解決問題。但 下圖 所示,如果在啞代理的任意一側(cè)還有一個聰明的代理,這個 問題就會再次露頭了
[圖片上傳失敗...(image-31c043-1607570892332)]
而且,網(wǎng)絡(luò)中出現(xiàn)“不可見”代理的情況現(xiàn)在變得很常見了,這些代理可 以是防火墻、攔截緩存,或者是反向代理服務(wù)器的加速器。這些設(shè)備對 瀏覽器是不可見的,所以瀏覽器不會向它們發(fā)送 Proxy-Connection 首部。透明的 Web 應(yīng)用程序正確地實現(xiàn)持久連接是非常重要的。
第二種 HTTP/1.1持久連接
HTTP/1.1 逐漸停止了對 keep-alive 連接的支持,用一種名為持久連接 (persistent connection)的改進型設(shè)計取代了它。持久連接的目的與 keep-alive 連接的目的相同,但工作機制更優(yōu)一些。 與 HTTP/1.0+ 的 keep-alive 連接不同,HTTP/1.1 持久連接在默認(rèn)情況下 是激活的。除非特別指明,否則 HTTP/1.1 假定所有連接都是持久的。 要在事務(wù)處理結(jié)束之后將連接關(guān)閉,HTTP/1.1 應(yīng)用程序必須向報文中 顯式地添加一個 Connection: close 首部。這是與以前的 HTTP 協(xié)議 版本很重要的區(qū)別,在以前的版本中,keep-alive 連接要么是可選的, 要么根本就不支持。
HTTP/1.1 客戶端假定在收到響應(yīng)后,除非響應(yīng)中包含了 Connection: close 首部,不然 HTTP/1.1 連接就仍維持在打開狀態(tài)。但是,客戶端 和服務(wù)器仍然可以隨時關(guān)閉空閑的連接。不發(fā)送 Connection: close 并不意味著服務(wù)器承諾永遠(yuǎn)將連接保持在打開狀態(tài)。
持久連接的限制和規(guī)則
在持久連接的使用中有以下限制和需要澄清的問題。
發(fā)送了 Connection: close 請求首部之后,客戶端就無法在那條 連接上發(fā)送更多的請求了。
如果客戶端不想在連接上發(fā)送其他請求了,就應(yīng)該在最后一條請求 中發(fā)送一個 Connection: close 請求首部。
只有當(dāng)連接上所有的報文都有正確的、自定義報文長度時——也就 是說,實體主體部分的長度都和相應(yīng)的 Content-Length 一致,或 者是用分塊傳輸編碼方式編碼的——連接才能持久保持。
HTTP/1.1 的代理必須能夠分別管理與客戶端和服務(wù)器的持久連接 ——每個持久連接都只適用于一跳傳輸。
(由于較老的代理會轉(zhuǎn)發(fā) Connection 首部,所以)HTTP/1.1 的 代理服務(wù)器不應(yīng)該與 HTTP/1.0 客戶端建立持久連接,除非它們了 解客戶端的處理能力。實際上,這一點是很難做到的,很多廠商都 違背了這一原則。
盡管服務(wù)器不應(yīng)該試圖在傳輸報文的過程中關(guān)閉連接,而且在關(guān)閉 連接之前至少應(yīng)該響應(yīng)一條請求,但不管 Connection 首部取了什 么值,HTTP/1.1 設(shè)備都可以在任意時刻關(guān)閉連接。
HTTP/1.1 應(yīng)用程序必須能夠從異步的關(guān)閉中恢復(fù)出來。只要不存 在可能會累積起來的副作用,客戶端都應(yīng)該重試這條請求。
除非重復(fù)發(fā)起請求會產(chǎn)生副作用,否則如果在客戶端收到整條響應(yīng) 之前連接關(guān)閉了,客戶端就必須要重新發(fā)起請求。
一個用戶客戶端對任何服務(wù)器或代理最多只能維護兩條持久連接, 以防服務(wù)器過載。代理可能需要更多到服務(wù)器的連接來支持并發(fā)用 戶的通信,所以,如果有 N 個用戶試圖訪問服務(wù)器的話,代理最多 要維持 2_N_ 條到任意服務(wù)器或父代理的連接。
管道化連接
HTTP/1.1 允許在持久連接上可選地使用請求管道。這是相對于 keepalive 連接的又一性能優(yōu)化。在響應(yīng)到達之前,可以將多條請求放入隊 列。當(dāng)?shù)谝粭l請求通過網(wǎng)絡(luò)流向地球另一端的服務(wù)器時,第二條和第三 條請求也可以開始發(fā)送了。在高時延網(wǎng)絡(luò)條件下,這樣做可以降低網(wǎng)絡(luò) 的環(huán)回時間,提高性能。
下圖 a-c 顯示了持久連接是怎樣消除 TCP 連接時延,以及管道化請求 (參見下圖 c)是如何消除傳輸時延的。
對管道化連接有幾條限制。
如果 HTTP 客戶端無法確認(rèn)連接是持久的,就不應(yīng)該使用管道。
必須按照與請求相同的順序回送 HTTP 響應(yīng)。HTTP 報文中沒有序 列號標(biāo)簽,因此如果收到的響應(yīng)失序了,就沒辦法將其與請求匹配 起來了。
HTTP 客戶端必須做好連接會在任意時刻關(guān)閉的準(zhǔn)備,還要準(zhǔn)備好 重發(fā)所有未完成的管道化請求。如果客戶端打開了一條持久連接, 并立即發(fā)出了 10 條請求,服務(wù)器可能在只處理了,比方說,5 條 請求之后關(guān)閉連接。剩下的 5 條請求會失敗,客戶端必須能夠應(yīng)對 這些過早關(guān)閉連接的情況,重新發(fā)出這些請求。
HTTP 客戶端不應(yīng)該用管道化的方式發(fā)送會產(chǎn)生副作用的請求(比 如 POST)??傊?,出錯的時候,管道化方式會阻礙客戶端了解服 務(wù)器執(zhí)行的是一系列管道化請求中的哪一些。由于無法安全地重試 POST 這樣的非冪等請求,所以出錯時,就存在某些方法永遠(yuǎn)不會 被執(zhí)行的風(fēng)險。
[圖片上傳失敗...(image-61a08f-1607570892332)]
關(guān)閉連接的奧秘
連接管理——尤其是知道在什么時候以及如何去關(guān)閉連接——是 HTTP 的實用魔法之一。這個問題比很多開發(fā)者起初意識到的復(fù)雜一些,而且 沒有多少資料涉及這個問題。
“任意”解除連接
所有 HTTP 客戶端、服務(wù)器或代理都可以在任意時刻關(guān)閉一條 TCP 傳 輸連接。通常會在一條報文結(jié)束時關(guān)閉連接,(除非服務(wù)器懷疑出現(xiàn)了客戶端或網(wǎng)絡(luò)故障,否則就不應(yīng)該在請求的中間關(guān)閉連接。)但出錯的時候,也可能 在首部行的中間,或其他奇怪的地方關(guān)閉連接。
對管道化持久連接來說,這種情形是很常見的。HTTP 應(yīng)用程序可以在 經(jīng)過任意一段時間之后,關(guān)閉持久連接。比如,在持久連接空閑一段時 間之后,服務(wù)器可能會決定將其關(guān)閉。
但是,服務(wù)器永遠(yuǎn)都無法確定在它關(guān)閉“空閑”連接的那一刻,在線路那 一頭的客戶端有沒有數(shù)據(jù)要發(fā)送。如果出現(xiàn)這種情況,客戶端就會在寫 入半截請求報文時發(fā)現(xiàn)出現(xiàn)了連接錯誤。
4.7.2 Content-Length 及截尾操作
每條 HTTP 響應(yīng)都應(yīng)該有精確的 Content-Length 首部,用以描述響應(yīng) 主體的尺寸。一些老的 HTTP 服務(wù)器會省略 Content-Length 首部,或 者包含錯誤的長度指示,這樣就要依賴服務(wù)器發(fā)出的連接關(guān)閉來說明數(shù) 據(jù)的真實末尾。
客戶端或代理收到一條隨連接關(guān)閉而結(jié)束的 HTTP 響應(yīng),且實際傳輸?shù)?實體長度與 Content-Length 并不匹配(或沒有 Content-Length) 時,接收端就應(yīng)該質(zhì)疑長度的正確性。
如果接收端是個緩存代理,接收端就不應(yīng)該緩存這條響應(yīng)(以降低今后 將潛在的錯誤報文混合起來的可能)。代理應(yīng)該將有問題的報文原封不 動地轉(zhuǎn)發(fā)出去,而不應(yīng)該試圖去“校正”Content-Length,以維護語義 的透明性。
連接關(guān)閉容限、重試以及冪等性
即使在非錯誤情況下,連接也可以在任意時刻關(guān)閉。HTTP 應(yīng)用程序要 做好正確處理非預(yù)期關(guān)閉的準(zhǔn)備。如果在客戶端執(zhí)行事務(wù)的過程中,傳 輸連接關(guān)閉了,那么,除非事務(wù)處理會帶來一些副作用,否則客戶端就 應(yīng)該重新打開連接,并重試一次。對管道化連接來說,這種情況更加嚴(yán) 重一些??蛻舳丝梢詫⒋罅空埱蠓湃腙犃兄信抨牐炊朔?wù)器可以關(guān) 閉連接,這樣就會留下大量未處理的請求,需要重新調(diào)度。
副作用是很重要的問題。如果在發(fā)送出一些請求數(shù)據(jù)之后,收到返回結(jié) 果之前,連接關(guān)閉了,客戶端就無法百分之百地確定服務(wù)器端實際激活 了多少事務(wù)。有些事務(wù),比如 GET 一個靜態(tài)的 HTML 頁面,可以反復(fù) 執(zhí)行多次,也不會有什么變化。而其他一些事務(wù),比如向一個在線書店 POST 一張訂單,就不能重復(fù)執(zhí)行,不然會有下多張訂單的危險。
如果一個事務(wù),不管是執(zhí)行一次還是很多次,得到的結(jié)果都相同,這個 事務(wù)就是冪等的。實現(xiàn)者們可以認(rèn)為 GET、HEAD、PUT、DELETE、 TRACE 和 OPTIONS 方法都共享這一特性。(基于 GET 構(gòu)建動態(tài)表單的管理者們要確保這些表單是冪等的。)客戶端不應(yīng)該以管道化方 式傳送非冪等請求(比如 POST)。否則,傳輸連接的過早終止就會造 成一些不確定的后果。要發(fā)送一條非冪等請求,就需要等待來自前一條 請求的響應(yīng)狀態(tài)。
盡管用戶 Agent 代理可能會讓操作員來選擇是否對請求進行重試,但一 定不能自動重試非冪等方法或序列。比如,大多數(shù)瀏覽器都會在重載一 個緩存的 POST 響應(yīng)時提供一個對話框,詢問用戶是否希望再次發(fā)起事 務(wù)處理。
正常關(guān)閉連接
如下圖所示,TCP 連接是雙向的。TCP 連接的每一端都有一個輸入 隊列和一個輸出隊列,用于數(shù)據(jù)的讀或?qū)憽7湃胍欢溯敵鲫犃兄械臄?shù)據(jù) 最終會出現(xiàn)在另一端的輸入隊列中。
[圖片上傳失敗...(image-b48ad7-1607570892332)]
1. 完全關(guān)閉與半關(guān)閉
應(yīng)用程序可以關(guān)閉 TCP 輸入和輸出信道中的任意一個,或者將兩 者都關(guān)閉了。套接字調(diào)用 close() 會將 TCP 連接的輸入和輸出信 道都關(guān)閉了。這被稱作“完全關(guān)閉”,如圖 a 所示。還可以用套 接字調(diào)用 shutdown() 單獨關(guān)閉輸入或輸出信道。這被稱為“半關(guān) 閉”,如圖 b 所示。
[圖片上傳失敗...(image-11ca89-1607570892332)]
2. TCP關(guān)閉及重置錯誤
簡單的 HTTP 應(yīng)用程序可以只使用完全關(guān)閉。但當(dāng)應(yīng)用程序開始與 很多其他類型的 HTTP 客戶端、服務(wù)器和代理進行對話且開始使用 管道化持久連接時,使用半關(guān)閉來防止對等實體收到非預(yù)期的寫入 錯誤就變得很重要了。
總之,關(guān)閉連接的輸出信道總是很安全的。連接另一端的對等實體 會在從其緩沖區(qū)中讀出所有數(shù)據(jù)之后收到一條通知,說明流結(jié)束 了,這樣它就知道你將連接關(guān)閉了。
關(guān)閉連接的輸入信道比較危險,除非你知道另一端不打算再發(fā)送其 他數(shù)據(jù)了。如果另一端向你已關(guān)閉的輸入信道發(fā)送數(shù)據(jù),操作系統(tǒng) 就會向另一端的機器回送一條 TCP“連接被對端重置”的報文,如下圖所示。大部分操作系統(tǒng)都會將這種情況作為很嚴(yán)重的錯誤來處 理,刪除對端還未讀取的所有緩存數(shù)據(jù)。對管道化連接來說,這是 非常糟糕的事情。[圖片上傳失敗...(image-5026-1607570892332)]
比如你已經(jīng)在一條持久連接上發(fā)送了 10 條管道式請求了,響應(yīng)也 已經(jīng)收到了,正在操作系統(tǒng)的緩沖區(qū)中存著呢(但應(yīng)用程序還未將 其讀走)?,F(xiàn)在,假設(shè)你發(fā)送了第 11 條請求,但服務(wù)器認(rèn)為你使 用這條連接的時間已經(jīng)夠長了,決定將其關(guān)閉。那么你的第 11 條 請求就會被發(fā)送到一條已關(guān)閉的連接上去,并會向你回送一條重置 信息。這個重置信息會清空你的輸入緩沖區(qū)。
當(dāng)你最終要去讀取數(shù)據(jù)的時候,會得到一個連接被對端重置的錯 誤,已緩存的未讀響應(yīng)數(shù)據(jù)都丟失了,盡管其中的大部分都已經(jīng)成 功抵達你的機器了。
正常關(guān)閉
HTTP 規(guī)范建議,當(dāng)客戶端或服務(wù)器突然要關(guān)閉一條連接時,應(yīng) 該“正常地關(guān)閉傳輸連接”,但它并沒有說明應(yīng)該如何去做。
總之,實現(xiàn)正常關(guān)閉的應(yīng)用程序首先應(yīng)該關(guān)閉它們的輸出信道,然 后等待連接另一端的對等實體關(guān)閉它的輸出信道。當(dāng)兩端都告訴對 方它們不會再發(fā)送任何數(shù)據(jù)(比如關(guān)閉輸出信道)之后,連接就會 被完全關(guān)閉,而不會有重置的危險。
但不幸的是,無法確保對等實體會實現(xiàn)半關(guān)閉,或?qū)ζ溥M行檢查。 因此,想要正常關(guān)閉連接的應(yīng)用程序應(yīng)該先半關(guān)閉其輸出信道,然 后周期性地檢查其輸入信道的狀態(tài)(查找數(shù)據(jù),或流的末尾)。如 果在一定的時間區(qū)間內(nèi)對端沒有關(guān)閉輸入信道,應(yīng)用程序可以強制 關(guān)閉連接,以節(jié)省資源。