用 Express 建立一個簡易 WebSocket 聊天室

WebSocket

前言

這一篇是來簡單記錄一下如何建立一個簡易的 WebSocket 聊天室,而這邊主要會使用 Express 來當作範例,後續也會提供一個簡易的範例程式碼,讓大家可以參考。

什麼是 WebSocket?

WebSocket 是一個全新的網路傳輸協定,與 HTTP 協定最大不同在於 WebSocket 是屬於全雙工的的通訊,什麼意思呢?當我們畫面有任何操作行為時,都必須要先跟後端伺服器要求資料,舉例來講,假設畫面上我們要新增一筆資料時,我們就會跟後端請求新增資料的行為,新增完畢後再請求一次資料,這樣的行為就是屬於單向的通訊。

而 WebSocket 就跟原本 HTTP 單向通訊不同了,當 WebSocket 建立起交握後,接下來每一次的傳輸都是雙向的,也就是說,當我們新增一筆資料時,後端會主動推送資料給前端,而不需要前端再去請求一次,這樣的行為就是屬於雙向的通訊。

準備專案

基本上這邊我們為了方便練習以及起手,所以一率都會使用 Express 產生器來快速建立專案

1
express --view=ejs express-websocket

接著我們會使用到一個套件,這個套件是用來處理 WebSocket 的,所以我們先安裝一下

1
npm i ws

到目前為止,我們專案的初步準備就完成了,接下來我們就來開始實作吧!

後端實作

首先一開始我們要先來實作後端的部分,目前我們專案的目錄結構如下

  • bin
  • node_modules
  • public
  • routes
  • views
  • app.js
  • package.json
  • package-lock.json

請你在當前根目錄下新增一個資料夾,名稱為 websocket,並在裡面新增一個檔案,名稱為 index.js,接下來呢?在官方 ws 文件中有提供範例程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', function connection(ws) {
ws.on('error', console.error);

ws.on('message', function message(data) {
console.log('received: %s', data);
});

ws.send('something');
});

那由於 Express 產生器主要是走 CommonJS 的方式,所以我們就將上面的程式碼改成 CommonJS 的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// websocket/index.js
const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', function connection(ws) {
ws.on('error', console.error);

ws.on('message', function message(data) {
console.log('received: %s', data);
});

ws.send('something');
});

接下來打開 app.js,因為這是我們程式碼的進入點,所以我們要在這邊引入我們的 WebSocket 服務,並且啟動:

1
2
3
4
// app.js
const express = require('express');
...
require('./websocket');

那麼到目前為主,我們的後端就完成了,接下來我們就來實作前端的部分吧!

前端實作

那麼前端不像後端額外安裝套件,因為 WebSocket 是屬於瀏覽器的 API,所以我們是可以在前端直接使用的,而語法也很簡單,只需要這樣寫就可以了

1
const ws = new WebSocket('ws://localhost:8080');

這邊有一個地方很有趣,也就是 ws://localhost:8080 這邊,以往我們在與後端做 AJAX 請求時都會是 https://example.com,而因為 WebSocket 兩者傳輸協定的不同,因此才會改用 ws://...,而這邊對應的 Port 主要是對應後端的 const wss = new WebSocketServer({ port: 8080 });

接下來讓我們打開 views/index.ejs 檔案,你應該只會看到以下

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
</body>
</html>

然後在底下增加一個 <script> 標籤,並貼入以下程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<div id="content">
</div>
<input type="text" id="input">
<button type="button" id="btn">送出</button>

<script>
const ws = new WebSocket('ws://localhost:8080');

// Connection opened
ws.addEventListener("open", (event) => {
socket.send("Hello Server!");
});

// Listen for messages
ws.addEventListener("message", (event) => {
console.log("Message from server ", event.data);
});
</script>
</body>
</html>

這邊我們可以看到我們畫面上新增了 #contentinputbutton 元素,接著你會發現我們從進入網頁那一刻,就已經開始與後端建立連線了,而當連線成功時,我們就會送出一個訊息給後端,並且監聽後端傳來的訊息,當後端傳來訊息時,我們就會在 console 中印出來。

接下來我們要來將前端改寫成一個簡單的聊天室,首先我們要先將 #btnclick 事件綁定,並且在點擊時,將 #input 的值送出去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const ws = new WebSocket('ws://localhost:8080');

// Connection opened
ws.addEventListener("open", (event) => {
socket.send("Hello Server!");
});

// Listen for messages
ws.addEventListener("message", (event) => {
console.log("Message from server ", event.data);
});

document.querySelector('#btn').addEventListener('click', () => {
const value = document.querySelector('#input').value;
ws.send(value);
});

接下來你只要輸入內容並點一下按鈕就會觸發後端的顯示。

那接下來我們該如何實作成聊天室呢?由於這邊只是一個簡單示範,所以我們會將資料暫時儲存在後端記憶體內,以便我們可以在後端將資料傳給所有連線的使用者,所以這邊就要來修改一下後端的程式碼,這邊也不用擔心看不懂,我會增加註解說明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// websocket/index.js
const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

// 暫存訊息放置處
const messages = [];

wss.on('connection', function connection(ws) {
ws.on('error', console.error);

ws.on('message', function message(data) {
// 由於 data 是 Buffer,所以要使用 toString 轉成字串
messages.push(data.toString());

// 將所有連線的 client 傳送訊息
wss.clients.forEach((client) => {
// 由於 messages 往前端傳送時,會是 Blob,所以要先轉成字串
client.send(JSON.stringify(messages) || []);
});
});

// 當連線時,將所有訊息傳送給連線的 client,所以算是初始化訊息
ws.send(JSON.stringify(messages) || []);
});

接著就是來調整前端的程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const ws = new WebSocket('ws://localhost:8080');

// Connection opened
ws.addEventListener("open", (event) => {
// 初始化時,就發送一個訊息給後端,這邊先註解
// ws.send("Hello Server!");
});

// Listen for messages
ws.addEventListener("message", (event) => {
console.log("Message from server ", JSON.parse(event.data));
// 取得 #content 元素
const content = document.querySelector('#content');
// 將 WebSocket 傳來的資料轉成陣列,並且將每個元素包在 <p> 內
const data = JSON.parse(event.data);
// 將資料放入 #content 中
content.innerHTML = data.map((text) => `<p>${text}</p>`).join('');
});

document.querySelector('#btn').addEventListener('click', () => {
// 取得 #input 元素的值
const value = document.querySelector('#input').value;
// 將資料送出
ws.send(value);
});

那麼這樣子你就可以做出一個超級簡易的聊天室囉~

範例

最後也提供一下這個範例的完整程式碼,有興趣的人可以參考看看。

結語

其實 WebSocket 用途還有很多,像是遊戲、即時通訊等等,這邊只是簡單的介紹一下,如果你有製作登入系統的話,就可以搭配上暱稱等等,所以其實 WebSocket 還滿有趣的。

Liker 讚賞

這篇文章如果對你有幫助,你可以花 30 秒登入 LikeCoin 並點擊下方拍手按鈕(最多五下)免費支持與牡蠣鼓勵我。
或者你可以也可以請我「喝一杯咖啡(Donate)」。

Buy Me A Coffee Buy Me A Coffee

Google AD

撰寫一篇文章其實真的很花時間,如果你願意「關閉 Adblock (廣告阻擋器)」來支持我的話,我會非常感謝你 ヽ(・∀・)ノ