/JavaScript, WebSocket

JavaScript | WebSocket 讓前後端沒有距離

前言

最近因為工作的關係接觸到 WebSocketWebSocket 是網路協定的一種, Client 可以透過此協定與 Server 做溝通,而他和一般 httphttps 不同的是, 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 可以先參考「」。

處理好安裝環境後還需要在下載兩個套件,分別是用來開發 Web 框架的 express 和負責處理 WebSocket 協定的 ws

npm install express
npm install ws

安裝完後可以到專案中的 package.json 中確認是否成功:

下載完後的套件會被記錄到 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 被打開時,會先執行我們監聽指定的事件:

開啟成功後會在 console 中打印提示

Client 端 - 連接 WebSocket Server

Server 端處理完後,就換到 Client 端來連結剛剛開啟的 WebSocket 服務,這裡另外建一個專案,在專案裡只需一個 index.htmlindex.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 ,另外的 onopenonclose 分別為他們指定一個 Function ,在開啟和關閉連線時執行,執行結果:

開啟連結後會執行 onopen 的事件

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

在 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 回傳的資料會在 eventdata 屬性中。

但是上方的程式碼還沒有增加在 Client 中發送訊息的 send ,因此下方在連接到 WebSocket 後直接在 console 中發送,並確認回傳訊息:

Server 接收 Client 發送的訊息後再回傳

不過上面看起來還是以 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 主動傳遞的訊息

最後一次回到 Server - 多人連線

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

例如以下例子,用兩個視窗開啟 Client 並各自發送請求:

客戶端無法共享 Server 發送的訊息

那該怎麼像廣播一樣,當我在某一個 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 協定的傳輸方式

關於 WebSocket 從 Client 或是 Server 在 send 資料時,除了字串外還可以使用 等型態(這部分感謝 留言提醒)。

另外,要傳送 JSON 的資料的時,記得在 send 中做 JSON.stringify ,接收到時再用 JSON.parse 轉成物件處理即可!

如果想找個伺服器部署 WebSocket ,可以參考「


本文介紹了 WebSocket 的基本使用方式及一些例子,希望能夠減少各位研究的時間,這幾天還會接著研究如何搭配 React

如果文章中有任何問題,或是不理解的地方,都可以留言告訴我!謝謝大家!

參考文章