/JavaScript, WebSocket

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 可以先參考「」。

處理好安裝環境後還需要在下載兩個套件,分別是用來開發 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.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 的事件

執行結果中可以看到 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 回傳的資料會在 event 的 data 屬性中。

但是上方的程式碼還沒有增加在 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 。

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

參考文章