在本篇文章以前,WebSocket
不少人據說過,沒見過,沒用過,覺得是個很高大上的技術,實際上這個技術並不神祕,能夠說是個很容易就能掌握的技術,但願在看完本文以後,立刻把文中的栗子拿出來本身試一試,實踐出真知。html
游泳、健身瞭解一下: 博客、 前端積累文檔、 公衆號、 GitHub
WebSocket
解決了什麼問題:客戶端(瀏覽器)和服務器端進行通訊,只能由客戶端發起ajax
請求,才能進行通訊,服務器端沒法主動向客戶端推送信息。前端
當出現相似體育賽事、聊天室、實時位置之類的場景時,客戶端要獲取服務器端的變化,就只能經過輪詢(定時請求)來了解服務器端有沒有新的信息變化。git
輪詢效率低,很是浪費資源(須要不斷髮送請求,不停連接服務器)
WebSocket的出現,讓服務器端能夠主動向客戶端發送信息,使得瀏覽器具有了實時雙向通訊的能力,這就是WebSocket
解決的問題github
新建一個html
文件,將本栗子找個地方跑一下試試,便可輕鬆入門WebSocket
:web
function socketConnect(url) { // 客戶端與服務器進行鏈接 let ws = new WebSocket(url); // 返回`WebSocket`對象,賦值給變量ws // 鏈接成功回調 ws.onopen = e => { console.log('鏈接成功', e) ws.send('我發送消息給服務端'); // 客戶端與服務器端通訊 } // 監聽服務器端返回的信息 ws.onmessage = e => { console.log('服務器端返回:', e.data) // do something } return ws; // 返回websocket對象 } let wsValue = socketConnect('ws://121.40.165.18:8800'); // websocket對象
上述栗子中WebSocket
的接口地址出自:WebSocket 在線測試,在開發的時候也能夠用於測試後端給的地址是否可用。ajax
當項目中不少地方使用WebSocket,把它封成一個class類,是更好的選擇。canvas
下面的栗子,作了很是詳細的註釋,建個html文件也可直接使用,websocket的經常使用API
都放進去了。後端
下方註釋的代碼,先不用管,涉及到心跳機制,用於保持WebSocket鏈接的瀏覽器
class WebSocketClass { /** * @description: 初始化實例屬性,保存參數 * @param {String} url ws的接口 * @param {Function} msgCallback 服務器信息的回調傳數據給函數 * @param {String} name 可選值 用於區分ws,用於debugger */ constructor(url, msgCallback, name = 'default') { this.url = url; this.msgCallback = msgCallback; this.name = name; this.ws = null; // websocket對象 this.status = null; // websocket是否關閉 } /** * @description: 初始化 鏈接websocket或重連webSocket時調用 * @param {*} 可選值 要傳的數據 */ connect(data) { // 新建 WebSocket 實例 this.ws = new WebSocket(this.url); this.ws.onopen = e => { // 鏈接ws成功回調 this.status = 'open'; console.log(`${this.name}鏈接成功`, e) // this.heartCheck(); if (data !== undefined) { // 有要傳的數據,就發給後端 return this.ws.send(data); } } // 監聽服務器端返回的信息 this.ws.onmessage = e => { // 把數據傳給回調函數,並執行回調 // if (e.data === 'pong') { // this.pingPong = 'pong'; // 服務器端返回pong,修改pingPong的狀態 // } return this.msgCallback(e.data); } // ws關閉回調 this.ws.onclose = e => { this.closeHandle(e); // 判斷是否關閉 } // ws出錯回調 this.onerror = e => { this.closeHandle(e); // 判斷是否關閉 } } // heartCheck() { // // 心跳機制的時間能夠本身與後端約定 // this.pingPong = 'ping'; // ws的心跳機制狀態值 // this.pingInterval = setInterval(() => { // if (this.ws.readyState === 1) { // // 檢查ws爲連接狀態 纔可發送 // this.ws.send('ping'); // 客戶端發送ping // } // }, 10000) // this.pongInterval = setInterval(() => { // this.pingPong = false; // if (this.pingPong === 'ping') { // this.closeHandle('pingPong沒有改變爲pong'); // 沒有返回pong 重啓webSocket // } // // 重置爲ping 若下一次 ping 發送失敗 或者pong返回失敗(pingPong不會改爲pong),將重啓 // console.log('返回pong') // this.pingPong = 'ping' // }, 20000) // } // 發送信息給服務器 sendHandle(data) { console.log(`${this.name}發送消息給服務器:`, data) return this.ws.send(data); } closeHandle(e = 'err') { // 由於webSocket並不穩定,規定只能手動關閉(調closeMyself方法),不然就重連 if (this.status !== 'close') { console.log(`${this.name}斷開,重連websocket`, e) // if (this.pingInterval !== undefined && this.pongInterval !== undefined) { // // 清除定時器 // clearInterval(this.pingInterval); // clearInterval(this.pongInterval); // } this.connect(); // 重連 } else { console.log(`${this.name}websocket手動關閉`) } } // 手動關閉WebSocket closeMyself() { console.log(`關閉${this.name}`) this.status = 'close'; return this.ws.close(); } } function someFn(data) { console.log('接收服務器消息的回調:', data); } // const wsValue = new WebSocketClass('ws://121.40.165.18:8800', someFn, 'wsName'); // 這個連接一天只能發送消息50次 const wsValue = new WebSocketClass('wss://echo.websocket.org', someFn, 'wsName'); // 阮一峯老師教程連接 wsValue.connect('當即與服務器通訊'); // 鏈接服務器 // setTimeout(() => { // wsValue.sendHandle('傳消息給服務器') // }, 1000); // setTimeout(() => { // wsValue.closeMyself(); // 關閉ws // }, 10000)
栗子裏面我直接寫在了一塊兒,能夠把class
放在一個js文件裏面,export
出去,而後在須要用的地方再import
進來,把參數傳進去就能夠用了。服務器
WebSocket並不穩定,在使用一段時間後,可能會斷開鏈接,貌似至今沒有一個爲什麼會斷開鏈接的公論,因此咱們須要讓WebSocket保持鏈接狀態,這裏推薦兩種方法。
class
類中就是用的這種方式:設置一個變量,在webSocket關閉/報錯的回調中,判斷是否是手動關閉的,若是不是的話,就從新鏈接,這樣作的優缺點以下:
由於第一種方案的缺點,而且可能會有其餘一些未知狀況致使斷開鏈接而沒有觸發Error或Close事件。這樣就致使實際鏈接已經斷開了,而客戶端和服務端殊不知道,還在傻傻的等着消息來。
而後聰明的程序猿們想出了一種叫作心跳機制的解決方法:
客戶端就像心跳同樣每隔固定的時間發送一次ping
,來告訴服務器,我還活着,而服務器也會返回pong
,來告訴客戶端,服務器還活着。
具體的實現方法,在上面class
的註釋中,將其打開,便可看到效果。
怕一開始就堆太多文字性的內容,把各位嚇跑了,如今你們已經會用了,咱們再回頭來看看WebSocket的其餘知識點。
WebSocket.readyState
下面是WebSocket.readyState
的四個值(四種狀態):
咱們能夠利用當前狀態來作一些事情,好比上面栗子中當WebSocket連接成功後,才容許客戶端發送ping
。
if (this.ws.readyState === 1) { // 檢查ws爲連接狀態 纔可發送 this.ws.send('ping'); // 客戶端發送ping }
WebSocket
還能夠發送/接收 二進制數據這裏我也沒有試過,我是看阮一峯老師的WebSocket教程才知道有這麼個東西,有興趣的能夠再去谷歌,你們知道一下就能夠。
二進制數據包括:blob
對象和Arraybuffer
對象,因此咱們須要分開來處理。
// 接收數據 ws.onmessage = function(event){ if(event.data instanceof ArrayBuffer){ // 判斷 ArrayBuffer 對象 } if(event.data instanceof Blob){ // 判斷 Blob 對象 } } // 發送 Blob 對象的例子 let file = document.querySelector('input[type="file"]').files[0]; ws.send(file); // 發送 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]; } ws.send(binary.buffer);
若是你要發送的二進制數據很大的話,如何判斷髮送完畢:
webSocket.bufferedAmount
屬性,表示還有多少字節的二進制數據沒有發送出去:
var data = new ArrayBuffer(10000000); socket.send(data); if (socket.bufferedAmount === 0) { // 發送完畢 } else { // 發送還沒結束 }
上述栗子出自阮一峯老師的WebSocket教程
最後再吹一波WebSocket:
協議控制的數據包頭部較小,而HTTP協議每次通訊都須要攜帶完整的頭部
看了本文以後,若是仍是有點迷糊的話,必定要把文中的兩個栗子,新建個html文件跑起來,本身鼓搗鼓搗一下。否則讀多少博客/教程都沒有用,實踐纔出真知,切勿紙上談兵。
以上2018.10.22
參考資料: