摘要:缺點(diǎn)可能會(huì)導(dǎo)致丟失數(shù)據(jù)在斷開重連的這段時(shí)間中,恰好雙方正在通信。博客前端積累文檔公眾號(hào)以上參考資料教程理解心跳及重連機(jī)制協(xié)議分鐘從入門到精通
在本篇文章之前,WebSocket很多人聽說過,沒見過,沒用過,以為是個(gè)很高大上的技術(shù),實(shí)際上這個(gè)技術(shù)并不神秘,可以說是個(gè)很容易就能掌握的技術(shù),希望在看完本文之后,馬上把文中的栗子拿出來(lái)自己試一試,實(shí)踐出真知。
游泳、健身了解一下:博客、前端積累文檔、公眾號(hào)、GitHubWebSocket解決了什么問題:
客戶端(瀏覽器)和服務(wù)器端進(jìn)行通信,只能由客戶端發(fā)起ajax請(qǐng)求,才能進(jìn)行通信,服務(wù)器端無(wú)法主動(dòng)向客戶端推送信息。
當(dāng)出現(xiàn)類似體育賽事、聊天室、實(shí)時(shí)位置之類的場(chǎng)景時(shí),客戶端要獲取服務(wù)器端的變化,就只能通過輪詢(定時(shí)請(qǐng)求)來(lái)了解服務(wù)器端有沒有新的信息變化。
輪詢效率低,非常浪費(fèi)資源(需要不斷發(fā)送請(qǐng)求,不停鏈接服務(wù)器)
WebSocket的出現(xiàn),讓服務(wù)器端可以主動(dòng)向客戶端發(fā)送信息,使得瀏覽器具備了實(shí)時(shí)雙向通信的能力,這就是WebSocket解決的問題
一個(gè)超簡(jiǎn)單的栗子:新建一個(gè)html文件,將本栗子找個(gè)地方跑一下試試,即可輕松入門WebSocket:
function socketConnect(url) { // 客戶端與服務(wù)器進(jìn)行連接 let ws = new WebSocket(url); // 返回`WebSocket`對(duì)象,賦值給變量ws // 連接成功回調(diào) ws.onopen = e => { console.log("連接成功", e) ws.send("我發(fā)送消息給服務(wù)端"); // 客戶端與服務(wù)器端通信 } // 監(jiān)聽服務(wù)器端返回的信息 ws.onmessage = e => { console.log("服務(wù)器端返回:", e.data) // do something } return ws; // 返回websocket對(duì)象 } let wsValue = socketConnect("ws://121.40.165.18:8800"); // websocket對(duì)象
上述栗子中WebSocket的接口地址出自:WebSocket 在線測(cè)試,在開發(fā)的時(shí)候也可以用于測(cè)試后端給的地址是否可用。
webSocket的class類:當(dāng)項(xiàng)目中很多地方使用WebSocket,把它封成一個(gè)class類,是更好的選擇。
下面的栗子,做了非常詳細(xì)的注釋,建個(gè)html文件也可直接使用,websocket的常用API都放進(jìn)去了。
下方注釋的代碼,先不用管,涉及到心跳機(jī)制,用于保持WebSocket連接的
class WebSocketClass { /** * @description: 初始化實(shí)例屬性,保存參數(shù) * @param {String} url ws的接口 * @param {Function} msgCallback 服務(wù)器信息的回調(diào)傳數(shù)據(jù)給函數(shù) * @param {String} name 可選值 用于區(qū)分ws,用于debugger */ constructor(url, msgCallback, name = "default") { this.url = url; this.msgCallback = msgCallback; this.name = name; this.ws = null; // websocket對(duì)象 this.status = null; // websocket是否關(guān)閉 } /** * @description: 初始化 連接websocket或重連webSocket時(shí)調(diào)用 * @param {*} 可選值 要傳的數(shù)據(jù) */ connect(data) { // 新建 WebSocket 實(shí)例 this.ws = new WebSocket(this.url); this.ws.onopen = e => { // 連接ws成功回調(diào) this.status = "open"; console.log(`${this.name}連接成功`, e) // this.heartCheck(); if (data !== undefined) { // 有要傳的數(shù)據(jù),就發(fā)給后端 return this.ws.send(data); } } // 監(jiān)聽服務(wù)器端返回的信息 this.ws.onmessage = e => { // 把數(shù)據(jù)傳給回調(diào)函數(shù),并執(zhí)行回調(diào) // if (e.data === "pong") { // this.pingPong = "pong"; // 服務(wù)器端返回pong,修改pingPong的狀態(tài) // } return this.msgCallback(e.data); } // ws關(guān)閉回調(diào) this.ws.onclose = e => { this.closeHandle(e); // 判斷是否關(guān)閉 } // ws出錯(cuò)回調(diào) this.onerror = e => { this.closeHandle(e); // 判斷是否關(guān)閉 } } // heartCheck() { // // 心跳機(jī)制的時(shí)間可以自己與后端約定 // this.pingPong = "ping"; // ws的心跳機(jī)制狀態(tài)值 // this.pingInterval = setInterval(() => { // if (this.ws.readyState === 1) { // // 檢查ws為鏈接狀態(tài) 才可發(fā)送 // this.ws.send("ping"); // 客戶端發(fā)送ping // } // }, 10000) // this.pongInterval = setInterval(() => { // this.pingPong = false; // if (this.pingPong === "ping") { // this.closeHandle("pingPong沒有改變?yōu)閜ong"); // 沒有返回pong 重啟webSocket // } // // 重置為ping 若下一次 ping 發(fā)送失敗 或者pong返回失敗(pingPong不會(huì)改成pong),將重啟 // console.log("返回pong") // this.pingPong = "ping" // }, 20000) // } // 發(fā)送信息給服務(wù)器 sendHandle(data) { console.log(`${this.name}發(fā)送消息給服務(wù)器:`, data) return this.ws.send(data); } closeHandle(e = "err") { // 因?yàn)閣ebSocket并不穩(wěn)定,規(guī)定只能手動(dòng)關(guān)閉(調(diào)closeMyself方法),否則就重連 if (this.status !== "close") { console.log(`${this.name}斷開,重連websocket`, e) // if (this.pingInterval !== undefined && this.pongInterval !== undefined) { // // 清除定時(shí)器 // clearInterval(this.pingInterval); // clearInterval(this.pongInterval); // } this.connect(); // 重連 } else { console.log(`${this.name}websocket手動(dòng)關(guān)閉`) } } // 手動(dòng)關(guān)閉WebSocket closeMyself() { console.log(`關(guān)閉${this.name}`) this.status = "close"; return this.ws.close(); } } function someFn(data) { console.log("接收服務(wù)器消息的回調(diào):", data); } // const wsValue = new WebSocketClass("ws://121.40.165.18:8800", someFn, "wsName"); // 這個(gè)鏈接一天只能發(fā)送消息50次 const wsValue = new WebSocketClass("wss://echo.websocket.org", someFn, "wsName"); // 阮一峰老師教程鏈接 wsValue.connect("立即與服務(wù)器通信"); // 連接服務(wù)器 // setTimeout(() => { // wsValue.sendHandle("傳消息給服務(wù)器") // }, 1000); // setTimeout(() => { // wsValue.closeMyself(); // 關(guān)閉ws // }, 10000)
栗子里面我直接寫在了一起,可以把class放在一個(gè)js文件里面,export出去,然后在需要用的地方再import進(jìn)來(lái),把參數(shù)傳進(jìn)去就可以用了。
WebSocket不穩(wěn)定WebSocket并不穩(wěn)定,在使用一段時(shí)間后,可能會(huì)斷開連接,貌似至今沒有一個(gè)為何會(huì)斷開連接的公論,所以我們需要讓W(xué)ebSocket保持連接狀態(tài),這里推薦兩種方法。
WebSocket設(shè)置變量,判斷是否手動(dòng)關(guān)閉連接:class類中就是用的這種方式:設(shè)置一個(gè)變量,在webSocket關(guān)閉/報(bào)錯(cuò)的回調(diào)中,判斷是不是手動(dòng)關(guān)閉的,如果不是的話,就重新連接,這樣做的優(yōu)缺點(diǎn)如下:
優(yōu)點(diǎn):請(qǐng)求較少(相對(duì)于心跳連接),易設(shè)置。
缺點(diǎn):可能會(huì)導(dǎo)致丟失數(shù)據(jù),在斷開重連的這段時(shí)間中,恰好雙方正在通信。
WebSocket心跳機(jī)制:因?yàn)榈谝环N方案的缺點(diǎn),并且可能會(huì)有其他一些未知情況導(dǎo)致斷開連接而沒有觸發(fā)Error或Close事件。這樣就導(dǎo)致實(shí)際連接已經(jīng)斷開了,而客戶端和服務(wù)端卻不知道,還在傻傻的等著消息來(lái)。
然后聰明的程序猿們想出了一種叫做心跳機(jī)制的解決方法:
客戶端就像心跳一樣每隔固定的時(shí)間發(fā)送一次ping,來(lái)告訴服務(wù)器,我還活著,而服務(wù)器也會(huì)返回pong,來(lái)告訴客戶端,服務(wù)器還活著。
具體的實(shí)現(xiàn)方法,在上面class的注釋中,將其打開,即可看到效果。
關(guān)于WebSocket怕一開始就堆太多文字性的內(nèi)容,把各位嚇跑了,現(xiàn)在大家已經(jīng)會(huì)用了,我們?cè)倩仡^來(lái)看看WebSocket的其他知識(shí)點(diǎn)。
WebSocket的當(dāng)前狀態(tài):WebSocket.readyState下面是WebSocket.readyState的四個(gè)值(四種狀態(tài)):
0: 表示正在連接
1: 表示連接成功,可以通信了
2: 表示連接正在關(guān)閉
3: 表示連接已經(jīng)關(guān)閉,或者打開連接失敗
我們可以利用當(dāng)前狀態(tài)來(lái)做一些事情,比如上面栗子中當(dāng)WebSocket鏈接成功后,才允許客戶端發(fā)送ping。
if (this.ws.readyState === 1) { // 檢查ws為鏈接狀態(tài) 才可發(fā)送 this.ws.send("ping"); // 客戶端發(fā)送ping }WebSocket還可以發(fā)送/接收 二進(jìn)制數(shù)據(jù)
這里我也沒有試過,我是看阮一峰老師的WebSocket教程才知道有這么個(gè)東西,有興趣的可以再去谷歌,大家知道一下就可以。
二進(jìn)制數(shù)據(jù)包括:blob對(duì)象和Arraybuffer對(duì)象,所以我們需要分開來(lái)處理。
// 接收數(shù)據(jù) ws.onmessage = function(event){ if(event.data instanceof ArrayBuffer){ // 判斷 ArrayBuffer 對(duì)象 } if(event.data instanceof Blob){ // 判斷 Blob 對(duì)象 } } // 發(fā)送 Blob 對(duì)象的例子 let file = document.querySelector("input[type="file"]").files[0]; ws.send(file); // 發(fā)送 ArrayBuffer 對(duì)象的例子 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]; } ws.send(binary.buffer);
如果你要發(fā)送的二進(jìn)制數(shù)據(jù)很大的話,如何判斷發(fā)送完畢:
webSocket.bufferedAmount屬性,表示還有多少字節(jié)的二進(jìn)制數(shù)據(jù)沒有發(fā)送出去:
var data = new ArrayBuffer(10000000); socket.send(data); if (socket.bufferedAmount === 0) { // 發(fā)送完畢 } else { // 發(fā)送還沒結(jié)束 }
上述栗子出自阮一峰老師的WebSocket教程
WebSocket的優(yōu)點(diǎn):最后再吹一波WebSocket:
雙向通信(一開始說的,也是最重要的一點(diǎn))。
數(shù)據(jù)格式比較輕量,性能開銷小,通信高效
協(xié)議控制的數(shù)據(jù)包頭部較小,而HTTP協(xié)議每次通信都需要攜帶完整的頭部
更好的二進(jìn)制支持
沒有同源限制,客戶端可以與任意服務(wù)器通信
與 HTTP 協(xié)議有著良好的兼容性。默認(rèn)端口也是80和443,并且握手階段采用 HTTP 協(xié)議,因此握手時(shí)不容易屏蔽,能通過各種 HTTP 代理服務(wù)器
結(jié)語(yǔ)看了本文之后,如果還是有點(diǎn)迷糊的話,一定要把文中的兩個(gè)栗子,新建個(gè)html文件跑起來(lái),自己鼓搗鼓搗一下。不然讀多少博客/教程都沒有用,實(shí)踐才出真知,切勿紙上談兵。
希望看完的朋友可以點(diǎn)個(gè)喜歡/關(guān)注,您的支持是對(duì)我最大的鼓勵(lì)。博客、前端積累文檔、公眾號(hào)、GitHub
以上2018.10.22
參考資料:
WebSocket 教程
理解WebSocket心跳及重連機(jī)制
WebSocket協(xié)議:5分鐘從入門到精通
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/98690.html
摘要:手摸手教你用寫一個(gè)解釋器用來(lái)編譯看起來(lái)是個(gè)高大上的東西,實(shí)際原理其實(shí)很簡(jiǎn)單,無(wú)非就是利用對(duì)象屬性可以用字符串表示這個(gè)特性來(lái)實(shí)現(xiàn)的黑魔法罷了。 手摸手教你用 js 寫一個(gè) js 解釋器 用 js 來(lái) 編譯 js 看起來(lái)是個(gè)高大上的東西,實(shí)際原理其實(shí)很簡(jiǎn)單,無(wú)非就是利用 js 對(duì)象屬性可以用字符串表示 這個(gè)特性來(lái)實(shí)現(xiàn)的黑魔法罷了。之所以看起來(lái)那么 深?yuàn)W, 大概是由于網(wǎng)上現(xiàn)有的教程,都是動(dòng)不...
摘要:最近項(xiàng)目中遇到一個(gè)需求,需要把一張圖片加上平鋪的水印類似這樣的效果首先想到的是用完成這種功能,因?yàn)槲抑耙矝]有接觸過,所以做這個(gè)功能的時(shí)候,就是一步一步的摸索中學(xué)習(xí),過程還是挺的,接下來(lái)跟我一步步來(lái)實(shí)現(xiàn)這個(gè)功能以及發(fā)現(xiàn)一些的坑吧。 最近項(xiàng)目中遇到一個(gè)需求,需要把一張圖片加上平鋪的水印 類似這樣的效果showImg(https://segmentfault.com/img/remote/...
摘要:只有動(dòng)手,你才能真的理解作者的構(gòu)思的巧妙只有動(dòng)手,你才能真正掌握一門技術(shù)持續(xù)更新中項(xiàng)目地址求求求源碼系列跟一起學(xué)如何寫函數(shù)庫(kù)中高級(jí)前端面試手寫代碼無(wú)敵秘籍如何用不到行代碼寫一款屬于自己的類庫(kù)原理講解實(shí)現(xiàn)一個(gè)對(duì)象遵循規(guī)范實(shí)戰(zhàn)手摸手,帶你用擼 Do it yourself!!! 只有動(dòng)手,你才能真的理解作者的構(gòu)思的巧妙 只有動(dòng)手,你才能真正掌握一門技術(shù) 持續(xù)更新中…… 項(xiàng)目地址 https...
閱讀 2968·2021-09-22 15:43
閱讀 5299·2021-09-06 15:02
閱讀 911·2019-08-29 13:55
閱讀 1746·2019-08-29 12:58
閱讀 3141·2019-08-29 12:38
閱讀 1314·2019-08-26 12:20
閱讀 2320·2019-08-26 12:12
閱讀 3395·2019-08-23 18:35