• QQ咨詢:4001806960
  • 咨詢熱線:400-180-6960

站內信最佳通信方案:WebSocket

作者:日期:2019-05-08 09:58:16 點擊:232

 

你不知道websocket

websocket已經滿大街都是了,我今天用我們之前的管理後台的站內信爲例,今天給大家扒一把源代碼,順便聊一聊你不知道的細節。

本文不討論ws是什麽,也不討論輪詢、長連接與websocket的區別,這些在網絡上屢見不鮮,如果年你還不知道ws是什麽,那麽趕緊去求助搜索引擎吧。等搞懂本段前面的內容再來看向下看吧。

首先呢給大家介紹一下我們的站內信系統。站內信是底層的通用系統之一,其主要功能是在web上爲用戶提供即時的消息通知,目前僅支持系統發出的通知,如客服點擊發貨後,對應的需要知道這條通知的用戶在系統內會收到一條消息通知。目前該功能暫不支持用戶向系統或其他用戶發送聊天消息。此外,這也是本人第一次將ws用于生産環境,如果有任何不足之處歡迎批評指正。

本文內容提要:

  • 建立ws連接

  • 主動斷開ws

  • 在https協議下

  • 心跳同步與斷開重連

  • 完整示例

建立ws連接

獲得WS的實例

浏覽器內置了WebSocket類型,建立連接只需要獲得WebSocket的實例即可建立連接,在實例化的過程中傳入服務端提供ws服務的接口(ip或者鏈接)。

let ws = new WebScoket(url)

ws實例的部分事件

  • open 事件

一旦服務器響應了WebSocket連接請求,open事件觸發並建立一個連接。open事件對應的回調函數稱作onopen。本例中,我們在成功建立連接後向服務端發送了一些用戶的信息,這些信息用來注冊消息服務。

ws.onopene = () => {
let {readyState} = ws
if (+readyState === 1) {
time = 0
}
let d = {
cmd: "register",
system: 1000,
access_token: ACCESS_TOKEN
}
let dStr = JSON.stringify(d)
console.log('webSocket-Connection-established')
ws.send(dStr)
}
  • readyState屬性 與 readyStateChange事件

與ajax相似,ws實例通過readyState屬性表示當前鏈接狀態,並且當readyState發生變更時會觸發readyStateChange事件,ws實例注冊該事件的handler函數爲onreadyStateChange屬性。

+ 0 :對應常量CONNECTING (numeric value 0),
正在建立連接連接,還沒有完成。The connection has not yet been established.
+ 1 :對應常量OPEN (numeric value 1),
連接成功建立,可以進行通信。The WebSocket connection is established and communication is possible.
+ 2 :對應常量CLOSING (numeric value 2)
連接正在進行關閉握手,即將關閉。The connection is going through the closing handshake.
+ 3 : 對應常量CLOSED (numeric value 3)
連接已經關閉或者根本沒有建立。The connection has been closed or could not be opened.
  • message 事件

WebSocket消息包含來自服務器的數據。message事件在接收到消息時觸發,對應于該事件的回調函數是onmessage。和ajax不同的是,ws獲取的數據將會自動傳入到回調的handler中。如果你需要在收到消息後做一些處理,例如本例子中,我們收到消息後需要調用消息通知彈窗,並且更新store中的未讀消息數量。

ws.onmessage = (e) => {
// 更新store
// 彈窗提示
// 後面有完整示例
}
  • error 事件

error事件在響應意外故障的時候觸發。與該事件對應的回調函數爲onerror。錯誤還會導致WebSocket連接關閉。如果你接收一個error事件,可以預期很快就會觸發close事件。close事件中的代碼和原因有時候能告訴你錯誤的根源。error事件處理程序是調用服務器重連邏輯以及處理來自WebSocket對象的異常的最佳場所。

  • close事件

close事件在WebSocket連接關閉時觸發。對應于close事件的回調函數是onclose。一旦連接關閉,客戶端和服務器不再能接收或者發送消息。

客戶端主動斷開ws

前面我們陳述了如何建立ws連接,但在實際場景中很多時候都需要我們主動斷開連接。例如用戶退出登錄後,我們需要告知服務端用戶下線了,不必再推送消息了。

ws內置了主動斷開的方法——close方法,注意,和dom事件不用,在dom事件中如果需要直接調用dom的click事件處理函數,我們可以通過dom.onclick()的方式,但是ws直接調用ws.onclose()是無效的。只能調用close方法

ws.close() // 主動斷開ws

在https協議下使用ws

在http協議中,我們使用的ws://ur的方式建立連接,但是如果頁面使用的是https,而websocket使用ws協議將會觸發錯誤導致連接失敗。正確的開啓方式是使用wss://url的方式建立連接。如果需要兼http和https兩種方式,你可以使用以下方式:

const IS_HTTPS = document.location.protocol.includes('https')
let wsProtocol = IS_HTTPS ? 'wss://' : 'ws://'

心跳同步與斷開重連

websocket在建立連接後,可能會因爲網絡抖動或者長時間沒有操作而斷開連接。通常解決這些問題的方案有兩種,心跳同步和斷開重連或者二者兼有的方式。

心跳同步,顧名思義,間隔一定的的時間主動向服務器發送一些數據,以此保持上時間的活動狀態。

斷開重連,在上面介紹事件時已經提及過這個解決方案,當ws因爲異常中斷時會觸發error事件,前端收到error會斷開ws,進而觸發close行爲。所以我們監聽了error事件,並且在其中重啓ws,這裏說重啓是個不大恰當的行爲,所謂重啓是新建一個新的WebScoket實例,而前一個實例已經無法再次使用。

我們使用斷開重連的方式,除非特殊要求,我個人認爲沒有必要增加額外的性能開銷去主動維持連接,反而應該采用被動的方式維護,一旦斷開,前端即刻重啓連接。

這裏還有一點需要注意,如果斷開是偶然的,那麽重連即可成功,若服務端發生了嚴重錯誤,導致無法提供ws服務,那麽不斷的嘗試是沒有意義的。所以在這裏我們還設置了嘗試次數,當達到嘗試上限時仍未能建立連接,我們就停止無效的嘗試。

export const inlineWebSocket = (that, ACCESS_TOKEN) => {
let host = document.location.host
const IS_HTTPS = document.location.protocol.includes('https')
let wsProtocol = IS_HTTPS ? 'wss://' : 'ws://'
let url = `${wsProtocol}${host}`
let time = 0 // 重連次數
let socket = {
init() {
// ws initialize
this.wsUri = url
this.webscoket = new WebSocket(url)
this.webscoket.onopen = (evt) => {
let {readyState} = this.webscoket
if (+readyState === 1) {
time = 0
}
let d = {
cmd: "rgb",
system: 77, //
access_token: ACCESS_TOKEN
}
let dStr = JSON.stringify(d)
console.log('webSocket-Connection-established')
this.webscoket.send(dStr)
}
this.webscoket.onreadyStateChange = () => {
console.log(this.webscoket.readyState)
}
this.webscoket.onclose = () => {
console.log('webSocket close')
if (time >= 3) {
return false
} else {
time++
this.init()
}
}
this.webscoket.onmessage = (e) => {
let isString = typeof e.data === 'string'
let d = isString ? JSON.parse(e.data) : e.data
that.updateUnreadMsgNum({payload}) // 更新未讀消息數量
that.$internalNotify({// some options}) // 通知
}
this.webscoket.onerror = (e) => {
console.log('WebSocket has been shut down in accident,the following is the error emssage,please ask for technological support!!')
console.log('we are trying to reconnect')
}
return this
},
shutWebSocket() {
time = 3
this.webscoket.close()
return this
}
}
return socket
}

上一篇: 如何利用 JS 的 Set 對象讓你的代碼運行的更快

下一篇: 返回列表