圖解 HTTP 連接管理
HTTP 協(xié)議對(duì)我們程序員來說太重要了,不管你使用的是哪個(gè)語言,HTTP 都是你需要知道的重點(diǎn)。
這不是一篇簡(jiǎn)單介紹 HTTP 基本概念的文章,如果你對(duì) HTTP 基本概念不是很熟悉,推薦你去讀 cxuan 寫的關(guān)于 HTTP 基礎(chǔ)文章 - 《看完這篇HTTP,跟面試官扯皮就沒問題了》。
所以我們假定在做的各位對(duì) HTTP 有一定的了解和認(rèn)識(shí)。
下面開始我們這篇文章。
搭載 HTTP 的 TCP
我們大家都知道,HTTP 這個(gè)應(yīng)用層協(xié)議是以 TCP 為基礎(chǔ)來傳輸數(shù)據(jù)的。當(dāng)你想訪問一個(gè)資源(資源在網(wǎng)絡(luò)中就是一個(gè) URL)時(shí),你需要先解析這個(gè)資源的 IP 地址和端口號(hào),從而和這個(gè) IP 和端口號(hào)所在的服務(wù)器建立 TCP 連接,然后 HTTP 客戶端發(fā)起服務(wù)請(qǐng)求(GET)報(bào)文,服務(wù)器對(duì)服務(wù)器的請(qǐng)求報(bào)文做出響應(yīng),等到不需要交換報(bào)文時(shí),客戶端會(huì)關(guān)閉連接,下面我用圖很好的說明了這個(gè)過程。
上面這幅圖很好的說明了 HTTP 從建立連接開始 -> 發(fā)起請(qǐng)求報(bào)文 -> 關(guān)閉連接的全過程,但是上面這個(gè)過程還忽略了一個(gè)很重要的點(diǎn),那就是TCP 建立連接的過程。
TCP 建立連接需要經(jīng)過三次握手,交換三個(gè)報(bào)文,我相信大家都對(duì)這個(gè)過程了然于胸了,如果你還不清楚 TCP 建立連接的過程,可以先閱讀 cxuan 的這篇文章 TCP 連接管理。
由于 HTTP 位于 TCP 的上層,所以 HTTP 的請(qǐng)求 -> 響應(yīng)過程的時(shí)效性(性能)很大程度上取決于底層 TCP 的性能,只有在了解了 TCP 連接的性能之后,才可以更好的理解 HTTP 連接的性能,從而才能夠?qū)崿F(xiàn)高性能的 HTTP 應(yīng)用程序。
我們通常把一次完整的請(qǐng)求 -> 相應(yīng)過程稱之為 HTTP 事務(wù)。
所以我后面一般會(huì)寫為 HTTP 事務(wù),你理解怎么回事就好。
我們接下來的重點(diǎn)要先從 TCP 的性能入手。
HTTP 時(shí)延損耗
再來回顧一下上面的 HTTP 事務(wù)的過程,你覺得有哪幾個(gè)過程會(huì)造成 HTTP 事務(wù)時(shí)延呢?如下圖所示:
從圖中可以看出,主要是有下面這幾個(gè)因素影響 HTTP 事務(wù)的時(shí)延:
- 客戶端會(huì)根據(jù) URL 確定服務(wù)器的 IP 和端口號(hào),這里主要是 DNS 把域名轉(zhuǎn)換為 IP 地址的時(shí)延,DNS 會(huì)發(fā)起 DNS 查詢,查詢服務(wù)器的 IP 地址。
- 第二個(gè)時(shí)延是 TCP 建立連接時(shí)會(huì)由客戶端向服務(wù)器發(fā)送連接請(qǐng)求報(bào)文,并等待服務(wù)器回送響應(yīng)報(bào)文的時(shí)延。每條新的 TCP 連接建立都會(huì)有建立時(shí)延。
- 一旦連接建立后,客戶端就會(huì)向服務(wù)器請(qǐng)求數(shù)據(jù),這個(gè)時(shí)延主要是服務(wù)器從 TCP 連接中讀取請(qǐng)求報(bào)文,并對(duì)請(qǐng)求進(jìn)行處理的時(shí)延。
- 服務(wù)器會(huì)向客戶端傳輸響應(yīng)報(bào)文的時(shí)延。
- 最后一個(gè)時(shí)延是 TCP 連接關(guān)閉的時(shí)延。
其中最后一點(diǎn)的優(yōu)化也是本文想要討論的一個(gè)重點(diǎn)。
HTTP 連接管理
試想一個(gè)問題,假設(shè)一個(gè)頁面有五個(gè)資源(元素),每個(gè)資源都需要客戶端打開一個(gè) TCP 連接、獲取資源、斷開連接,而且每個(gè)連接都是串行打開的,如下圖所示:
串行的意思就是,這五個(gè)連接必須是有先后順序,不會(huì)出現(xiàn)同時(shí)有兩個(gè)以上的連接同時(shí)打開的情況。
上面五個(gè)資源就需要打開五條連接,資源少還好說,CPU 能夠處理,如果頁面資源達(dá)到上百或者更多的時(shí)候呢?每個(gè)資源還需要單獨(dú)再打開一條連接嗎?這樣顯然會(huì)急劇增加 CPU 的處理壓力,造成大量的時(shí)延,顯然是沒有必要的。
串行還有一個(gè)缺點(diǎn)就是,有些瀏覽器在對(duì)象加載完畢之前是無法知道對(duì)象的尺寸(size)的,并且瀏覽器需要對(duì)象尺寸信息來將他們放在屏幕中合理的位置上,所以在加載了足夠多的對(duì)象之前,屏幕是不會(huì)顯示任何內(nèi)容的,這就會(huì)造成,其實(shí)對(duì)象一直在加載,但是我們以為瀏覽器卡住了。
所以,有沒有能夠優(yōu)化 HTTP 性能的方式呢?這個(gè)問題問得好,當(dāng)然是有的。
(1) 并行連接
這是一種最常見的,也是最容易想到的一種連接方式,HTTP 允許客戶端打開多條連接,并行執(zhí)行多個(gè) HTTP 事務(wù),加入并行連接后,整個(gè) HTTP 事務(wù)的請(qǐng)求過程是這樣的。
采用并行連接這種方式會(huì)克服單條連接的空載時(shí)間和帶寬限制,因?yàn)槊總€(gè)事務(wù)都有連接,因此時(shí)延能夠重疊起來,會(huì)提高頁面的加載速度。
但是并行連接并不一定快,如果帶寬不夠的情況下,甚至頁面響應(yīng)速度還不如串行連接,因?yàn)樵诓⑿羞B接中,每個(gè)連接都會(huì)去競(jìng)爭(zhēng)使用有效的帶寬,每個(gè)對(duì)象都會(huì)以較慢的速度加載,有可能連接 1 加載了 95% ,連接 2 占用帶寬加載了 80%,連接 3 ,連接 4 。。。。。。雖然每個(gè)對(duì)象都在加載,但是頁面上卻沒有任何響應(yīng)。
而且,打開大量連接會(huì)消耗很多內(nèi)存資源,從而出現(xiàn)性能問題,上面討論的就五個(gè)連接,這個(gè)還比較少,復(fù)雜的 web 頁面有可能會(huì)有數(shù)十甚至數(shù)百個(gè)內(nèi)嵌對(duì)象,也就是說,客戶端可以打開數(shù)百個(gè)連接,而且有許多的客戶端同時(shí)發(fā)出申請(qǐng),這樣很容易會(huì)成為性能瓶頸。
這樣看來,并行連接并不一定"快",實(shí)際上并行連接并沒有加快頁面的傳輸速度,并行連接也只是造成了一種假象,這是一切并行的通病。
(2) 持久連接
Web 客戶端通常會(huì)打開到同一個(gè)站點(diǎn)的連接,而且初始化了對(duì)某服務(wù)器請(qǐng)求的應(yīng)用程序很可能會(huì)在不久的將來對(duì)這臺(tái)服務(wù)器發(fā)起更多的請(qǐng)求,比如獲取更多的圖片。這種特性被稱為站點(diǎn)局部性(site locality)。
因此,HTTP 1.1 以及 HTTP1.0 的允許 HTTP 在執(zhí)行完一次事務(wù)之后將連接繼續(xù)保持在打開狀態(tài),這個(gè)打開狀態(tài)其實(shí)指的就是 TCP 的打開狀態(tài),以便于下一次的 HTTP 事務(wù)能夠復(fù)用這條連接。
在一次 HTTP 事務(wù)結(jié)束之后仍舊保持打開狀態(tài)的 TCP 連接被稱為持久連接。
非持久連接會(huì)在每個(gè)事務(wù)結(jié)束之后關(guān)閉,相對(duì)的,持久連接會(huì)在每個(gè)事務(wù)結(jié)束之后繼續(xù)保持打開狀態(tài)。持久連接會(huì)在不同事務(wù)之間保持打開狀態(tài),直到客戶端或者服務(wù)器決定將其關(guān)閉為止。
長(zhǎng)連接也是有缺點(diǎn)的,如果單一客戶端發(fā)起請(qǐng)求數(shù)量不是很頻繁,但是連接的客戶端卻有很多的話,服務(wù)器早晚會(huì)有崩潰的時(shí)候。
持久連接一般有兩種選型方式,一種是 HTTP 1.0 + keep-alive ;一種是 HTTP 1.1 + persistent。
HTTP 1.1 之前的版本默認(rèn)連接都是非持久連接,如果想要在舊版本的 HTTP 上使用持久連接,需要指定 Connection 的值為 Keep-Alive。
HTTP 1.1 版本都是持久性連接,如果想要斷開連接時(shí),需要指定 Connection 的值為 close,這也是我們上面說的兩種選型方式的版本因素。
下面是使用了持久連接之后的 HTTP 事務(wù)與使用串行 HTTP 事務(wù)連接的對(duì)比圖:
這張圖對(duì)比了 HTTP 事務(wù)在串行連接上和持久連接的時(shí)間損耗圖,可以看到,HTTP 持久連接省去了連接打開 - 連接關(guān)閉的時(shí)間,所以在時(shí)間損耗上有所縮減。
在持久性連接中,還有一個(gè)非常有意思的地方,就是 Connection 選項(xiàng),Connection 是一個(gè)通用選項(xiàng),也就是客戶端和服務(wù)端都具有的一個(gè)標(biāo)頭,下面是一個(gè)具有持久性連接的客戶端和服務(wù)端的請(qǐng)求-響應(yīng)圖:
從這張圖可以看出,持久連接主要使用的就是 Connection 標(biāo)頭,這也就意味著,Connection 就是持久性連接的實(shí)現(xiàn)方式。所以下面我們主要討論一下 Connection 這個(gè)大佬。
Connection 標(biāo)頭
Connection 標(biāo)頭具有兩種作用:
- 和 Upgrade 一起使用進(jìn)行協(xié)議升級(jí)
- 管理持久連接
(1) 和 Upgrade 一起使用進(jìn)行協(xié)議升級(jí)
HTTP 提供了一種特殊的機(jī)制,這一機(jī)制允許將一個(gè)已建立的連接升級(jí)成新的協(xié)議,一般寫法如下:
- GET /index.html HTTP/1.1
- Host: www.example.com
- Connection: upgrade
- Upgrade: example/1, foo/2
HTTP/2 明確禁止使用此機(jī)制,這個(gè)機(jī)制只屬于HTTP/1.1。
也就是說,客戶端發(fā)起 Connection:upgrade 就表明這是一個(gè)連接升級(jí)的請(qǐng)求,如果服務(wù)器決定升級(jí)這次連接,就會(huì)返回一個(gè) 101 Switching Protocols 響應(yīng)狀態(tài)碼,和一個(gè)要切換到的協(xié)議的頭部字段 Upgrade。如果服務(wù)器沒有(或者不能)升級(jí)這次連接,它會(huì)忽略客戶端發(fā)送的 Upgrade 頭部字段,返回一個(gè)常規(guī)的響應(yīng):例如返回 200。
(2) 管理持久連接
我們上面說持久連接有兩種方式,一種是 HTTP 1.0 + Keep-Alive ;一種是 HTTP 1.1 + persistent。
- Connection: Keep-Alive
- Keep-Alive: timeout=10,max=500
在 HTTP 1.0 + Keep-Alive 這種方式下,客戶端可以通過包含 Connection:Keep-Alive 首部請(qǐng)求將一條連接保持在打開狀態(tài)。
這里需要注意⚠️一點(diǎn):Keep-Alive 首部只是將請(qǐng)求保持在活躍狀態(tài),發(fā)出 Keep-Alive 請(qǐng)求之后,客戶端和服務(wù)器不一定會(huì)同意進(jìn)行 Keep-Alive 會(huì)話。它們可以在任何時(shí)刻關(guān)閉空閑的 Keep-Alive 連接,并且客戶端和服務(wù)器可以限制 Keep-Alive 連接所處理事務(wù)的數(shù)量。
Keep-Alive 這個(gè)標(biāo)頭有下面幾種選項(xiàng):
- timeout:這個(gè)參數(shù)估計(jì)了服務(wù)器希望將連接保持在活躍狀態(tài)的時(shí)間。
- max :這個(gè)參數(shù)是跟在 timeout 參數(shù)后面的,它表示的是服務(wù)器還能夠?yàn)槎嗌賯€(gè)事務(wù)打開持久連接。
Keep-Alive 這個(gè)首部是可選的,但是只有在提供 Connection:Keep-Alive 時(shí)才能使用它。
Keep-Alive 的使用有一定限制,下面我們就來討論一下 Keep-Alive 的使用限制問題。
Keep-Alive 使用限制和規(guī)則
- 在 HTTP/1.0 中,Keep-Alive 并不是默認(rèn)使用的,客戶端必須發(fā)送一個(gè) Connection:Keep-Alive 請(qǐng)求首部來激活 Keep-Alive 連接。
- 通過檢測(cè)響應(yīng)中是否含有 Connection:Keep-Alive 首部字段,客戶端可以判斷服務(wù)器是否在發(fā)出響應(yīng)之后關(guān)閉連接。
- 代理和網(wǎng)管必須執(zhí)行 Connection 首部規(guī)則,它們必須在將報(bào)文轉(zhuǎn)發(fā)出去或者將緩存之前,刪除 Connection 首部中的首部字段和 Connection 首部自身,因?yàn)?Connection 是一個(gè) Hop-by-Hop 首部,這個(gè)首部說的是只對(duì)單次轉(zhuǎn)發(fā)有效,會(huì)因?yàn)檗D(zhuǎn)發(fā)給緩存/代理服務(wù)器而失效。
- 嚴(yán)格來說,不應(yīng)該與無法確定是否支持 Connection 首部的代理服務(wù)器建立 Keep-Alive 連接,以防止出現(xiàn)啞代理問題,啞代理問題我們下面會(huì)說。
Keep-Alive 和啞代理問題
這里我先解釋一下什么是代理服務(wù)器,然后再說啞代理問題。
(1) 什么是代理服務(wù)器?
代理服務(wù)器就是代替客戶端去獲取網(wǎng)絡(luò)信息的一種媒介,通俗一點(diǎn)就是網(wǎng)絡(luò)信息的中轉(zhuǎn)站。
(2) 為什么我們需要代理服務(wù)器?
最廣泛的一種用處是我們需要使用代理服務(wù)器來替我們?cè)L問一些我們客戶端無法直接訪問的網(wǎng)站。除此之外,代理服務(wù)器還有很多功能,比如緩存功能,可以降低費(fèi)用,節(jié)省帶寬;對(duì)信息的實(shí)時(shí)監(jiān)控和過濾,代理服務(wù)器相對(duì)于目標(biāo)服務(wù)器(最終獲取信息的服務(wù)器)來說,也是一個(gè)客戶端,它能夠獲取服務(wù)器提供的信息,代理服務(wù)器相對(duì)于客戶端來說,它是一個(gè)服務(wù)器,由它來決定提供哪些信息給客戶端,以此來達(dá)到監(jiān)控和過濾的功能。
啞代理問題出現(xiàn)就出現(xiàn)在代理服務(wù)器上,再細(xì)致一點(diǎn)就是出現(xiàn)在不能識(shí)別 Connection 首部的代理服務(wù)器,而且不知道在發(fā)出請(qǐng)求之后會(huì)刪除 Connection 首部的代理服務(wù)器。
假設(shè)一個(gè) Web 客戶端正在通過一個(gè)啞代理服務(wù)器與 Web 服務(wù)器進(jìn)行對(duì)話,如下圖所示:
來解釋一下上面這幅圖:
- 首先,Web 客戶端向代理發(fā)送了一條報(bào)文,其中包含了 Connection: Keep-Alive 首部,希望在這次 HTTP 事務(wù)之后繼續(xù)保持活躍狀態(tài),然后客戶端等待響應(yīng),以確定對(duì)方是否允許持久連接。
- 啞代理(這里先界定為啞代理是不妥的,我們往往先看做的事,再給這件事定性,現(xiàn)在這個(gè)服務(wù)器還沒做出啞代理行為呢,就給他定性了)收到了這條 HTTP 請(qǐng)求,但它不理解 Connection 首部,它也不知道 Keep-Alive 是什么意思,因此只是沿著轉(zhuǎn)發(fā)鏈路將報(bào)文發(fā)送給服務(wù)器,但 Connection 首部是個(gè) Hop-by-Hop 首部,只適用于單條鏈路傳輸,所以這個(gè)代理服務(wù)器不應(yīng)該再將其發(fā)送給服務(wù)器了,但是它還是發(fā)送了,后面就會(huì)發(fā)生一些難頂?shù)氖虑椤?/li>
- 經(jīng)過轉(zhuǎn)發(fā)的 HTTP 請(qǐng)求到達(dá)服務(wù)器后,會(huì)誤以為對(duì)方希望保持 Keep-Alive 持久連接,經(jīng)過評(píng)估后,服務(wù)器作出響應(yīng),它同意進(jìn)行 Keep-Alive 對(duì)話,所以它回送了一個(gè) Connection:Keep-Alive 響應(yīng)并到達(dá)了啞代理服務(wù)器。
- 啞代理服務(wù)器會(huì)直接將響應(yīng)發(fā)送給客戶端,客戶端收到響應(yīng)后,就知道服務(wù)器可以使用持久連接。然而,此時(shí)客戶端和服務(wù)器都知道要使用 Keep-Alive 持久連接,但是啞代理服務(wù)器卻對(duì) Keep-Alive 一無所知。
- 由于代理對(duì) Keep-Alive 一無所知,所以會(huì)收到的所有數(shù)據(jù)都會(huì)發(fā)送給客戶端,然后等待服務(wù)器關(guān)閉連接,但是代理服務(wù)器卻認(rèn)為應(yīng)該保持打開狀態(tài),所以不會(huì)去關(guān)閉連接。這樣,啞代理服務(wù)器就一直掛在那里等待連接的關(guān)閉。
- 等到客戶端發(fā)送下一個(gè) HTTP 事務(wù)后,啞代理會(huì)直接忽視新的 HTTP 事務(wù),因?yàn)樗⒉徽J(rèn)為一條連接上還會(huì)有其他請(qǐng)求的到來,所以會(huì)直接忽略新的請(qǐng)求。
這就是 Keep-Alive 的啞代理。
那么如何解決這個(gè)問題呢?用 Proxy-Connection
Proxy-Connection 解決啞代理
網(wǎng)景公司提出了一種使用 Proxy-Connection 標(biāo)頭的辦法,首先瀏覽器會(huì)向代理發(fā)送 Proxy-Connection 擴(kuò)展首部,而不是官方支持的 Connection 首部。如果代理服務(wù)器是啞代理的話,它會(huì)直接將 Proxy-Connection 發(fā)送給服務(wù)器,而服務(wù)器收到 Proxy-Connection 的話,就會(huì)忽略這個(gè)首部,這樣不會(huì)帶來任何問題。如果是一個(gè)聰明的代理服務(wù)器,在收到 Proxy-Connection 的時(shí)候,就會(huì)直接將 Connection 替換掉 Proxy-Connection ,再發(fā)送給服務(wù)器。
HTTP/1.1 持久連接
HTTP/1.1 逐漸停止了對(duì) Keep-Alive 連接的支持,用一種名為 persistent connection 的改進(jìn)型設(shè)計(jì)取代了 Keep-Alive ,這種改進(jìn)型設(shè)計(jì)也是持久連接,不過比 HTTP/1.0 的工作機(jī)制更優(yōu)。
與 HTTP/1.0 的 Keep-Alive 連接不同,HTTP/1.1 在默認(rèn)情況下使用的就是持久連接。除非特別指明,否則 HTTP/1.1 會(huì)假定所有連接都是持久連接。如果想要在事務(wù)結(jié)束后關(guān)閉連接的話,就需要在報(bào)文中顯示添加一個(gè) Connection:close 首部。這是與以前的 HTTP 協(xié)議版本很重要的區(qū)別。
使用 persistent connection 也會(huì)有一些限制和規(guī)則:
- 首先,發(fā)送了 Connection: close 請(qǐng)求后,客戶端就無法在這條連接上發(fā)送更多的請(qǐng)求。這同時(shí)也可以說,如果客戶端不想發(fā)送其他請(qǐng)求,就可以使用 Connection:close 關(guān)閉連接。
- HTTP/1.1 的代理必須能夠分別管理客戶端和服務(wù)器的持久連接 ,每個(gè)持久連接都只適用于單次傳輸。
- 客戶端對(duì)任何服務(wù)器或者代理最好只維護(hù)兩條持久連接,以防止服務(wù)器過載。
- 只有實(shí)體部分的長(zhǎng)度和相應(yīng)的 Content-Length保持一致時(shí),或者使用分塊傳輸編碼的方式時(shí),連接才能保持長(zhǎng)久。
管道化連接
HTTP/1.1 允許在持久連接上使用請(qǐng)求管道。這是相對(duì)于 Keep-Alive 連接的又一個(gè)性能優(yōu)化。管道就是一個(gè)承載 HTTP 請(qǐng)求的載體,我們可以將多個(gè) HTTP 請(qǐng)求放入管道,這樣能夠降低網(wǎng)絡(luò)的環(huán)回時(shí)間,提升性能。下圖是使用串行連接、并行連接、管道化連接的示意圖:
使用管道化的連接也有幾處限制:
- 如果 HTTP 客戶端無法確認(rèn)連接是持久的,就不應(yīng)該使用管道。
- 必須按照與請(qǐng)求的相同順序回送 HTTP 響應(yīng),因?yàn)?HTTP 沒有序號(hào)這個(gè)概念,所以一旦響應(yīng)失序,就沒辦法將其與請(qǐng)求匹配起來了。
- HTTP 客戶端必須做好連接會(huì)在任何時(shí)刻關(guān)閉的準(zhǔn)備,還要準(zhǔn)備好重發(fā)所有未完成的管道化請(qǐng)求。
HTTP 關(guān)閉連接
所有 HTTP 客戶端、服務(wù)器或者代理都可以在任意時(shí)刻關(guān)閉一條 HTTP 傳輸連接。通常情況下會(huì)在一次響應(yīng)后關(guān)閉連接,但是保不準(zhǔn)也會(huì)在 HTTP 事務(wù)的過程中出現(xiàn)。
但是,服務(wù)器無法確定在關(guān)閉的那一刻,客戶端有沒有數(shù)據(jù)要發(fā)送,如果出現(xiàn)這種情況,客戶端就會(huì)在進(jìn)行數(shù)據(jù)傳輸?shù)倪^程中發(fā)生了寫入錯(cuò)誤。
即使在不出錯(cuò)的情況下,連接也可以在任意時(shí)刻關(guān)閉。如果在事務(wù)傳輸?shù)倪^程中出現(xiàn)了連接關(guān)閉情況,就需要重新打開連接進(jìn)行重試。如果是單條連接還好說,如果是管道化連接,就比較糟糕,因?yàn)楣艿阑B接會(huì)把大量的連接丟在管道中,此時(shí)如果服務(wù)器關(guān)閉,就會(huì)造成大量的連接未響應(yīng),需要重新調(diào)度。
如果一個(gè) HTTP 事務(wù)不管執(zhí)行一次還是執(zhí)行 n 次,它得到的結(jié)果始終是一樣的,那么我們就認(rèn)為這個(gè)事務(wù)是冪等的,一般 GET、HEAD、PUT、DELETE、TRACE 和 OPTIONS方法都認(rèn)為是冪等的??蛻舳瞬粦?yīng)該以管道化的方式發(fā)送任何非冪等請(qǐng)求,比如 POST,否則就會(huì)造成不確定的后果。
由于 HTTP 使用 TCP 作為傳輸層的協(xié)議,所以 HTTP 關(guān)閉連接其實(shí)還是 TCP 關(guān)閉連接的過程。
HTTP 關(guān)閉連接一共分為三種情況:完全關(guān)閉、半關(guān)閉和正常關(guān)閉。
應(yīng)用程序可以關(guān)閉 TCP 輸入和輸出信道中的任何一個(gè),或者將二者同時(shí)關(guān)閉。調(diào)用套接字 close() 方法會(huì)講輸入和輸出同時(shí)關(guān)閉,這就被稱為完全關(guān)閉。還可以調(diào)用套接字的 shutdown 方法單獨(dú)關(guān)閉輸入或者輸出信道,這被稱為半關(guān)閉。HTTP 規(guī)范建議當(dāng)客戶端和服務(wù)器突然需要關(guān)閉連接的時(shí)候,應(yīng)該正常關(guān)閉,但是它沒有說如何去做。