WebSocket其實沒那麼難

寫在前面

webSocket是一項可讓服務器將數據主動推送給客戶端的技術。前幾天寫了一個日誌功能,日誌數據須要實時更新。正好項目中有封裝好的WebSocket組件,且接口支持webSocket,就用它實現了。也是第一次用,簡單研究了一下,分享出來。
文章示例代碼:https://github.com/neroneroff...html

什麼是WebSocket

首先須要明白webSocket的概念,下邊是維基百科的解釋前端

WebSocket是一種通訊協議,可在單個TCP鏈接上進行全雙工通訊。WebSocket使得客戶端和服務器之間的數據交換變得更加簡單,
容許服務端主動向客戶端推送數據。在WebSocket API中,瀏覽器和服務器只須要完成一次握手,二者之間就能夠創建持久性的鏈接,
並進行雙向數據傳輸。

首先,要明白WebSocket是一種通訊協議,區別於HTTP協議,HTTP協議只能實現客戶端請求,服務端響應的這種單項通訊。
而WebSocket能夠實現客戶端與服務端的雙向通信,說白了,最大也是最明顯的區別就是能夠作到服務端主動將消息推送給客戶端。git

其他的特色有:github

  • 握手階段採用 HTTP 協議。
  • 數據格式輕量,性能開銷小。客戶端與服務端進行數據交換時,服務端到客戶端的數據包頭只有2到10字節,客戶端到服務端須要加上另外4字節的掩碼。
    HTTP每次都須要攜帶完整頭部。
  • 更好的二進制支持,能夠發送文本,和二進制數據
  • 沒有同源限制,客戶端能夠與任意服務器通訊
  • 協議標識符是ws(若是加密,則是wss),請求的地址就是後端支持websocket的API。

幾種與服務端實時通訊的方法

咱們都知道,不使用WebSocket與服務器實時交互,通常有兩種方法。AJAX輪詢和Long Polling長輪詢。web

AJAX輪詢

AJAX輪詢是定時發送請求,也就是普通的客戶端與服務端通訊過程,只不過是無限循環發送,這樣,能夠保證服務端一旦有最新消息,就能夠被客戶端獲取。express

Long Polling長輪詢

Long Polling長輪詢是客戶端和瀏覽器保持一個長鏈接,等服務端有消息返回,斷開。
而後再從新鏈接,也是個循環的過程,無窮盡也。。。npm

客戶端發起一個Long Polling,服務端若是沒有數據要返回的話,
會hold住請求,等到有數據,就會返回給客戶端。客戶端又會再次發起一次Long Polling,再重複一次上面的過程。後端

缺點

上邊這兩種方式都有個致命的弱點,開銷太大,被動性。假設併發很高的話,這對服務端是個考驗。
而WebSocket一次握手,持久鏈接,以及主動推送的特色能夠解決上邊的問題,又不至於損耗性能。瀏覽器

WebSocket鏈接過程

客戶端發起HTTP握手,告訴服務端進行WebSocket協議通信,並告知WebSocket協議版本。服務端確認協議版本,升級爲WebSocket協議。以後若是有數據須要推送,會主動推送給客戶端。服務器

鏈接開始時,客戶端使用HTTP協議和服務端升級協議,升級完成後,後續數據交換遵循WebSocket協議。咱們看看Request Headers

Accept-Encoding: gzip, deflate, br
Accept-Language: zh,zh-TW;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6
Cache-Control: no-cache
Connection: Upgrade
Host: 127.0.0.1:3000
Origin: http://localhost:3000
Pragma: no-cache
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: bwb9SFiJONXhQ/A4pLaXIg==
Sec-WebSocket-Version: 13
Upgrade: websocket

重點字段是這些:

  • Connection: Upgrade 表示要升級協議
  • Upgrade: websocket 要升級協議到websocket協議
  • Sec-WebSocket-Version 表示websocket的版本。若是服務端不支持該版本,須要返回一個Sec-WebSocket-Versionheader,裏面包含服務端支持的版本號。
  • Sec-WebSocket-Key 對應服務端響應頭的Sec-WebSocket-Accept,因爲沒有同源限制,websocket客戶端可任意鏈接支持websocket的服務。這個就至關於一個鑰匙一把鎖,避免多餘的,無心義的鏈接。

再看看看服務端響應的 Response Headers

Connection: Upgrade
Sec-WebSocket-Accept: 2jrbCWSCPlzPtxarlGTp4Y8XD20=
Upgrade: websocket

關鍵是這個字段

  • Sec-WebSocket-Accept: 用來告知服務器願意發起一個websocket鏈接, 值根據客戶端請求頭的Sec-WebSocket-Key計算出來

WebSocket API

客戶端若想要與支持webScoket的服務器通訊,可使用WebSocket構造函數返回WebSocket對象。

const ws = new WebSocket("ws://localhost:3000/websocket");

這樣,客戶端就會與服務端開始鏈接。

返回的實例對象的屬性:

  • WebSocket.onopen: 鏈接成功後的回調
  • WebSocket.onclose: 鏈接關閉後的回調
  • WebSocket.onerror: 鏈接失敗後的回調
  • WebSocket.onmessage: 客戶端接收到服務端數據的回調
  • webSocket.bufferedAmount: 未發送至服務器的二進制字節數
  • WebSocket.binaryType: 使用二進制的數據類型鏈接
  • WebSocket.protocol : 服務器選擇的下屬協議
  • WebSocket.url : WebSocket 的絕對路徑
  • WebSocket.readyState: 當前鏈接狀態,對應的四個常量
名稱
WebSocket.CONNECTING 0
WebSocket.OPEN 1
WebSocket.CLOSING 2
WebSocket.CLOSED 3

方法:

  • WebSocket.close() 關閉當前鏈接
  • WebSocket.send(data) 向服務器發送數據

示例

講了那麼多概念之後,終於能夠看看怎麼用了。實現WebSocket通訊,須要客戶端和服務端配合。

本身寫了一個例子,服務端在開始鏈接後,利用定時器主動向客戶端發送隨機數,客戶端也能夠發給服務器消息,
而後服務器返回這條消息給客戶端。客戶端就是js+html,服務端用了express + express-ws來實現。
代碼在這裏:https://github.com/neroneroff... 能夠clone下來,安裝依賴,npm start運行看下效果。

圖片描述

客戶端

前端頁面,最終效果如以上效果圖:

<body>
  <div class="websocket">
    <div class="receive">
      <p>服務端返回的消息</p>
      <div id="receive-box"></div>
    </div>
    <div class="send">
      <textarea type="text" id="msg-need-send"></textarea>
      <p>
        <button id="send-btn">點擊發消息給服務端</button>
      </p>
    </div>
    <button id="exit">關閉鏈接</button>
  </div>
</body>

js,使用webSocket的代碼都在這裏。作的事情就是給頁面的元素綁定事件。
而後建立WebSocket對象,監聽對象的鏈接、接收消息、關閉等事件,將數據反饋到頁面中

const msgBox = document.getElementById("msg-need-send")
const sendBtn = document.getElementById("send-btn")
const exit = document.getElementById("exit")
const receiveBox = document.getElementById("receive-box")

// 建立一個webSocket對象
const ws = new WebSocket("ws://127.0.0.1:3000/websocket/test")
ws.onopen = e => {
  // 鏈接後監聽
  console.log(`WebSocket 鏈接狀態: ${ws.readyState}`)
}

ws.onmessage = data => {
  // 當服務端返回數據的時候,放到頁面裏
  receiveBox.innerHTML += `<p>${data.data}</p>`
  receiveBox.scrollTo({
    top: receiveBox.scrollHeight,
    behavior: "smooth"
  })
}

ws.onclose = data => {
  // 監聽鏈接關閉
  console.log("WebSocket鏈接已關閉")
  console.log(data);
}

sendBtn.onclick = () => {
  // 點擊發送按鈕。將數據發送給服務端
  ws.send(msgBox.value)
}
exit.onclick = () => {
  // 客戶端主動關閉鏈接
  ws.close()
}

服務端

考慮到了模塊化開發,沒有直接把代碼放到直接建立服務的文件中。而是使用了路由,給webSocket服務分配一個單獨的接口

const express = require("express");
const expressWs = require("express-ws")
const router = express.Router()
expressWs(router);

router.ws("/test", (ws, req) => {
  ws.send("鏈接成功")
  let interval
  // 鏈接成功後使用定時器定時向客戶端發送數據,同時要注意定時器執行的時機,要在鏈接開啓狀態下才能夠發送數據
  interval = setInterval(() => {
    if (ws.readyState === ws.OPEN) {
      ws.send(Math.random().toFixed(2))
    } else {
      clearInterval(interval)
    }
  }, 1000)
  // 監聽客戶端發來的數據,直接將信息原封不動返回回去
  ws.on("message", msg => {
    ws.send(msg)
  })
})


module.exports = router

最後看一下數據交互的過程

圖片描述

總結

上邊簡單實現了一個webSocket通訊。實際的東西還有不少,好比webSocket擴展,心跳檢測,數據加密,身份認證等知識點。但本身也須要再去研究,因此先不作介紹了。

相關文章

WebSocket協議:5分鐘從入門到精通

WebSocket 教程--阮一峯

WebSocket API

express-ws文檔

歡迎關注個人公衆號: 一口一個前端,不按期分享我所理解的前端知識

clipboard.png

相關文章
相關標籤/搜索