長(zhǎng)鏈接相關(guān)淺析
定義
長(zhǎng)連接,指在一個(gè)連接上可以連續(xù)發(fā)送多個(gè)數(shù)據(jù)包,在連接保持期間,如果沒(méi)有數(shù)據(jù)包發(fā)送,需要雙方發(fā)鏈路檢測(cè)包。
http長(zhǎng)鏈接
HTTP協(xié)議本身是應(yīng)用層協(xié)議,其長(zhǎng)連接和短連接本質(zhì)上是TCP長(zhǎng)連接和短連接。 HTTP1.1規(guī)定了默認(rèn)保持長(zhǎng)連接(HTTP persistent connection ,也有翻譯為持久連接),數(shù)據(jù)傳輸完成了保持TCP連接不斷開(kāi)(不發(fā)RST包、不四次握手),等待在同域名下繼續(xù)用這個(gè)通道傳輸數(shù)據(jù)。
http協(xié)議里的keep-alive
http1.1 協(xié)議里增加了 keepalive的支持, 并且默認(rèn)開(kāi)啟。
客戶端和服務(wù)端在建立連接并完成request后并不會(huì)立即斷開(kāi)TCP連接,而是在下次request來(lái)臨時(shí)復(fù)用這次TCP連接。但是這里也必須要有TCP連接的timeout時(shí)間限制。不然會(huì)造成服務(wù)端端口被長(zhǎng)期占用釋放不了。

客戶端和服務(wù)端在建立連接并完成request后并不會(huì)立即斷開(kāi)TCP連接,而是在下次request來(lái)臨時(shí)復(fù)用這次TCP連接。但是這里也必須要有TCP連接的timeout時(shí)間限制。不然會(huì)造成服務(wù)端端口被長(zhǎng)期占用釋放不了。
對(duì)于不適用keepalive的request來(lái)說(shuō),不管是客戶端還是服務(wù)端都是通過(guò)TCP的鏈接的斷開(kāi)知道request的結(jié)束(TCP 揮手時(shí)會(huì)check 數(shù)據(jù)包的 seq, 保證數(shù)據(jù)完整性)。 支持keepalive后,如何知道request結(jié)束了呢?
在Http1.1的版本里, 解決方案是request 和reponse里使用contentLength來(lái)幫助確認(rèn)是否收到全部數(shù)據(jù)。
動(dòng)態(tài)生成的文件沒(méi)有Content-Length,服務(wù)器是不可能預(yù)先知道內(nèi)容大小,這時(shí)就可以使用Transfer-Encoding:chunk模式來(lái)傳輸數(shù)據(jù)了,即如果要一邊產(chǎn)生數(shù)據(jù),一邊發(fā)給客戶端,服務(wù)器就需要使用"Transfer-Encoding: chunked"這樣的方式來(lái)代替Content-Length。這時(shí)候就要根據(jù)chunked編碼來(lái)判斷,chunked編碼的數(shù)據(jù)在最后有一個(gè)長(zhǎng)度為0chunked塊,表明本次傳輸數(shù)據(jù)結(jié)束.
管道機(jī)制(Pipelining)

HTTP Pipelining是把多個(gè)HTTP請(qǐng)求放到一個(gè)TCP連接中一一發(fā)送,而在發(fā)送過(guò)程中不需要等待服務(wù)器對(duì)前一個(gè)請(qǐng)求的響應(yīng);只不過(guò),客戶端還是要按照發(fā)送請(qǐng)求的順序來(lái)接收響應(yīng)。但就像在超市收銀臺(tái)或者銀行柜臺(tái)排隊(duì)時(shí)一樣,你并不知道前面的顧客是干脆利索的還是會(huì)跟收銀員/柜員磨蹭到世界末日。不管怎么說(shuō),服務(wù)器(即收銀員/柜員)是要按照順序處理請(qǐng)求的,如果前一個(gè)請(qǐng)求非常耗時(shí)(顧客磨蹭),那么后續(xù)請(qǐng)求都會(huì)受到影響,這就是所謂的線頭阻塞(Head of line blocking)。
HTTP2.0 新增了多路復(fù)用的技術(shù),通過(guò)在應(yīng)用層http協(xié)議和傳輸層tcp協(xié)議之間增加一個(gè)二進(jìn)制分幀層,將請(qǐng)求分為不同的幀,每一幀會(huì)標(biāo)識(shí)出該幀屬于哪個(gè)流,流是多個(gè)幀組成的數(shù)據(jù)流。所謂多路復(fù)用,即在一個(gè)TCP連接中存在多個(gè)流,即可以同時(shí)發(fā)送多個(gè)請(qǐng)求,對(duì)端可以通過(guò)幀中的表示知道該幀屬于哪個(gè)請(qǐng)求。在客戶端,這些幀亂序發(fā)送,到對(duì)端后再根據(jù)每個(gè)幀首部的流標(biāo)識(shí)符重新組裝。通過(guò)該技術(shù),可以避免HTTP舊版本的隊(duì)頭阻塞問(wèn)題,極大提高傳輸性能。
WebSocket
WebSocket是HTML5新增的協(xié)議,它的目的是在瀏覽器和服務(wù)器之間建立一個(gè)不受限的雙向通信的通道,比如說(shuō),服務(wù)器可以在任意時(shí)刻發(fā)送消息給瀏覽器。
為什么傳統(tǒng)的HTTP協(xié)議不能做到WebSocket實(shí)現(xiàn)的功能?這是因?yàn)镠TTP協(xié)議是一個(gè)請(qǐng)求-響應(yīng)協(xié)議,請(qǐng)求必須先由瀏覽器發(fā)給服務(wù)器,服務(wù)器才能響應(yīng)這個(gè)請(qǐng)求,再把數(shù)據(jù)發(fā)送給瀏覽器。換句話說(shuō),瀏覽器不主動(dòng)請(qǐng)求,服務(wù)器是沒(méi)法主動(dòng)發(fā)數(shù)據(jù)給瀏覽器的。
這樣一來(lái),要在瀏覽器中搞一個(gè)實(shí)時(shí)聊天,在線炒股(不鼓勵(lì)),或者在線多人游戲的話就沒(méi)法實(shí)現(xiàn)了,只能借助Flash這些插件。
也有人說(shuō),HTTP協(xié)議其實(shí)也能實(shí)現(xiàn)啊,比如用輪詢或者Comet。輪詢是指瀏覽器通過(guò)JavaScript啟動(dòng)一個(gè)定時(shí)器,然后以固定的間隔給服務(wù)器發(fā)請(qǐng)求,詢問(wèn)服務(wù)器有沒(méi)有新消息。這個(gè)機(jī)制的缺點(diǎn)一是實(shí)時(shí)性不夠,二是頻繁的請(qǐng)求會(huì)給服務(wù)器帶來(lái)極大的壓力。
Comet本質(zhì)上也是輪詢,但是在沒(méi)有消息的情況下,服務(wù)器先拖一段時(shí)間,等到有消息了再回復(fù)。這個(gè)機(jī)制暫時(shí)地解決了實(shí)時(shí)性問(wèn)題,但是它帶來(lái)了新的問(wèn)題:以多線程模式運(yùn)行的服務(wù)器會(huì)讓大部分線程大部分時(shí)間都處于掛起狀態(tài),極大地浪費(fèi)服務(wù)器資源。另外,一個(gè)HTTP連接在長(zhǎng)時(shí)間沒(méi)有數(shù)據(jù)傳輸?shù)那闆r下,鏈路上的任何一個(gè)網(wǎng)關(guān)都可能關(guān)閉這個(gè)連接,而網(wǎng)關(guān)是我們不可控的,這就要求Comet連接必須定期發(fā)一些ping數(shù)據(jù)表示連接“正常工作”。
以上兩種機(jī)制都治標(biāo)不治本,所以,HTML5推出了WebSocket標(biāo)準(zhǔn),讓瀏覽器和服務(wù)器之間可以建立無(wú)限制的全雙工通信,任何一方都可以主動(dòng)發(fā)消息給對(duì)方。
WebSocket協(xié)議
WebSocket并不是全新的協(xié)議,而是利用了HTTP協(xié)議來(lái)建立連接。我們來(lái)看看WebSocket連接是如何創(chuàng)建的。
首先,WebSocket連接必須由瀏覽器發(fā)起,因?yàn)檎?qǐng)求協(xié)議是一個(gè)標(biāo)準(zhǔn)的HTTP請(qǐng)求,格式如下:
GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13
該請(qǐng)求和普通的HTTP請(qǐng)求有幾點(diǎn)不同:
- GET請(qǐng)求的地址不是類(lèi)似
/path/,而是以ws://開(kāi)頭的地址; - 請(qǐng)求頭
Upgrade: websocket和Connection: Upgrade表示這個(gè)連接將要被轉(zhuǎn)換為WebSocket連接; -
Sec-WebSocket-Key是用于標(biāo)識(shí)這個(gè)連接,并非用于加密數(shù)據(jù); -
Sec-WebSocket-Version指定了WebSocket的協(xié)議版本。
隨后,服務(wù)器如果接受該請(qǐng)求,就會(huì)返回如下響應(yīng):
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string
該響應(yīng)代碼101表示本次連接的HTTP協(xié)議即將被更改,更改后的協(xié)議就是Upgrade: websocket指定的WebSocket協(xié)議。
版本號(hào)和子協(xié)議規(guī)定了雙方能理解的數(shù)據(jù)格式,以及是否支持壓縮等等。如果僅使用WebSocket的API,就不需要關(guān)心這些。
現(xiàn)在,一個(gè)WebSocket連接就建立成功,瀏覽器和服務(wù)器就可以隨時(shí)主動(dòng)發(fā)送消息給對(duì)方。消息有兩種,一種是文本,一種是二進(jìn)制數(shù)據(jù)。通常,我們可以發(fā)送JSON格式的文本,這樣,在瀏覽器處理起來(lái)就十分容易。
為什么WebSocket連接可以實(shí)現(xiàn)全雙工通信而HTTP連接不行呢?實(shí)際上HTTP協(xié)議是建立在TCP協(xié)議之上的,TCP協(xié)議本身就實(shí)現(xiàn)了全雙工通信,但是HTTP協(xié)議的請(qǐng)求-應(yīng)答機(jī)制限制了全雙工通信。WebSocket連接建立以后,其實(shí)只是簡(jiǎn)單規(guī)定了一下:接下來(lái),咱們通信就不使用HTTP協(xié)議了,直接互相發(fā)數(shù)據(jù)吧。
安全的WebSocket連接機(jī)制和HTTPS類(lèi)似。首先,瀏覽器用wss://xxx創(chuàng)建WebSocket連接時(shí),會(huì)先通過(guò)HTTPS創(chuàng)建安全的連接,然后,該HTTPS連接升級(jí)為WebSocket連接,底層通信走的仍然是安全的SSL/TLS協(xié)議。
參考資料:
廖雪峰websocket:https://www.liaoxuefeng.com/wiki/1022910821149312/1103303693824096