※ 先建立基本的express後端服務:
1.建立新資料夾:Socket
mkdir socket
2.進入資料夾:Socket
cd bsocket
3. 安裝 Experss 到專案中npm init -y //初始化專案,建立 package.json 檔
npm install express
4.在 package.json 中type預設使用 CommonJS 模式改為 module
"keywords": [],
"author": "",
"license": "ISC",
"type": "module",
"description": "",
"dependencies": {
"express": "^5.1.0"
}
5.修改腳本
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
6.建立index.js:寫入基本的伺服器程式碼。
import express from 'express';
import http from 'http';
const app = express();
const server = http.createServer(app);
// 提供靜態檔案服務
app.use(express.static('public'));
// 啟動伺服器
server.listen(3000, () => {
console.log('Server started on port 3000');
});
7.啟動express
npm run dev

8.建立靜態資料夾:public --> index.html
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
</head>
<body>
<p>index.html</p>
</body>
</html>
※ 在前端引用Socket.io的套件:
★ 最基本的html + Javascript環境中的使用套件:
透過Socket.io官方提供的CDN,將程式碼抓取下來。public --> index.html
<head>
...
<script src="/socket.io/socket.io.js"></script>
</head>
※ 在後端引用Socket.io的套件:
npm install socket.io
※ 撰寫index.js程式碼內容:建立一個即時通訊伺服器
//引入 Socket.io 模組:
import { Server } from 'socket.io';
//初始化 Socket.io 伺服器:
const io = new Server(server);
※ 撰寫index.js程式碼內容:監控客戶端的連線狀態
io.on('connect', socket => {
console.log(`${socket.id} connected.`)
})
程式碼解說:
io.on('connect', ...)
:
io
:是經由Socket.io
初始化的伺服器物件。.on('connect', callback)
:表示當客戶端連接到伺服器時,會觸發connect
事件。callback
:是一個回呼函式,在客戶端成功連接時執行。- 箭頭函式接受一個參數 socket,代表與該客戶端相關的通訊物件。
socket.id
:在使用Socket.io時,每個建立的連線都會自動被分配一個唯一的id,用於辨別不同的連線。- 在主控台輸出訊息,記錄每個客戶端成功連接到伺服器的時刻,便於監控和除錯。
※ 撰寫index.html程式碼內容:在前端建立連線
<script>
//建立連線
const socket = io();
</script>
程式碼解說:
建立連線:使用 Socket.io 的客戶端庫來初始化與伺服器的連線。
運作原理:前端使用const socket = io()送出連線請求,後端的Socket.io伺服器接收請求後,透過監聽connection事件,存取每個連線的socket.id並將id印出來。
id印出結果:

※ 撰寫index.html程式碼內容:顯示前端是誰
<body>
<!-- 顯示id -->
<p style="opacity: 0.5; font-size: 18px; margin: 0;">your id is</p>
<p style="margin: 0; font-size: 24px;" id="user-id"></p>
</body>
※ 撰寫index.html程式碼內容:在建立連線後顯示在指定的 HTML 元素上
<script>
//在建立連線後顯示在指定的 HTML 元素上
socket.on('connect', () => {
document.getElementById('user-id').innerText = socket.id
})
</script>
程式碼解說:
socket.on('connect', ...)
是用於監聽 Socket.io 的connect
事件。- 每個建立的連線都會分配一個唯一的
socket.id
,它是用來識別該連線的。 document.getElementById('user-id').innerText = socket.id
是用來取得 HTML 中具有id="user-id"
的元素。- 然後將該元素的文字內容更新為當前的
socket.id
。
id印出結果:

※ 撰寫index.html程式碼內容:新增和加入房間按鈕
<body>
....
<!--創建房間按鈕 -->
<input id="create-room-nameinput">
<button onclick="onCreateRoom()">Create Room</button>
<!--加入房間按鈕 -->
<input id="join-room-name-input">
<button onclick="onJoinRoom()">Join Room</button>
</body>
按鈕結果:

※ 撰寫index.html程式碼內容:新增和加入房間功能
//建立房間功能
function onCreateRoom() {
//幫房間取名字
const roomId = document.getElementById('create-room-name-input').value;
//建立取名字事件告訴後端
socket.emit('create-room', roomId);
}
程式碼解說:
- 從 HTML 文件中的一個輸入欄位獲取使用者輸入的值。
document.getElementById
是 JavaScript 提供的一個方法,用來選取 HTML 文件中擁有特定id
的元素。- 這裡的
'create-room-name-input'
是目標元素的id
,該元素是表單欄位。 value
是用來存取或設定輸入元素(例如input )的內容,這將取得使用者在這個輸入欄位中輸入的文字。- 使用
const
宣告的變數roomId
會存儲從輸入欄位取得的值。 - 利用
socket.emit
方法,通過 WebSocket 連線向伺服器發送一個事件。 socket.emit
是 Socket.IO 的一個方法,用來觸發事件並將其傳遞到伺服器端。- 第一個參數(這裡是
'create-room'
)是事件名稱,表明該事件的目的是「建立房間」,伺服器可以針對該事件名稱進行監聽和處理。 - 第二個參數(這裡是
roomId
)是附帶的資料,用於傳遞給伺服器;它告訴伺服器應該為該名稱建立一個新的房間。
//加入房間功能
function onJoinRoom() {
const roomId = document.getElementById('create-room-name-input').value;
}
程式碼解說:
※ 撰寫index.js程式碼內容:後端接受前端發起的事件
//後端接受前端發起的事件
socket.on('create-room', (roomId) => {
console.log(`${socket.id} create a room with id ${roomId}`);
//加入房間
socket.join(roomId)
//通知對方加入房間結果
socket.emit('room-joined', roomId)
})
})
程式碼解說:
socket.on
是 Socket.IO 的方法,用於監聽由客戶端發送的事件。- 第一個參數(
'create-room'
)是事件名稱。伺服器會監聽名稱為'create-room'
的事件。 - 第二個參數是一個回調函式(
callback
),當事件被觸發時,這個函式會被執行。 - 當
'create-room'
事件被觸發時,伺服器會執行這個回調函式。 roomId
是從客戶端發送來的資料(例如房間名稱),作為事件的參數。- 伺服器端的控制台中輸出
'create room'
和房間的 ID 或名稱(roomId
)相關資訊,方便開發者檢查事件的執行狀態。
創建房間結果:網頁重新整理後

socket.join(roomId)
的作用是在伺服器端,將該 Socket 連線分配到指定的房間。roomId
是房間的唯一識別符號,用戶端或伺服器可以用此 ID 來將 Socket 分組到特定的「房間」中。- 每個房間都有自己的名稱(或 ID),連線可以自由加入或離開。
socket.emit
是 Socket.IO 的方法,用於從客戶端發送事件到伺服器。- 第一個參數是事件名稱(
'room-joined'
),表明這是一個「客戶端已加入某個房間」的通知。 - 第二個參數是附帶的資料(這裡是
roomId
),通常代表已加入的房間名稱或 ID。
※ 撰寫index.html程式碼內容:建立成功加入房間訊息
<script>
...
//後端通知前端加入房間
socket.on('room-joined', (roomId) => {
document.getElementById('current-room-id').innerText = roomId;
})
</script>
程式碼解說:
socket.on
是用於監聽事件的方法,當指定的事件被伺服器觸發時,執行對應的回調函式。- 第一個參數是事件名稱(
'room-joined'
),當事件觸發時,roomId
(房間 ID)被作為參數傳遞給回調函式。 - 第二個參數是回調函式,在事件觸發時顯示一個彈出對話框,告訴用戶他們已加入的房間名稱。
- 根據變數
roomId
的值,動態修改網頁上id="current-room-id"
的元素所顯示的內容。 - document.getElementById是 JavaScript 中用來選擇 HTML 文件中的特定元素。
- 這裡的
current-room-id
即是要尋找的id
。 - 把變數
roomId
的值指派給innerText
這個屬性,會將該 HTML 元素內部的文字內容更新為roomId
的值。
加入房間結果:網頁重新整理後

※ 撰寫index.html程式碼內容:加入哪個房間
<body>
...
<!--加入哪個房間 -->
<p style="opacity: 0.5; font-size: 18px; margin: 0;">Current room</p>
<p style="margin: 0; font-size: 24px;" id="current-room-id"></p>
</body>
※ 撰寫index.js程式碼內容:後端檢查是否重複命名
socket.on('create-room', (roomId) => {
//檢查是否重複命名
if (io.sockets.adapter.rooms.get(roomId)) {
socket.emit('room-create-failed', 'room id has been taken')
return;
}
})
程式碼解說:
io.sockets.adapter.rooms
是 Socket.IO 的房間管理器,用來追蹤所有已建立的房間。.get(roomId)
是用來檢查名為roomId
的房間是否已經存在。- 如果該房間存在,這個方法會返回一個物件(或房間的相關資訊);如果不存在,則返回
undefined
。 socket.emit
是用來向當前連接的客戶端(即請求建立房間的用戶)發送一個事件,事件名稱是'room-create-failed'
,表示房間創建失敗。- 第二個參數
'room id has been taken'
是傳遞給客戶端的消息,告訴用戶該房間 ID 已被占用。 return
用於終止函數的執行,防止後續邏輯執行。
※ 撰寫index.html程式碼內容:前端檢查是否重複命名
<script>
//檢查是否重複命名
socket.on('room-create-failed', (reason) => {
alert(`Create room failed because ${reason}`)
})
</script>
檢查是否重複命名結果:網頁重新整理後

※ 撰寫index.html程式碼內容:加入房間功能
//加入房間功能
function onJoinRoom() {
const roomId = document.getElementById('join-room-name-input').value;
//加入事件告訴後端
socket.emit('join-room', roomId);
}
程式碼解說:
document.getElementById('join-room-name-input').value
:
- 它透過 DOM 獲取 HTML 中具有 ID
join-room-name-input
的輸入框的值,並將其存入roomId
變數中。 - 這個值是使用者手動輸入的房間名稱或 ID,目的是指向具體的聊天房間。
socket.emit('join-room', roomId);
:
- 這段是基於 Socket.IO 的語法,用於向伺服器發送自訂的事件。
'join-room'
: 事件名稱,告訴後端此操作是要加入某個房間。roomId
: 傳遞的參數,用來指定目標房間。
※ 撰寫index.js程式碼內容:後端接受前端加入房間事件
//後端接受前端加入房間事件
socket.on('join-room', (roomId) => {
socket.join(roomId);
socket.emit('room-joined', roomId);
})
程式碼解說:
socket.on('join-room', (roomId) => { ... })
:
- 使用 Socket.IO 的
on
方法,註冊一個事件處理器。 - 當前端透過
socket.emit('join-room', roomId)
觸發join-room
事件時,後端就使用 Socket.IO 的 join 方法,將該 socket (代表前端的一個連線)加入到特定的房間,房間的名稱為 roomId。
socket.emit('room-joined', roomId);
:
- 使用 Socket.IO 的
emit
方法向該使用者發送一個事件,名稱為room-joined
。 - 同時將
roomId
傳遞回前端,用來通知使用者他們成功加入了該房間。
加入房間結果:網頁重新整理後

※ 撰寫index.js程式碼內容:即時掌握房間內的新成員資訊
//房間內新加入者資訊
io.to(roomId).emit('user-joined-room', roomId, socket.id)
程式碼解說:
- 向特定的房間 (roomId) 內所有的使用者廣播一個事件。
io.to(roomId)
:這部分使用了 Socket.IO 的功能,將訊息僅發送至指定的房間roomId
。換句話說,只有在這個房間裡的用戶會收到廣播的訊息。.emit('user-joined-room', roomId, socket.id)
:這段是指廣播一個名為user-joined-room
的事件,同時傳送兩個參數:
roomId
:表示該房間的 ID。
socket.id
:表示加入者的唯一 Socket.IO 連線 ID。
※ 撰寫index.html程式碼內容:接受房間內的新成員通知轉告
//接受房間內的新成員通知轉告
socket.on('user-joined-room', (roomId, userId) => {
alert(`${userId} has been joined room`)
})
程式碼解說:
- 當有新的成員加入房間時,客戶端會接收到事件,並透過警示框通知使用者。
- 當
socket
接收到名為user-joined-room
的事件時,會執行後面定義的回呼函式 (callback function
)。通常是由伺服器端使用.emit()
發送的,目的在通知客戶端。 - 回呼函式的參數
roomId
和userId
是事件攜帶的資料(payload)。 roomId
:指定房間的 ID,代表事件涉及哪個房間。userId
:新加入者的使用者 ID。
新成員加入特定房間通知結果:網頁重新整理後

※ 撰寫index.html程式碼內容:製作聊天室的UI
<body>
<!--輸入框打訊息 -->
<input id="message-input">
<!--按鈕發訊息 -->
<button onclick="onSendMessage()">Send Message</button>
<!--顯示所有訊息 -->
<ul id="messages"></ul>
</body>
※ 撰寫index.html程式碼內容:發送訊息功能
function onSendMessage() {
const message = document.getElementById('message-input').value;
const currentRoomId = document.getElementById('current-room-id').innerText;
socket.emit('send-message', currentRoomId, message);
}
程式碼解說:
- const message = document.getElementById('message-input').value;
- 這一行的作用是從 HTML 網頁中取得使用者在輸入框 (id: message-input) 中輸入的訊息文字。
- .value 是用來提取輸入框的文字值。
- const currentRoomId = document.getElementById('current-room-id').innerText;
- 這一行是從 HTML 中的某個元素 (id: current-room-id) 取得目前房間的 ID。
- .innerText 是用於取得該元素的文字內容。
- socket.emit('send-message', currentRoomId, message);
- 這部分使用了 Socket.IO 的 emit() 方法向伺服器發送一個名為 send-message 的事件。
- 此事件攜帶兩個參數:currentRoomId:代表訊息所屬的房間 ID。 message:代表使用者要發送的訊息。
※ 撰寫index.js程式碼內容:後端收到前端發送訊息
//後端收到發送訊息
socket.on('send-message', (roomId, message) => {
io.to(roomId).emit('receive-message',roomId, socket.id, message);
})
程式碼解說:
- 伺服器端處理用戶發送訊息的一部分。
- socket.on('send-message'}):當伺服器接收到來自客戶端的
send-message
事件時,觸發回呼函式。 - 回呼函式接收兩個參數:
roomId
:訊息所屬的房間 ID。message
:用戶發送的訊息內容。
- io.to(roomId):部分使用 Socket.IO 的功能,將訊息廣播到指定的房間
roomId,
只有該房間內的所有連線用戶才會收到事件。 - emit('receive-message'):此方法會向房間
roomId
的用戶廣播一個名為receive-message
的事件。 socket.id
: 這是目前連線的客戶端唯一的識別碼,代表是哪個客戶端發送的訊息。message
: 這是具體要發送的訊息內容。
※ 撰寫index.html程式碼內容:前端接收訊息
//接收訊息
socket.on('receive-message', (roomId,userId, message) => {
alert(`${userId} said ${message}`)
})
程式碼解說:
- 當
'receive-message'
事件被觸發,例如伺服器端呼叫io.emit('receive-message', userId, message)
。 - 客戶端接收到
'receive-message'
事件後,執行回呼函數。 - 使用
alert
在網頁上顯示彈出式通知。
收到訊息結果:網頁重新整理後

※ 撰寫index.js程式碼內容:回傳roomId
//後端收到發送訊息
socket.on('send-message', (roomId, message) => {
io.to(roomId).emit('receive-message', roomId, socket.id, message);
})//新增roomId
})
※ 撰寫index.html程式碼內容:回傳roomId
//新增roomId
socket.on('receive-message', (roomId, userId, message) => {
//以一個物件形式去保存歷史資料
messages.push({
roomId,
message: `${userId} said ${message}`
})
※ 撰寫index.html程式碼內容:重新渲染聊天紀錄列表
<body>
...
<!--輸入框打訊息 -->
<input id="message-input" />
<!--按鈕發訊息 -->
<button onclick="onSendMessage()">Send Message</button>
<!--顯示所有訊息 -->
<ul id="messages">
</body>
<script>
//開一個陣列保存歷史資料
const messages = []
...
//接受房間內的新成員通知轉告
socket.on('user-joined-room', (roomId, userId) => {
messages.push({
roomId,
message: `${userId} has joined this room`
})
//收到有人進來時需要重新渲染
renderMessages();
})
//接收訊息
socket.on('receive-message', (roomId, userId, message) => {
//以一個物件形式去保存歷史資料
messages.push({
roomId,
message: `${userId} said ${message}`
})
//收到訊息時需要重新渲染
renderMessages();
})
//顯示當前頻道的訊息;重新渲染聊天紀錄列表
function renderMessages() {
//清空messages整個列表
document.getElementById('messages').innerHTML = '';
//取得當前聊天室的id
const currentRoomId = document.getElementById('current-room-id').innerText;
//從所有聊天紀錄中過濾出符合現在roomId的訊息,再一個個推到訊息列表中
messages.filter(msg => msg.roomId === currentRoomId).map(msg => {
const messageNode = document.createElement('li');
messageNode.innerText = msg.message;
document.getElementById('messages').appendChild(messageNode);
})
}
</script>
程式碼解說:
- 先將網頁中
messages
這個id
對應的元素內的內容清空,確保待會顯示的是最新的訊息,而不是舊訊息的累加。 - 從網頁中
current-room-id
這個id
對應的元素中,取得當前聊天室的 ID,方便用來篩選與該聊天室相關的訊息。 messages
是一個訊息的列表。這一步是用filter
方法,從所有的訊息中選出屬於當前聊天室 (msg.roomId === currentRoomId
) 的訊息。- 使用
map
遍歷過濾後的訊息,對於每個訊息 (msg
),創建一個新的li
元素 (messageNode
)。 - 將訊息的內容 (
msg.message
) 設置為這個li
元素的文字內容,最後將這個li
元素加到messages
的列表中,顯示出來。
重新渲染聊天紀錄列表結果:網頁重新整理後
