即時通訊 — Socket.io實作

更新於 發佈於 閱讀時間約 30 分鐘

※ 先建立基本的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
raw-image


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印出結果

raw-image

※ 撰寫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印出結果

raw-image

※ 撰寫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>

按鈕結果

raw-image

※ 撰寫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)相關資訊,方便開發者檢查事件的執行狀態。

創建房間結果:網頁重新整理後

raw-image
  • 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 的值。

加入房間結果:網頁重新整理後

raw-image


※ 撰寫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>

檢查是否重複命名結果:網頁重新整理後

raw-image

※ 撰寫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 傳遞回前端,用來通知使用者他們成功加入了該房間。

加入房間結果:網頁重新整理後

raw-image

※ 撰寫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() 發送的,目的在通知客戶端。
  • 回呼函式的參數 roomIduserId 是事件攜帶的資料(payload)。
  • roomId:指定房間的 ID,代表事件涉及哪個房間。
  • userId:新加入者的使用者 ID。

新成員加入特定房間通知結果:網頁重新整理後

raw-image

※ 撰寫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);
  }

程式碼解說:

  1. const message = document.getElementById('message-input').value;
    • 這一行的作用是從 HTML 網頁中取得使用者在輸入框 (id: message-input) 中輸入的訊息文字。
    • .value 是用來提取輸入框的文字值。
  2. const currentRoomId = document.getElementById('current-room-id').innerText;
    • 這一行是從 HTML 中的某個元素 (id: current-room-id) 取得目前房間的 ID。
    • .innerText 是用於取得該元素的文字內容。
  3. 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 事件時,觸發回呼函式。
  • 回呼函式接收兩個參數:
  1. roomId:訊息所屬的房間 ID。
  2. 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 在網頁上顯示彈出式通知。

收到訊息結果:網頁重新整理後

raw-image

※ 撰寫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 的列表中,顯示出來。

重新渲染聊天紀錄列表結果:網頁重新整理後

raw-image





留言
avatar-img
留言分享你的想法!
avatar-img
奧莉薇走在成為後端工程師之路上
18會員
141內容數
全端網頁開發專業知識分享
2025/04/26
※ 場景: 即時聊天應用: 設計一個支持多房間功能的即時聊天平台,像 WhatsApp、LINE或Facebook Messenger,提供文字、語音、視訊聊天功能,方便管理群組聊天。 功能亮點:加入特別功能,例如可加入多房間功能、使用者名單、表情符號支持、文件分享或訊息已讀未讀狀態。 展示
2025/04/26
※ 場景: 即時聊天應用: 設計一個支持多房間功能的即時聊天平台,像 WhatsApp、LINE或Facebook Messenger,提供文字、語音、視訊聊天功能,方便管理群組聊天。 功能亮點:加入特別功能,例如可加入多房間功能、使用者名單、表情符號支持、文件分享或訊息已讀未讀狀態。 展示
2025/04/10
※ 什麼是 Socket.io:一個基於傳統 WebSocket API 之上的框架。 ※ Socket.io常用功能: Custom Events:在 Socket.io 中,開發者可以創建自己的事件來處理特定的功能或需求。 Rooms:分組的功能。每個連接的用戶(或稱為 socket)可
Thumbnail
2025/04/10
※ 什麼是 Socket.io:一個基於傳統 WebSocket API 之上的框架。 ※ Socket.io常用功能: Custom Events:在 Socket.io 中,開發者可以創建自己的事件來處理特定的功能或需求。 Rooms:分組的功能。每個連接的用戶(或稱為 socket)可
Thumbnail
2025/04/06
※ 建立靜態的網頁腳本: <!DOCTYPE html> <html> <head>   <title>WebSocket minimal demo</title> </head> <body>   <!-- 聊天室紀錄 -->   <ul class="chat-list"></ul>   <!
Thumbnail
2025/04/06
※ 建立靜態的網頁腳本: <!DOCTYPE html> <html> <head>   <title>WebSocket minimal demo</title> </head> <body>   <!-- 聊天室紀錄 -->   <ul class="chat-list"></ul>   <!
Thumbnail
看更多
你可能也想看
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
在這篇教學文章中,我們將展示如何使用 Node.js 建立一個簡單的伺服器,並解決常見的跨來源資源共享(CORS)問題,確保伺服器能夠接收並處理來自不同來源的資料。
Thumbnail
在這篇教學文章中,我們將展示如何使用 Node.js 建立一個簡單的伺服器,並解決常見的跨來源資源共享(CORS)問題,確保伺服器能夠接收並處理來自不同來源的資料。
Thumbnail
當我們架好站、WebService測試完,接著就是測試區域網路連線啦~
Thumbnail
當我們架好站、WebService測試完,接著就是測試區域網路連線啦~
Thumbnail
前面已經安裝好IIS後,並且也新建站台了,那麼接下來這篇就會分享如何使用它
Thumbnail
前面已經安裝好IIS後,並且也新建站台了,那麼接下來這篇就會分享如何使用它
Thumbnail
前段時間我們有介紹「【Python 軍火庫🧨 - websockets】雙向溝通的渠道」, 這種方式可以達到基本的連線沒問題,但隨著資安意識的抬頭, 我們的websocket連線也會需要在通道之上進行加密, 那麼我們將根據使用情境來教您如何選用適當的連線。 Server端 我們的Serve
Thumbnail
前段時間我們有介紹「【Python 軍火庫🧨 - websockets】雙向溝通的渠道」, 這種方式可以達到基本的連線沒問題,但隨著資安意識的抬頭, 我們的websocket連線也會需要在通道之上進行加密, 那麼我們將根據使用情境來教您如何選用適當的連線。 Server端 我們的Serve
Thumbnail
Express 是一個流行的 web 框架,使用 JavsScript 實現,執行在 node 環境上,主要用來寫後端應用。
Thumbnail
Express 是一個流行的 web 框架,使用 JavsScript 實現,執行在 node 環境上,主要用來寫後端應用。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News