Websocket詳談

很多場景下的應(yīng)用對數(shù)據(jù)實時更新要求很高邑商。比如股票交易怜浅,數(shù)字資產(chǎn)交易闷煤,還有一些需要動態(tài)更新數(shù)據(jù)的大屏數(shù)據(jù)可視化應(yīng)用等等。在html5面世前涝滴,動態(tài)更新數(shù)據(jù)的做法大都是使用ajax輪詢來實現(xiàn)绣版,但是輪詢的效率低,而且非常浪費資源(因為必須不斷建立連接)歼疮。到目前websocket已經(jīng)很受大家喜愛杂抽,也逐步替代了輪詢的做法,使用websocket的場景也越來越多韩脏。下面就來詳細(xì)介紹:

WebSocket簡介

WebSocket 是 HTML5 新增的一種在單個 TCP 連接上進(jìn)行全雙工通訊的協(xié)議缩麸。誕生于2008年,在2011年成為國際標(biāo)準(zhǔn)≈杷兀現(xiàn)在新版的所有瀏覽器都已經(jīng)支持匙睹,但不兼容低版本的瀏覽器。

WebSocket的最大特點是:允許客戶端和服務(wù)器之間進(jìn)行全雙工通信济竹,以便任一方都可以通過建立的連接將數(shù)據(jù)推送到另一端痕檬,是真正的雙向平等對話,屬于服務(wù)器推送技術(shù)的一種送浊。

RFC6455 中定義了它的通信標(biāo)準(zhǔn)梦谜。

為什么需要 WebSocket ?

了解HTTP協(xié)議的童鞋應(yīng)該都知道HTTP協(xié)議有以下兩個突出的特性:

其一:HTTP協(xié)議的通信只能由客戶端發(fā)起,它無法實現(xiàn)服務(wù)器主動向客戶端推送消息(單向請求)唁桩。

其二:HTTP協(xié)議是一種無狀態(tài)的應(yīng)用層協(xié)議闭树,它采用的是請求/響應(yīng)模型。每次通信都需要攜帶驗證信息進(jìn)行身份校驗(耗時荒澡、耗資源报辱、效率低)。

WebSocket可以說是在HTTP的基礎(chǔ)上發(fā)明來的单山,改善了HTTP協(xié)議上面的兩個特性碍现。WebSocket只需要建立一次HTTP連接,就可以一直保持連接狀態(tài)(如果兩端長時間都沒有通信也是會被關(guān)閉連接的 - 后面會講到)米奸,此時已經(jīng)是從HTTP協(xié)議升級到了WebSocket協(xié)議昼接,后面的通信都是基于websocket協(xié)議。這相比于輪詢方式的不停建立連接顯然效率要大大提高悴晰。

WebSocket如何工作慢睡?

Web瀏覽器和服務(wù)器都必須支持 WebSocket 協(xié)議來建立和維護(hù)連接。由于 WebSocket 連接長期存在铡溪,與典型的 HTTP 連接不同漂辐,對服務(wù)器有重要的影響。

基于多線程或多進(jìn)程的服務(wù)器無法適用于 WebSocket佃却,因為它旨在打開連接者吁,盡可能快地處理請求,然后關(guān)閉連接饲帅。

客戶端簡單示例:

var ws = new WebSocket("ws://echo.websocket.org");
或者加密協(xié)議:
var ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function(evt) { 
  console.log("連接建立成功,可以開始通信了..."); 
  ws.send("Hello WebSocket!");
};

ws.onerror = function(evt) {
  console.log("連接出錯 ...");
};   

ws.onmessage = function(evt) {
  console.log( "收到服務(wù)端消息: " + evt.data);
  ws.close();
};

ws.onclose = function(evt) {
  console.log("關(guān)閉連接 ...");
};      

Websocket客戶端 API

1瘤泪、WebSocket 構(gòu)造函數(shù):

WebSocket 對象作為一個構(gòu)造函數(shù)灶泵,用于新建 WebSocket 實例。
var webSocket = new WebSocket('ws://localhost:8080');
執(zhí)行上面語句之后对途,客戶端就會與服務(wù)器進(jìn)行連接

2赦邻、webSocket.readyState

readyState屬性返回實例對象的當(dāng)前狀態(tài),共有四種:
CONNECTING:值為0实檀,表示正在連接惶洲。
OPEN:值為1,表示連接成功膳犹,可以通信了恬吕。
CLOSING:值為2,表示連接正在關(guān)閉须床。
CLOSED:值為3铐料,表示連接已經(jīng)關(guān)閉,或者打開連接失敗。

3钠惩、webSocket.bufferedAmount

bufferedAmount屬性柒凉,表示還有多少字節(jié)的二進(jìn)制數(shù)據(jù)沒有發(fā)送出去。它可以用來判斷發(fā)送是否結(jié)束
var data = new ArrayBuffer(10000000);
webSocket.send(data);

if (webSocket.bufferedAmount === 0) {
// 發(fā)送完畢
} else {
// 發(fā)送還沒結(jié)束
}

4篓跛、webSocket.onopen

onopen屬性膝捞,用于指定連接成功后的回調(diào)函數(shù)
webSocket.onopen = function () {
webSocket.send('Hello Server!');
}
webSocket.addEventListener('open', function (event) {
webSocket.send('Hello Server!');
});

5、webSocket.onclose

onclose屬性愧沟,用于指定連接關(guān)閉后的回調(diào)函數(shù)
webSocket.onclose = function(event) {
var code = event.code;
var reason = event.reason;
var wasClean = event.wasClean;
// handle close event
};

webSocket.addEventListener("close", function(event) {
var code = event.code;
var reason = event.reason;
var wasClean = event.wasClean;
// handle close event
});

6蔬咬、webSocket.onmessage

onmessage屬性,用于指定收到服務(wù)器數(shù)據(jù)后的回調(diào)函數(shù)
webSocket.onmessage = function(event) {
var data = event.data;
// 處理數(shù)據(jù)
};

webSocket.addEventListener("message", function(event) {
var data = event.data;
// 處理數(shù)據(jù)
});

注意央渣,服務(wù)器數(shù)據(jù)可能是文本计盒,也可能是二進(jìn)制數(shù)據(jù)(blob對象或Arraybuffer對象)
webSocket.onmessage = function(event){
if(typeof event.data === String) {
console.log("Received data string");
}

if(event.data instanceof ArrayBuffer){
var buffer = event.data;
console.log("Received arraybuffer");
}
}

除了動態(tài)判斷收到的數(shù)據(jù)類型,也可以使用binaryType屬性芽丹,顯式指定收到的二進(jìn)制數(shù)據(jù)類型北启。

// 收到的是 blob 數(shù)據(jù)
webSocket.binaryType = "blob";
webSocket.onmessage = function(e) {
console.log(e.data.size);
};

// 收到的是 ArrayBuffer 數(shù)據(jù)
webSocket.binaryType = "arraybuffer";
webSocket.onmessage = function(e) {
console.log(e.data.byteLength);
};

7、webSocket.onerror

onerror屬性拔第,用于指定報錯時的回調(diào)函數(shù)
webSocket.onerror = function(event) {
// handle error event
};

webSocket.addEventListener("error", function(event) {
// handle error event
});

8咕村、webSocket.send()

實例對象的send()方法用于向服務(wù)器發(fā)送數(shù)據(jù)

發(fā)送文本的例子
webSocket.send('your message');

發(fā)送 Blob 對象的例子。
var file = document.querySelector('input[type="file"]').files[0];
webSocket.send(file);

發(fā)送 ArrayBuffer 對象的例子蚊俺。
// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
binary[i] = img.data[i];
}
webSocket.send(binary.buffer);

9懈涛、webSocket.close()

實例對象的close()方法用于向服務(wù)器關(guān)閉連接
webSocket.close()

服務(wù)端如何實現(xiàn)?

WebSocket 在服務(wù)端的實現(xiàn)非常豐富泳猬。Node.js批钠、Java、C++得封、Python 等多種語言都有自己的解決方案
常用的 Node 實現(xiàn)有以下三種:

WebSocket小結(jié):

HTTP 和 WebSocket 有什么關(guān)系埋心?

Websocket 其實是一個新的應(yīng)用層協(xié)議,跟 HTTP 協(xié)議基本沒有關(guān)系忙上,只是為了兼容現(xiàn)有瀏覽器的握手規(guī)范而已拷呆,也就是說它是 HTTP 協(xié)議上的一種補(bǔ)充。
首先Websocket是基于HTTP協(xié)議的疫粥,或者說借用了HTTP的協(xié)議來完成一部分握手茬斧。

websocket握手階段:

GET /chat HTTP/1.1
Host: localhost:8080
Origin: http://127.0.0.1:3000
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw==
Sec-WebSocket-Protocol: chat, superchat

Connection: Upgrade:表示要升級協(xié)議
Upgrade: websocket:表示要升級到websocket協(xié)議
Sec-WebSocket-Version: 13:表示websocket的版本。如果服務(wù)端不支持該版本梗逮,需要返回一個Sec-WebSocket-Version header项秉,里面包含服務(wù)端支持的版本號
Sec-WebSocket-Key:是一個Base64 encode的值,這個是瀏覽器隨機(jī)生成的库糠,與后面服務(wù)端響應(yīng)首部的Sec-WebSocket-Accept是配套的伙狐,提供基本的防護(hù)涮毫,比如惡意的連接,或者無意的連接
Sec-WebSocket-Protocol: 是一個用戶定義的字符串贷屎,用來區(qū)分同URL下罢防,不同的服務(wù)所需要的協(xié)議。

然后服務(wù)器會返回下列東西唉侄,表示已經(jīng)接受到請求咒吐, 成功建立Websocket啦!

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

這里開始就是HTTP最后負(fù)責(zé)的區(qū)域了属划,告訴客戶恬叹,我已經(jīng)成功切換協(xié)議啦~

Sec-WebSocket-Accept:這個則是經(jīng)過服務(wù)器確認(rèn),根據(jù)客戶端請求首部的Sec-WebSocket-Key計算出來的
計算公式為:
1同眯、將Sec-WebSocket-Key跟258EAFA5-E914-47DA-95CA-C5AB0DC85B11拼接绽昼。
2、通過SHA1計算出摘要须蜗,并轉(zhuǎn)成base64字符串

toBase64( sha1( Sec-WebSocket-Key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 ) )

Sec-WebSocket-Protocol:則是表示最終使用的協(xié)議硅确。

至此,http已經(jīng)完成它所有工作了明肮,接下來就是完全按照Websocket協(xié)議進(jìn)行通信了菱农。

Sec-WebSocket-Key/Sec-WebSocket-Accept的主要作用在于提供基礎(chǔ)的防護(hù),減少惡意連接柿估、意外連接:

1循未、避免服務(wù)端收到非法的websocket連接(比如http客戶端不小心請求連接websocket服務(wù),此時服務(wù)端可以直接拒絕連接)
2秫舌、確保服務(wù)端理解websocket連接的妖。因為ws握手階段采用的是http協(xié)議,因此可能ws連接是被一個http服務(wù)器處理并返回的足陨,此時客戶端可以通過Sec-WebSocket-Key來確保服務(wù)端認(rèn)識ws協(xié)議羔味。(并非百分百保險,比如總是存在那么些無聊的http服務(wù)器钠右,光處理Sec-WebSocket-Key,但并沒有實現(xiàn)ws協(xié)議忘蟹。飒房。。)
3媚值、用瀏覽器里發(fā)起ajax請求狠毯,設(shè)置header時,Sec-WebSocket-Key以及其他相關(guān)的header是被禁止的褥芒。這樣可以避免客戶端發(fā)送ajax請求時嚼松,意外請求協(xié)議升級(websocket upgrade)
4嫡良、可以防止反向代理(不理解ws協(xié)議)返回錯誤的數(shù)據(jù)。比如反向代理前后收到兩次ws連接的升級請求献酗,反向代理把第一次請求的返回給cache住寝受,然后第二次請求到來時直接把cache住的請求給返回(無意義的返回)。
5罕偎、Sec-WebSocket-Key主要目的并不是確保數(shù)據(jù)的安全性很澄,因為Sec-WebSocket-Key、Sec-WebSocket-Accept的轉(zhuǎn)換計算公式是公開的颜及,而且非常簡單甩苛,最主要的作用是預(yù)防一些常見的意外情況(非故意的)。

強(qiáng)調(diào):Sec-WebSocket-Key/Sec-WebSocket-Accept 的換算俏站,只能帶來基本的保障讯蒲,但連接是否安全、數(shù)據(jù)是否安全肄扎、客戶端/服務(wù)端是否合法的 ws客戶端墨林、ws服務(wù)端,其實并沒有實際性的保證

websocket優(yōu)點:

1反浓、支持雙向通信萌丈,實時性更強(qiáng)。
2雷则、不用頻繁送HTTP請求辆雾,只需要發(fā)送一個HTTP請求進(jìn)行websocket握手,接下來則可以利用該TCP連接通過websocket協(xié)議通訊月劈,避免了傳輸多個HTTP Header的浪費
3度迂、支持傳輸文本和二進(jìn)制。
4猜揪、websocket數(shù)據(jù)傳輸是基于數(shù)據(jù)幀的惭墓,可以分片傳輸,不需要怕數(shù)據(jù)太大包容納不下而姐。
5腊凶、支持?jǐn)U展。ws協(xié)議定義了擴(kuò)展拴念,用戶可以擴(kuò)展協(xié)議钧萍,或者實現(xiàn)自定義的子協(xié)議。(比如支持自定義壓縮算法等)

WebSocket客戶端政鼠、服務(wù)端通信的最小單位是幀(frame)风瘦,由1個或多個幀組成一條完整的消息(message)

websocket出現(xiàn)之前的一些持久連接操作:

1、長輪詢:建立連接 -> 傳輸數(shù)據(jù) -> 保持連接 -> 公般。万搔。胡桨。-> 響應(yīng) -> 關(guān)閉連接
采取的是阻塞模型(一直打電話,沒收到就不掛電話)瞬雹,也就是說昧谊,客戶端發(fā)起連接后,如果沒消息挖炬,就一直不返回Response給客戶端揽浙。直到有消息才返回,返回完之后意敛,客戶端再次建立連接馅巷,周而復(fù)始。需要有很高的并發(fā)草姻,也就是說同時接待客戶的能力钓猬。(場地大小)服務(wù)器hold連接會消耗資源撩独,返回數(shù)據(jù)順序無保證敞曹,難于管理維護(hù)

2、ajax輪詢:建立連接 -> 傳輸數(shù)據(jù) -> 響應(yīng) -> 關(guān)閉連接 -> 定時循環(huán)上面的過程
定時向后臺發(fā)請求综膀,需要服務(wù)器有很快的處理速度和資源澳迫。(速度)請求中有大半是無用,浪費帶寬和服務(wù)器資源

3剧劝、長連接:建立連接 -> 傳輸數(shù)據(jù) -> 保持連接 -> 傳輸數(shù)據(jù) -> 橄登。。讥此。 -> 關(guān)閉連接
http1.0默認(rèn)進(jìn)行短連接拢锹,通過使用Connection: keep-alive進(jìn)行長連接,http1.1默認(rèn)進(jìn)行持久連接萄喳。在一次 TCP 連接中可以完成多個 http 請求卒稳,但是對每個請求仍然要單獨發(fā) header,keep-alive不會永久保持連接他巨,它有一個保持時間充坑,可以在不同的服務(wù)器軟件(如Nginx\Apache)中設(shè)定這個時間。
啟用keep-alive模式肯定更高效染突,性能更高匪傍。因為避免了建立/釋放連接的開銷

以上持久連接的缺點:
1、被動性 - 只能由客戶端發(fā)送請求
2觉痛、在傳統(tǒng)的方式上,要不斷的建立和關(guān)閉連接茵休,由于http是非狀態(tài)性的薪棒,每次都要重新傳輸identity info(鑒別信息)手蝎,來告訴服務(wù)端你是誰,解析耗時俐芯,耗資源棵介,效率還低
3、http1.1串行單線程處理吧史,響應(yīng)是有順序的邮辽,只有上一個請求完成后,下一個才能響應(yīng)贸营。一旦有任務(wù)處理超時等吨述,后續(xù)任務(wù)只能被阻塞(線頭阻塞)
4、keep-alive雙方并沒有建立正真的連接會話钞脂,服務(wù)端可以在任何一次請求完成后關(guān)閉

websocket長時間沒有通信會自動斷開的原因揣云?

利用nginx代理websocket的時候,發(fā)現(xiàn)客戶端和服務(wù)器握手成功后冰啃,如果在60s時間內(nèi)沒有數(shù)據(jù)交互邓夕,連接就會自動斷開。
nginx.conf 文件里location 中的proxy_read_timeout 默認(rèn)60s斷開阎毅。
保持持久連接的做法:
1焚刚、把服務(wù)器的默認(rèn)時間改大 + 發(fā)送心跳機(jī)制
2、定時檢測客戶端是否已經(jīng)斷開連接扇调,斷開重連

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末矿咕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子肃拜,更是在濱河造成了極大的恐慌痴腌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,331評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件燃领,死亡現(xiàn)場離奇詭異士聪,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)猛蔽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評論 3 398
  • 文/潘曉璐 我一進(jìn)店門剥悟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人曼库,你說我怎么就攤上這事区岗。” “怎么了毁枯?”我有些...
    開封第一講書人閱讀 167,755評論 0 360
  • 文/不壞的土叔 我叫張陵慈缔,是天一觀的道長。 經(jīng)常有香客問我种玛,道長藐鹤,這世上最難降的妖魔是什么瓤檐? 我笑而不...
    開封第一講書人閱讀 59,528評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮娱节,結(jié)果婚禮上挠蛉,老公的妹妹穿的比我還像新娘。我一直安慰自己肄满,他們只是感情好谴古,可當(dāng)我...
    茶點故事閱讀 68,526評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著稠歉,像睡著了一般掰担。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上轧抗,一...
    開封第一講書人閱讀 52,166評論 1 308
  • 那天恩敌,我揣著相機(jī)與錄音,去河邊找鬼横媚。 笑死纠炮,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的灯蝴。 我是一名探鬼主播恢口,決...
    沈念sama閱讀 40,768評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼穷躁!你這毒婦竟也來了耕肩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,664評論 0 276
  • 序言:老撾萬榮一對情侶失蹤问潭,失蹤者是張志新(化名)和其女友劉穎猿诸,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狡忙,經(jīng)...
    沈念sama閱讀 46,205評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡梳虽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,290評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了灾茁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窜觉。...
    茶點故事閱讀 40,435評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖北专,靈堂內(nèi)的尸體忽然破棺而出禀挫,到底是詐尸還是另有隱情,我是刑警寧澤拓颓,帶...
    沈念sama閱讀 36,126評論 5 349
  • 正文 年R本政府宣布语婴,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏腻格。R本人自食惡果不足惜画拾,卻給世界環(huán)境...
    茶點故事閱讀 41,804評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望菜职。 院中可真熱鬧,春花似錦旗闽、人聲如沸酬核。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,276評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嫡意。三九已至,卻和暖如春捣辆,著一層夾襖步出監(jiān)牢的瞬間蔬螟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工汽畴, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留旧巾,地道東北人。 一個月前我還...
    沈念sama閱讀 48,818評論 3 376
  • 正文 我出身青樓忍些,卻偏偏與公主長得像鲁猩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子罢坝,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,442評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 原文地址:http://www.ibm.com/developerworks/cn/java/j-lo-WebSo...
    敢夢敢當(dāng)閱讀 8,917評論 0 50
  • Socket并非是一個協(xié)議廓握,而是為了方便使用TCP而抽象出來的一層,是位于應(yīng)用層和傳輸控制層之間的一組接口嘁酿。換句話...
    JunChow520閱讀 3,343評論 0 4
  • WebSocket 機(jī)制 WebSocket 是 HTML5 一種新的協(xié)議隙券。它實現(xiàn)了瀏覽器與服務(wù)器全雙工通信,能更...
    勇敢的_心_閱讀 2,271評論 0 4
  • 一闹司、Finalize方法的過程 一個對象真正宣告死亡娱仔,至少要經(jīng)歷兩次標(biāo)記過程:如果對象在進(jìn)行可達(dá)性分析之后發(fā)現(xiàn)沒有...
    兔子胡蘿卜抱閱讀 1,559評論 0 1
  • #幸福是需要修出來的~每天進(jìn)步1%~幸福實修09班~張媛 20170727(11/30)09班 【幸福三朵玫瑰】 ...
    自在如我是閱讀 226評論 1 1