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
。
如果文章中有任何問題,或是不理解的地方,都可以留言告訴我!謝謝大家!
參考文章