Giới thiệu về Real-time Communication và lý do chọn SignalR

Giới thiệu về Real-time Communication và lý do chọn SignalR

June 16, 2025 0 By Nam Vu

Trong các bài viết trước, chúng ta đã cùng nhau khám phá nhiều phương pháp khác nhau để xây dựng tính năng giao tiếp thời gian thực (real-time communication) trong ứng dụng, bao gồm cả việc sử dụng WebSocket thuần.

Bạn đọc có thể đọc lại tại đây: https://blog.ntechdevelopers.com/web-socket-va-bai-toan-thoi-gian-thuc/

Việc nắm vững những kiến thức nền tảng này là rất quan trọng, nhưng khi đưa vào sản phẩm thực tế, đặc biệt trong môi trường doanh nghiệp, một giải pháp được hỗ trợ chính thức, ổn định và tối ưu như SignalR sẽ mang lại lợi ích lớn hơn cả về mặt kỹ thuật lẫn chi phí phát triển.

Trong bài viết này, chúng ta sẽ cùng tìm hiểu cách SignalR hoạt động và xây dựng một ứng dụng chat đơn giản nhằm thể hiện mức độ dễ dàng khi tích hợp các tính năng real-time nâng cao bằng SignalR.

Vì sao nên sử dụng SignalR?

Mặc dù WebSocket là một giao thức mạnh mẽ để thiết lập kết nối hai chiều, nhưng nó vẫn còn khá “thô sơ” nếu xét đến nhu cầu thực tế. Với WebSocket thuần, bạn phải tự xử lý hàng loạt tính năng phổ biến như:

  • Quản lý kết nối
  • Cơ chế heartbeat
  • Tuần tự hóa/giải tuần tự dữ liệu (serialization)
  • Cấu trúc kênh dữ liệu giữa các client

SignalR giúp bạn tiết kiệm thời gian và công sức bằng cách trừu tượng hóa toàn bộ các tác vụ kỹ thuật lặp lại này, để bạn có thể tập trung vào nghiệp vụ.

Ưu điểm của SignalR

  • Thư viện mã nguồn mở, được hỗ trợ bởi Microsoft
  • Tích hợp sâu với ASP.NET Core
  • Hỗ trợ tự động scale qua backplane
  • Cung cấp nhiều tính năng thời gian thực “có sẵn”

Nhược điểm

  • Yêu cầu client phải tích hợp thư viện riêng (không hoạt động được ở mọi môi trường)
  • Có một lượng nhỏ overhead

Những tính năng nổi bật của SignalR

SignalR cung cấp sẵn các tính năng quan trọng cho ứng dụng real-time:

  • Hỗ trợ serialization qua JSON hoặc MessagePack
  • Kết nối lâu dài với khả năng tự động reconnect
  • Quản lý kết nối theo HubGroup
  • Tích hợp với hệ thống xác thực ASP.NET Core Identity

Cơ chế hoạt động bên trong SignalR

SignalR hỗ trợ 3 phương thức truyền dữ liệu:

  • WebSocket (ưu tiên cao nhất)
  • Server-Sent Events
  • Long Polling (dự phòng cuối cùng)

Khi client kết nối đến server, SignalR thực hiện quy trình handshake để chọn ra giao thức tối ưu nhất. Nếu môi trường hỗ trợ WebSocket, SignalR sẽ sử dụng ngay mà không cần fallback.

Cài đặt một server SignalR đơn giản

Chúng ta sẽ xây dựng lại một ứng dụng chat đơn giản như đã làm ở bài WebSocket, nhưng lần này sử dụng SignalR.

Cài đặt package:

dotnet add package Microsoft.AspNetCore.SignalR

Cấu hình server:

using Microsoft.AspNetCore.SignalR;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(o =>
{
    o.AddPolicy("AllowAnyOrigin", p => p
        .WithOrigins("null")
        .AllowAnyHeader()
        .AllowCredentials());
});
builder.Services.AddSignalR();

var app = builder.Build();
app.UseCors("AllowAnyOrigin");
app.MapHub<ChatHub>("/chatHub");
app.Run();

public class ChatHub : Hub
{
    public async Task SendMessage(string message)
    {
        await Clients.All.SendAsync("ReceiveMessage", message);
    }
}

Tạo client SignalR bằng HTML + JavaScript

<script src="<https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.0/signalr.min.js>"></script>
const connection = new signalR.HubConnectionBuilder()
  .withAutomaticReconnect()
  .withUrl("<http://localhost:5008/chatHub>")
  .build();

connection.start().then(() => {
  document.getElementById("chatbox").addEventListener("keyup", function (event) {
    if (event.key === "Enter") {
      connection.invoke("SendMessage", event.target.value);
      event.target.value = "";
    }
  });
});

connection.on("ReceiveMessage", function (message) {
  const messages = document.getElementById("messages");
  messages.innerHTML += `<p>${message}</p>`;
});

Mở rộng với tính năng người dùng và phòng

public record User(string Name, string Room);
public record Message(string User, string Text);

public class ChatHub : Hub
{
    private static ConcurrentDictionary<string, User> _users = new();

    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        if (_users.TryGetValue(Context.ConnectionId, out var user))
        {
            await Groups.RemoveFromGroupAsync(Context.ConnectionId, user.Room);
            await Clients.Group(user.Room).SendAsync("UserLeft", user.Name);
        }
    }

    public async Task JoinRoom(string userName, string roomName)
    {
        _users.TryAdd(Context.ConnectionId, new User(userName, roomName));
        await Groups.AddToGroupAsync(Context.ConnectionId, roomName);
        await Clients.Group(roomName).SendAsync("UserJoined", userName);
    }

    public async Task SendMessageToRoom(string roomName, string content)
    {
        var message = new Message(_users[Context.ConnectionId].Name, content);
        await Clients.Group(roomName).SendAsync("ReceiveMessage", message);
    }
}

Xử lý phía client với nhiều người dùng

document.getElementById("joinRoom").addEventListener("click", () => {
  const roomName = document.getElementById("roomName").value;
  const userName = document.getElementById("userName").value;
  connection.invoke("JoinRoom", userName, roomName);
});

document.getElementById("messageInput").addEventListener("keyup", function (event) {
  if (event.key === "Enter") {
    const message = document.getElementById("messageInput").value;
    const roomName = document.getElementById("roomName").value;
    if (message && roomName) {
      connection.invoke("SendMessageToRoom", roomName, message);
    }
  }
});

connection.on("ReceiveMessage", function (msg) {
  const messages = document.getElementById("messages");
  messages.innerHTML += `<p>${msg.user}: ${msg.text}</p>`;
});

connection.on("UserJoined", function (msg) {
  const messages = document.getElementById("messages");
  messages.innerHTML += `<p>${msg} đã tham gia.</p>`;
});

connection.on("UserLeft", function (msg) {
  const messages = document.getElementById("messages");
  messages.innerHTML += `<p>${msg} đã rời khỏi phòng.</p>`;
});

Chúng ta vừa xây dựng một ứng dụng chat đơn giản sử dụng SignalR, từ server đến client, và mở rộng thêm tính năng người dùng/phòng.

SignalR không chỉ đơn giản hóa giao tiếp thời gian thực mà còn hỗ trợ nhiều tính năng mở rộng như xác thực, phân cụm (scaling), và tích hợp với Azure. Ở các bài viết tiếp theo, chúng ta sẽ đi sâu hơn vào các vấn đề như scaling, bảo mật và tích hợp SignalR với các hệ thống phức tạp hơn.

Nếu bạn đang phát triển ứng dụng cần real-time và đang tìm giải pháp đáng tin cậy – SignalR là một lựa chọn bạn nên cân nhắc nghiêm túc.

Bài viết sau mình sẽ đi chi tiết hơn về SignalR, cùng đợi mình nhé!

#ntechdevelopers