作者:Gaby 原文鏈接 https:///post/7020964728386093093一.前言 踩著年末的尾巴,提前布局來年,為來年的工作做個(gè)好的鋪墊,所以就開始了面試歷程,因?yàn)轫?xiàng)目中使用到了 WebSocket ,面試官在深挖項(xiàng)目經(jīng)驗(yàn)的時(shí)候,也難免提到 WebSocket 相關(guān)的知識(shí)點(diǎn),因?yàn)橹安]有考慮這么深,所以,回答的還是有所欠缺,因此,趕緊趁熱再熟悉熟悉,也借此機(jī)會(huì),整理出來供大家咀嚼,每個(gè)項(xiàng)目都有其值得挖掘的閃光點(diǎn),要用有愛的眼睛去發(fā)現(xiàn)。 二.什么是 WebSocket WebSocket 是一種在單個(gè)TCP連接上進(jìn)行全雙工通信的協(xié)議。Websocket 使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡(jiǎn)單,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù)。 在 WebSocket API 中,瀏覽器和服務(wù)器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接, 并進(jìn)行雙向數(shù)據(jù)傳輸。(維基百科) WebSocket 本質(zhì)上是一種計(jì)算機(jī)網(wǎng)絡(luò)應(yīng)用層的協(xié)議,用來彌補(bǔ) http 協(xié)議在持久通信能力上的不足。 WebSocket 協(xié)議在2008年誕生,2011年成為國(guó)際標(biāo)準(zhǔn)?,F(xiàn)在最新版本瀏覽器都已經(jīng)支持了。 它的最大特點(diǎn)就是,服務(wù)器可以主動(dòng)向客戶端推送信息,客戶端也可以主動(dòng)向服務(wù)器發(fā)送信息,是真正的雙向平等對(duì)話,屬于服務(wù)器推送技術(shù)的一種。 WebSocket 的其他特點(diǎn)包括:
ws://:80/some/path
為什么需要 WebSocket? 我們已經(jīng)有了 HTTP 協(xié)議,為什么還需要另一個(gè)協(xié)議?它能帶來什么好處? 因?yàn)?HTTP 協(xié)議有一個(gè)缺陷:通信只能由客戶端發(fā)起,不具備服務(wù)器推送能力。 舉例來說,我們想了解查詢今天的實(shí)時(shí)數(shù)據(jù),只能是客戶端向服務(wù)器發(fā)出請(qǐng)求,服務(wù)器返回查詢結(jié)果。HTTP 協(xié)議做不到服務(wù)器主動(dòng)向客戶端推送信息。 這種單向請(qǐng)求的特點(diǎn),注定了如果服務(wù)器有連續(xù)的狀態(tài)變化,客戶端要獲知就非常麻煩。我們只能使用'輪詢':每隔一段時(shí)候,就發(fā)出一個(gè)詢問,了解服務(wù)器有沒有新的信息。最典型的場(chǎng)景就是聊天室。輪詢的效率低,非常浪費(fèi)資源(因?yàn)楸仨毑煌_B接,或者 HTTP 連接始終打開)。 在 WebSocket 協(xié)議出現(xiàn)以前,創(chuàng)建一個(gè)和服務(wù)端進(jìn)雙通道通信的 web 應(yīng)用,需要依賴HTTP協(xié)議,進(jìn)行不停的輪詢,這會(huì)導(dǎo)致一些問題:
http 協(xié)議本身是沒有持久通信能力的,但是我們?cè)趯?shí)際的應(yīng)用中,是很需要這種能力的,所以,為了解決這些問題,WebSocket 協(xié)議由此而生,于2011年被IETF定為標(biāo)準(zhǔn)RFC6455,并被RFC7936所補(bǔ)充規(guī)范。并且在 HTML5 標(biāo)準(zhǔn)中增加了有關(guān) WebSocket 協(xié)議的相關(guān) api ,所以只要實(shí)現(xiàn)了 HTML5 標(biāo)準(zhǔn)的客戶端,就可以與支持 WebSocket 協(xié)議的服務(wù)器進(jìn)行全雙工的持久通信了。 WebSocket 與 HTTP 的區(qū)別 WebSocket 與 HTTP 的關(guān)系圖:
下面一張圖說明了 HTTP 與 WebSocket 的主要區(qū)別: 不同點(diǎn):
與http協(xié)議一樣, WebSocket 協(xié)議也需要通過已建立的TCP連接來傳輸數(shù)據(jù)。具體實(shí)現(xiàn)上是通過http協(xié)議建立通道,然后在此基礎(chǔ)上用真正 WebSocket 協(xié)議進(jìn)行通信,所以WebSocket協(xié)議和http協(xié)議是有一定的交叉關(guān)系的。首先, WebSocket 是一個(gè)持久化的協(xié)議,相對(duì)于 HTTP 這種非持久的協(xié)議來說。簡(jiǎn)單的舉個(gè)例子吧,用目前應(yīng)用比較廣泛的 PHP 生命周期來解釋。 HTTP 的生命周期通過 Request 來界定,也就是一個(gè) Request 一個(gè) Response ,那么在 HTTP1.0 中,這次 HTTP 請(qǐng)求就結(jié)束了。 在 HTTP1.1 中進(jìn)行了改進(jìn),使得有一個(gè) keep-alive,也就是說,在一個(gè) HTTP 連接中,可以發(fā)送多個(gè) Request,接收多個(gè) Response。但是請(qǐng)記住 Request = Response, 在 HTTP 中永遠(yuǎn)是這樣,也就是說一個(gè) Request 只能有一個(gè) Response。而且這個(gè) Response 也是被動(dòng)的,不能主動(dòng)發(fā)起。首先 WebSocket 是基于 HTTP 協(xié)議的,或者說借用了 HTTP 協(xié)議來完成一部分握手。 首先我們來看個(gè)典型的 WebSocket 握手
熟悉 HTTP 的童鞋可能發(fā)現(xiàn)了,這段類似 HTTP 協(xié)議的握手請(qǐng)求中,多了這么幾個(gè)東西。
這個(gè)就是 WebSocket 的核心了,告訴 Apache 、 nginx 等服務(wù)器:注意啦,我發(fā)起的請(qǐng)求要用 WebSocket 協(xié)議,快點(diǎn)幫我找到對(duì)應(yīng)的助理處理~而不是那個(gè)老土的 HTTP 。
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é)議啦~
依然是固定的,告訴客戶端即將升級(jí)的是 WebSocket 協(xié)議,而不是 mozillasocket ,lurnarsocket 或者 shitsocket。 然后, Sec-WebSocket-Accept 這個(gè)則是經(jīng)過服務(wù)器確認(rèn),并且加密過后的 Sec-WebSocket-Key。服務(wù)器:好啦好啦,知道啦,給你看我的 ID CARD 來證明行了吧。后面的, Sec-WebSocket-Protocol 則是表示最終使用的協(xié)議。至此,HTTP 已經(jīng)完成它所有工作了,接下來就是完全按照 WebSocket 協(xié)議進(jìn)行了??偨Y(jié), WebSocket 連接的過程是:
優(yōu)點(diǎn):
缺點(diǎn):
心跳就是客戶端定時(shí)的給服務(wù)端發(fā)送消息,證明客戶端是在線的, 如果超過一定的時(shí)間沒有發(fā)送則就是離線了。 如何判斷在線離線? 當(dāng)客戶端第一次發(fā)送請(qǐng)求至服務(wù)端時(shí)會(huì)攜帶唯一標(biāo)識(shí)、以及時(shí)間戳,服務(wù)端到db或者緩存去查詢改請(qǐng)求的唯一標(biāo)識(shí),如果不存在就存入db或者緩存中, 第二次客戶端定時(shí)再次發(fā)送請(qǐng)求依舊攜帶唯一標(biāo)識(shí)、以及時(shí)間戳,服務(wù)端到db或者緩存去查詢改請(qǐng)求的唯一標(biāo)識(shí),如果存在就把上次的時(shí)間戳拿取出來,使用當(dāng)前時(shí)間戳減去上次的時(shí)間, 得出的毫秒秒數(shù)判斷是否大于指定的時(shí)間,若小于的話就是在線,否則就是離線; 如何解決斷線問題 通過查閱資料了解到 nginx 代理的 websocket 轉(zhuǎn)發(fā),無消息連接會(huì)出現(xiàn)超時(shí)斷開問題。網(wǎng)上資料提到解決方案兩種,一種是修改nginx配置信息,第二種是 websocket 發(fā)送心跳包。下面就來總結(jié)一下本次項(xiàng)目實(shí)踐中解決的 websocket 的斷線 和 重連 這兩個(gè)問題的解決方案。主動(dòng)觸發(fā)包括主動(dòng)斷開連接,客戶端主動(dòng)發(fā)送消息給后端
ws.close(); 主動(dòng)斷開連接,根據(jù)需要使用,基本很少用到。
ws.send('hello world');
下面主要講一下客戶端也就是前端如何實(shí)現(xiàn)心跳包: 首先了解一下心跳包機(jī)制 跳包之所以叫心跳包是因?yàn)椋核裥奶粯用扛艄潭〞r(shí)間發(fā)一次,以此來告訴服務(wù)器,這個(gè)客戶端還活著。事實(shí)上這是為了保持長(zhǎng)連接,至于這個(gè)包的內(nèi)容,是沒有什么特別規(guī)定的,不過一般都是很小的包,或者只包含包頭的一個(gè)空包。 在 TCP 的機(jī)制里面,本身是存在有心跳包的機(jī)制的,也就是 TCP 的選項(xiàng):SO_KEEPALIVE 。系統(tǒng)默認(rèn)是設(shè)置的2小時(shí)的心跳頻率。但是它檢查不到機(jī)器斷電、網(wǎng)線拔出、防火墻這些斷線。而且邏輯層處理斷線可能也不是那么好處理。一般,如果只是用于?;钸€是可以的。 心跳包一般來說都是在邏輯層發(fā)送空的 echo 包來實(shí)現(xiàn)的。下一個(gè)定時(shí)器,在一定時(shí)間間隔下發(fā)送一個(gè)空包給客戶端,然后客戶端反饋一個(gè)同樣的空包回來,服務(wù)器如果在一定時(shí)間內(nèi)收不到客戶端發(fā)送過來的反饋包,那就只有認(rèn)定說掉線了。 在長(zhǎng)連接下,有可能很長(zhǎng)一段時(shí)間都沒有數(shù)據(jù)往來。理論上說,這個(gè)連接是一直保持連接的,但是實(shí)際情況中,如果中間節(jié)點(diǎn)出現(xiàn)什么故障是難以知道的。更要命的是,有的節(jié)點(diǎn)(防火墻)會(huì)自動(dòng)把一定時(shí)間之內(nèi)沒有數(shù)據(jù)交互的連接給斷掉。在這個(gè)時(shí)候,就需要我們的心跳包了,用于維持長(zhǎng)連接,?;睢?/p> 心跳檢測(cè)步驟:
// 前端解決方案:心跳檢測(cè) var heartCheck = { timeout: 30000, //30秒發(fā)一次心跳 timeoutObj: null, serverTimeoutObj: null, reset: function(){ clearTimeout(this.timeoutObj); clearTimeout(this.serverTimeoutObj); return this; }, start: function(){ var self = this; this.timeoutObj = setTimeout(function(){ //這里發(fā)送一個(gè)心跳,后端收到后,返回一個(gè)心跳消息, //onmessage拿到返回的心跳就說明連接正常 ws.send('ping'); console.log('ping!') self.serverTimeoutObj = setTimeout(function(){//如果超過一定時(shí)間還沒重置,說明后端主動(dòng)斷開了 ws.close(); //如果onclose會(huì)執(zhí)行reconnect,我們執(zhí)行ws.close()就行了.如果直接執(zhí)行reconnect 會(huì)觸發(fā)onclose導(dǎo)致重連兩次 }, self.timeout); }, this.timeout); } }
針對(duì)這種異常的中斷解決方案就是處理重連,下面我們給出的重連方案是使用js庫(kù)處理:引入reconnecting-websocket.min.js,ws建立鏈接方法使用js庫(kù)api方法:
斷網(wǎng)監(jiān)測(cè)支持使用js庫(kù):offline.min.js
以上方案,只是拋磚引玉,如果大家有更好的解決方案歡迎評(píng)論區(qū)分享交流。 七、總結(jié) WebSocket 是為了在 web 應(yīng)用上進(jìn)行雙通道通信而產(chǎn)生的協(xié)議,相比于輪詢HTTP請(qǐng)求的方式,WebSocket 有節(jié)省服務(wù)器資源,效率高等優(yōu)點(diǎn)。WebSocket 中的掩碼是為了防止早期版本中存在中間緩存污染攻擊等問題而設(shè)置的,客戶端向服務(wù)端發(fā)送數(shù)據(jù)需要掩碼,服務(wù)端向客戶端發(fā)送數(shù)據(jù)不需要掩碼。WebSocket 中 Sec-WebSocket-Key 的生成算法是拼接服務(wù)端和客戶端生成的字符串,進(jìn)行SHA1哈希算法,再用base64編碼。WebSocket 協(xié)議握手是依靠 HTTP 協(xié)議的,依靠于 HTTP 響應(yīng)101進(jìn)行協(xié)議升級(jí)轉(zhuǎn)換。 參考
|
|