Skip to content
Coding

SignalR 的學習與應用(1):簡單網頁聊天室

SignalR 的學習與應用(1):簡單網頁聊天室

SignalR 簡介

如果你是第一次接觸 SignalR,可以想像它是一個幫你處理即時通訊的工具。它會自動幫你搞定誰在線上、誰下線了,還能讓你輕鬆把訊息送給所有人或指定的人,而且可以應付很多人同時使用。簡單來說,不用煩惱那些複雜的底層細節,SignalR 幫你包好了!

傳輸協議

你可能會想問,SignalR 到底怎麼做到讓訊息這麼即時的呢? 其實它會自動幫你選擇最適合的傳輸方式:

  • WebSocket:就像一直開著的專線,速度最快,也是首選。
  • Server-Sent Events (SSE):伺服器主動推消息給你,像訂閱新聞一樣。
  • Long Polling:不斷問伺服器「有新消息嗎?」來達成即時效果。 不用擔心怎麼挑,SignalR 會幫你搞定!

實作步驟

1. 建立專案並添加 SignalR

在 .NET Core/.NET 5+ 中,SignalR 已經被整合到 ASP.NET Core 框架中了,所以我們不需要額外安裝 SignalR 的服務器端套件,只需要在 Program.cs 中配置 SignalR:

using WebApiDemo.Hubs; 
 
var builder = WebApplication.CreateBuilder(args); 
 
// 添加 SignalR 服務到 DI 容器 
builder.Services.AddSignalR(); 
 
var app = builder.Build(); 
 
app.UseStaticFiles(); 
app.UseRouting();     // 啟用路由 
 
// 配置 SignalR Hub 路由 
app.MapHub<ChatHub>("/chatHub"); 
 
// 設置默認頁面 
app.MapGet("/", () => Results.File("wwwroot/index.html", "text/html")); 
 
app.Run();

2. 創建 Hub

Hub 是 SignalR 的核心角色,可以想成「訊息小幫手」,負責在用戶的瀏覽器和伺服器之間傳遞訊息。基本的 Hub 需要做的事很簡單:收到使用者的訊息並轉送給其他人,處理使用者連線和斷線的通知。

using Microsoft.AspNetCore.SignalR; 
 
namespace WebApiDemo.Hubs; 
 
public class ChatHub : Hub 
{ 
    // 這個方法用來接收使用者的訊息並廣播給所有人 
    public async Task SendMessage(string user, string message) 
    { 
        await Clients.All.SendAsync("ReceiveMessage", user, message); 
    } 
 
    // 使用者連線時會觸發這裡,把新加入的使用者通知給大家 
    public override async Task OnConnectedAsync() 
    { 
        await Clients.All.SendAsync("UserConnected", Context.ConnectionId); 
        await base.OnConnectedAsync(); 
    } 
 
    // 使用者斷線時會執行這裡,把誰離開的通知發給所有人 
    public override async Task OnDisconnectedAsync(Exception? exception) 
    { 
        await Clients.All.SendAsync("UserDisconnected", Context.ConnectionId); 
        await base.OnDisconnectedAsync(exception); 
    } 
}

Hub 重要屬性:Clients:負責把訊息發送給誰,例如:

  • Clients.All:送給所有人,不管是誰
  • Clients.Caller:只回傳給發送訊息的人
  • Clients.Others:送給除了發訊息的人以外的所有人

Context:提供關於這次連線的資訊,例如:

  • Context.ConnectionId:每個使用者連上來後都有一個獨一無二的編號
  • Context.User:使用者的身份資訊,適用在需要登入或認證的應用程式

3. 客戶端實現

在 root 目錄底下建立 wwwroot 的資料夾,並且建立 index.html 輸入以下內容:

<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="UTF-8"> 
    <title>SignalR 聊天室</title> 
    <style> 
        #messagesList { border: 1px solid #ccc; height: 300px; overflow-y: auto; padding: 10px; margin-bottom: 10px; } 
        .form-group { margin: 10px 0; } 
        .form-group label { display: inline-block; width: 70px; } 
        .form-group input { padding: 5px; width: 200px; } 
        #sendButton { padding: 5px 15px; margin-top: 10px; } 
    </style> 
</head> 
<body> 
    <div class="container"> 
        <h2>SignalR 聊天室</h2> 
        <div id="messagesList"></div> 
         
        <div class="form-group"> 
            <label for="userInput">用戶名:</label> 
            <input type="text" id="userInput" /> 
        </div> 
        <div class="form-group"> 
            <label for="messageInput">消息:</label> 
            <input type="text" id="messageInput" /> 
        </div> 
        <button id="sendButton">發送</button> 
    </div> 
 
    <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.0/signalr.min.js"></script> 
    <script> 
        // 建立連接 
        const connection = new signalR.HubConnectionBuilder() 
            .withUrl("/chatHub")  // Hub 的路由 
            .build(); 
 
        document.getElementById("sendButton").disabled = true; 
 
        // 註冊接收消息的處理函數 
        connection.on("ReceiveMessage", function (user, message) { 
            const msg = `${user}: ${message}`; 
            const li = document.createElement("div"); 
            li.textContent = msg; 
            document.getElementById("messagesList").appendChild(li); 
        }); 
 
        // 處理用戶連接事件 
        connection.on("UserConnected", function (connectionId) { 
            const msg = `用戶已連接 (${connectionId})`; 
            const li = document.createElement("div"); 
            li.textContent = msg; 
            li.style.color = "green"; 
            document.getElementById("messagesList").appendChild(li); 
        }); 
 
        connection.on("UserDisconnected", function (connectionId) { 
            const msg = `用戶已斷開連接 (${connectionId})`; 
            const li = document.createElement("div"); 
            li.textContent = msg; 
            li.style.color = "red"; 
            document.getElementById("messagesList").appendChild(li); 
        }); 
 
        // 啟動連接 
        connection.start().then(function () { 
            document.getElementById("sendButton").disabled = false; 
        }).catch(function (err) { 
            return console.error(err.toString()); 
        }); 
 
        // 發送消息 
        document.getElementById("sendButton").addEventListener("click", function (event) { 
            const user = document.getElementById("userInput").value; 
            const message = document.getElementById("messageInput").value; 
            // 調用服務器端的 SendMessage 方法 
            connection.invoke("SendMessage", user, message).catch(function (err) { 
                return console.error(err.toString()); 
            }); 
            document.getElementById("messageInput").value = ""; 
            event.preventDefault(); 
        }); 
    </script> 
</body> 
</html>

這時候運行你的專案(記得設定 launchSettings.json),輸入 localhost:5000/index.html ,就可以看到以下的畫面:

如果你再多開一個新的 tab,就會看到第一個 tab 多了一行連接的用戶:

兩個不同的人可以輸入訊息,按下發送後,另外一邊也會收到
有人離開的話就可以送出通知

到這裡,一個簡單的聊天室就建立完成了。