JavaScript | WebSocket 讓前後端沒有距離
前言
最近因為工作的關係接觸到 WebSocket , WebSocket 是網路協定的一種, Client 可以透過此協定與 Server 做溝通,而他和一般 http 或 https 不同的是, WebSocket 協定只需透過一次連結便能保持連線,不必再透過一直發送 Request 來與 Server 互動!
WebSocket
如前言所說, WebSocket 只需開啟連結便能和 Server 做溝通,且 Websocket 傳送資料的方式是雙向的, Client 端可以像 Ajax 一樣做請求, Server 端也能主動發送 Client 所需要的資料。
一般的 WebSocket 請求網址會長這個樣子:
ws://example.com//經過 SSL 加密後,前方的 ws 會變成 wss
wss://example.com
### Server 端 - 搭建 WebSocket 環境
WebSocket 的 Server 部分,本文會以 Node.js 建置,如果電腦上還沒有安裝 Node.js 可以先參考「[筆記][node.js]第一次建置node.js開發環境和安裝npm就上手!」。
處理好安裝環境後還需要在下載兩個套件,分別是用來開發 Web 框架的 express 和負責處理 WebSocket 協定的 ws :
npm install express
npm install ws
安裝完後可以到專案中的 package.json 中確認是否成功:

接著在專案裡新增一個 JavaScript 檔案 server.js 當作專案的進入點:
//import express 和 ws 套件
const express = require('express')
const SocketServer = require('ws').Server
//指定開啟的 port
const PORT = 3000
//創建 express 的物件,並綁定及監聽 3000 port ,且設定開啟後在 console 中提示
const server = express()
.listen(PORT, () => console.log(`Listening on ${PORT}`))
//將 express 交給 SocketServer 開啟 WebSocket 的服務
const wss = new SocketServer({ server })
//當 WebSocket 從外部連結時執行
wss.on('connection', ws => {
//連結時執行此 console 提示
console.log('Client connected')
//當 WebSocket 的連線關閉時執行
ws.on('close', () => {
console.log('Close connected')
})
})沒問題後便可以輸入以下指令執行 server.js :
node server.js
當本機 Server 的指定 Port 被打開時,會先執行我們監聽指定的事件:

Client 端 - 連接 WebSocket Server
Server 端處理完後,就換到 Client 端來連結剛剛開啟的 WebSocket 服務,這裡另外建一個專案,在專案裡只需一個 index.html 及 index.js :
index.html 的部分先簡單處理,只需引用 index.js 就可以了:
<html>
<body>
<script src='./index.js'></script>
</body>
</html>index.js 的部分會用來處理與 WebSocket 的連結:
//使用 WebSocket 的網址向 Server 開啟連結
let ws = new WebSocket('ws://localhost:3000')
//開啟後執行的動作,指定一個 function 會在連結 WebSocket 後執行
ws.onopen = () => {
console.log('open connection')
}
//關閉後執行的動作,指定一個 function 會在連結中斷後執行
ws.onclose = () => {
console.log('close connection')
}上方的 url 為剛剛使用 node.js 在本機上執行的 Server ,另外的 onopen 及 onclose 分別為他們指定一個 Function ,在開啟和關閉連線時執行,執行結果:

執行結果中可以看到 onopen 中在 console 打印的提示,除此之外,也可以從剛剛執行 Server 的地方觀察開啟連結後的訊息:

上方也列出 WebSocket 的物件有哪些屬性,比較重要的還有 onmessage , Client 就是靠它在接收由 Server 發送的資料,但在提到它之前,得先回到 Server 了解如何和 Client 做溝通。
回到 Server 端 - 處理接收發送訊息
提到溝通,過程一定是有來有往,在開啟 WebSocket 後, Server 端會使用 send 發送訊息,接收則是如同在 connection 內監聽 close 一樣,只是換成對 message 設定監聽,並接收一個參數 data ,捕獲 Client 端發送的訊息:
wss.on('connection', ws => {
console.log('Client connected')
//對 message 設定監聽,接收從 Client 發送的訊息
ws.on('message', data => {
//data 為 Client 發送的訊息,現在將訊息原封不動發送出去
ws.send(data)
})
ws.on('close', () => {
console.log('Close connected')
})
})回到 Client 端 - 處理接收發送訊息
剛剛處理完 Server ,要換回 Client 端使用 onmessage 處理接收及 send 送出訊息:
let ws = new WebSocket('ws://localhost:3000')
ws.onopen = () => {
console.log('open connection')
}
ws.onclose = () => {
console.log('close connection')
}
//接收 Server 發送的訊息
ws.onmessage = event => {
console.log(event)
}onmessage 指定的函式中多了一個參數 event ,裡面會有這次溝通的詳細訊息,從 Server 回傳的資料會在 event 的 data 屬性中。
但是上方的程式碼還沒有增加在 Client 中發送訊息的 send ,因此下方在連接到 WebSocket 後直接在 console 中發送,並確認回傳訊息:

不過上面看起來還是以 Client 做 send 發送訊息給 Server 處理過才得到回傳資料,該怎麼從 Server 上直接發送呢?很簡單,只需要透過 setInterval 就能讓 Server 在固定時間發送資料給 Client ,例如下方的例子:
wss.on('connection', ws => {
console.log('Client connected')
//固定送最新時間給 Client
const sendNowTime = setInterval(()=>{
ws.send(String(new Date()))
},1000)
ws.on('message', data => {
ws.send(data)
})
ws.on('close', () => {
//連線中斷時停止 setInterval
clearInterval(sendNowTime)
console.log('Close connected')
})
})Client 連結後的結果如下:

最後一次回到 Server - 多人連線
通常 WebSocket 都會運用在聊天室,但是就剛剛的使用方式來說,今天 ClientA 和 ClientB 都連結同一個 Server ,而他們各自在 Client 使用 send 發送資料給 Server , 在這個情況下 Server 只會依據兩個 Client 各自發送的內容,再分別回傳給 ClientA 和 ClientB ,並無法讓 ClientB 能夠在 ClientA 發送訊息時也收到回傳的資料。
例如以下例子,用兩個視窗開啟 Client 並各自發送請求:

那該怎麼像廣播一樣,當我在某一個 Client 發送訊息時,讓 Server 告知所有其他同時連接中的 Client 都知道我對 Server 發送這個訊息,也同時接收到 Server 回傳的資料呢?
答案就在一開始下載的套件 ws 中,它可以使用 clients 找出當前所有連結中的 Client 資訊,並透過迴圈將訊息發送至每一個 Client 中:
wss.on('connection', ws => {
console.log('Client connected')
ws.on('message', data => {
//取得所有連接中的 client
let clients = wss.clients
//做迴圈,發送訊息至每個 client
clients.forEach(client => {
client.send(data)
})
})
ws.on('close', () => {
console.log('Close connected')
})
})這麼一來,不論是哪個 Client 端發送訊息, Server 都會將訊息回覆給所有連接中的 Client :

補充內容
雖然在 WebSocket 的協定上 Client 和 Server 不需再通過 Request ,因此在開發人員工具中的 Network 中就看不到 Request 的資料,但是取而代之的是,那些傳遞過程可以透過第一次要求連接時的 Request 中觀察:

關於 WebSocket 從 Client 或是 Server 在 send 資料時,除了字串外還可以使用 USVString 、 ArrayBuffer 、 Blod 和 ArrayBufferView 等型態(這部分感謝 Hank Hsiao 留言提醒)。
另外,要傳送 JSON 的資料的時,記得在 send 中做 JSON.stringify ,接收到時再用 JSON.parse 轉成物件處理即可!
如果想找個伺服器部署 WebSocket ,可以參考「Heroku | 搭配 Git 在 Heroku 上部署網站的手把手教學」
本文介紹了 WebSocket 的基本使用方式及一些例子,希望能夠減少各位研究的時間,這幾天還會接著研究如何搭配 React 。
如果文章中有任何問題,或是不理解的地方,都可以留言告訴我!謝謝大家!
參考文章