.NET8使用SignalR實現(xiàn)實時通信的完整指南
SignalR 是一個強大的實時通信庫,能夠在服務器和客戶端之間建立雙向通信。本文將詳細介紹如何在 .NET 8 中使用 SignalR,包括服務端配置、CORS 設置、前端調(diào)用以及 WinForms 客戶端實現(xiàn),并深入探討 SignalR 在使用通信技術的順序:WebSocket -> Server-Sent Events (SSE)。
一、服務端實現(xiàn)
1. 安裝 SignalR 包
在項目中安裝 SignalR 核心包:
dotnet add package Microsoft.AspNetCore.SignalR
2. 配置 SignalR 與 CORS
由于 SignalR 可能涉及跨域請求,需要在 Program.cs 中配置 CORS:
var builder = WebApplication.CreateBuilder(args);
// 添加 SignalR 服務
builder.Services.AddSignalR();
// 配置 CORS
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll", policy =>
{
policy.WithOrigins("https://localhost:7100") // 允許的前端地址
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials(); // 允許攜帶認證信息(SignalR 必需)
});
});
var app = builder.Build();
// 使用 CORS
app.UseCors("AllowAll");
// 映射 SignalR Hub
app.MapHub<ChatHub>("/chatHub");
app.Run();
3. 創(chuàng)建 Hub 類
創(chuàng)建一個繼承自 Hub 的類來處理客戶端連接和消息:
using Microsoft.AspNetCore.SignalR;
using System.Collections.Concurrent;
namespace SignalRDemo;
public class ChatHub : Hub
{
// 存儲用戶與連接ID的映射
private static readonly ConcurrentDictionary<string, string> _userConnections = new();
// 存儲群組與連接ID的映射
private static readonly ConcurrentDictionary<string, HashSet<string>> _groupConnections = new();
/// <summary>
/// 客戶端連接時觸發(fā)
/// </summary>
public override async Task OnConnectedAsync()
{
Console.WriteLine($"客戶端 {Context.ConnectionId} 已連接");
await base.OnConnectedAsync();
}
/// <summary>
/// 客戶端斷開連接時觸發(fā)
/// </summary>
public override async Task OnDisconnectedAsync(Exception? exception)
{
Console.WriteLine($"客戶端 {Context.ConnectionId} 已斷開連接");
// 從用戶映射中移除
var user = _userConnections.FirstOrDefault(kv => kv.Value == Context.ConnectionId).Key;
if (!string.IsNullOrEmpty(user))
{
_userConnections.TryRemove(user, out _);
}
// 從所有群組中移除
foreach (var group in _groupConnections.Keys)
{
_groupConnections[group].Remove(Context.ConnectionId);
}
await base.OnDisconnectedAsync(exception);
}
/// <summary>
/// 用戶登錄(綁定用戶名和連接ID)
/// </summary>
public async Task Login(string username)
{
_userConnections[username] = Context.ConnectionId;
await Clients.Caller.SendAsync("LoginSuccess", $"你已登錄為 {username}");
}
/// <summary>
/// 加入群組
/// </summary>
public async Task JoinGroup(string groupName)
{
if (!_groupConnections.ContainsKey(groupName))
{
_groupConnections[groupName] = new HashSet<string>();
}
_groupConnections[groupName].Add(Context.ConnectionId);
await Clients.Caller.SendAsync("JoinGroupSuccess", $"你已加入群組 {groupName}");
}
/// <summary>
/// 發(fā)送消息給所有人
/// </summary>
public async Task SendMessageToAll(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
/// <summary>
/// 發(fā)送消息給單個人
/// </summary>
public async Task SendMessageToUser(string targetUser, string user, string message)
{
if (_userConnections.TryGetValue(targetUser, out var connectionId))
{
await Clients.Client(connectionId).SendAsync("ReceiveMessage", user, message);
}
else
{
await Clients.Caller.SendAsync("Error", $"用戶 {targetUser} 不在線");
}
}
/// <summary>
/// 發(fā)送消息給多個人
/// </summary>
public async Task SendMessageToUsers(IReadOnlyList<string> targetUsers, string user, string message)
{
var connectionIds = new List<string>();
foreach (var targetUser in targetUsers)
{
if (_userConnections.TryGetValue(targetUser, out var connectionId))
{
connectionIds.Add(connectionId);
}
}
if (connectionIds.Any())
{
await Clients.Clients(connectionIds).SendAsync("ReceiveMessage", user, message);
}
else
{
await Clients.Caller.SendAsync("Error", "沒有目標用戶在線");
}
}
/// <summary>
/// 發(fā)送消息給群組
/// </summary>
public async Task SendMessageToGroup(string groupName, string user, string message)
{
if (_groupConnections.TryGetValue(groupName, out var connections))
{
if (connections.Any())
{
await Clients.Clients(connections).SendAsync("ReceiveMessage", user, message);
}
else
{
await Clients.Caller.SendAsync("Error", $"群組 {groupName} 沒有成員");
}
}
else
{
await Clients.Caller.SendAsync("Error", $"群組 {groupName} 不存在");
}
}
}
二、前端調(diào)用(瀏覽器)
1. 安裝 SignalR 客戶端
在前端項目中安裝 SignalR 客戶端庫:
npm install @microsoft/signalr
2. 前端代碼示例
創(chuàng)建一個簡單的 HTML 頁面,使用 JavaScript 連接 SignalR:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>SignalR Chat</title>
</head>
<body>
<input type="text" id="username" placeholder="請輸入用戶名" />
<button onclick="login()">登錄</button>
<br />
<input type="text" id="message" placeholder="請輸入消息" />
<button onclick="sendToAll()">發(fā)送給所有人</button>
<button onclick="sendToUser()">發(fā)送給單個人</button>
<button onclick="sendToGroup()">發(fā)送給群組</button>
<ul id="messages"></ul>
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/7.0.0/signalr.min.js"></script>
<script>
let connection;
let username;
// 連接 SignalR
function connect() {
connection = new signalR.HubConnectionBuilder()
.withUrl("https://localhost:7138/chatHub") // 后端地址
.build();
// 接收消息
connection.on("ReceiveMessage", (user, message) => {
const li = document.createElement("li");
li.textContent = `${user}: ${message}`;
document.getElementById("messages").appendChild(li);
});
// 連接成功事件
connection.start().then(() => {
console.log("已連接到 SignalR 服務端");
}).catch(err => {
console.error(err.toString());
});
}
// 登錄
function login() {
username = document.getElementById("username").value;
connection.invoke("Login", username).catch(err => {
console.error(err.toString());
});
}
// 發(fā)送給所有人
function sendToAll() {
const message = document.getElementById("message").value;
connection.invoke("SendMessageToAll", username, message).catch(err => {
console.error(err.toString());
});
}
// 發(fā)送給單個人
function sendToUser() {
const targetUser = prompt("請輸入目標用戶名:");
const message = document.getElementById("message").value;
connection.invoke("SendMessageToUser", targetUser, username, message).catch(err => {
console.error(err.toString());
});
}
// 發(fā)送給群組
function sendToGroup() {
const groupName = prompt("請輸入群組名稱:");
const message = document.getElementById("message").value;
connection.invoke("SendMessageToGroup", groupName, username, message).catch(err => {
console.error(err.toString());
});
}
// 頁面加載時連接
connect();
</script>
</body>
</html>
三、WinForms 客戶端調(diào)用
1. 安裝 SignalR 客戶端包
dotnet add package Microsoft.AspNetCore.SignalR.Client
2. WinForms 客戶端代碼
在窗體中添加以下控件:
TextBox:用于輸入用戶名TextBox:用于輸入消息Button:登錄按鈕Button:發(fā)送給所有人按鈕ListBox:顯示接收的消息
然后編寫代碼:
using Microsoft.AspNetCore.SignalR.Client;
using System;
using System.Windows.Forms;
namespace SignalRWinFormsClient;
public partial class Form1 : Form
{
private HubConnection _connection;
private string _username;
public Form1()
{
InitializeComponent();
}
private async void Form1_Load(object sender, EventArgs e)
{
// 連接 SignalR
_connection = new HubConnectionBuilder()
.WithUrl("https://localhost:7138/chatHub")
.Build();
// 接收消息
_connection.On<string, string>("ReceiveMessage", (user, message) =>
{
Invoke(new Action(() =>
{
listBoxMessages.Items.Add($"{user}: {message}");
}));
});
try
{
await _connection.StartAsync();
listBoxMessages.Items.Add("已連接到 SignalR 服務端");
}
catch (Exception ex)
{
listBoxMessages.Items.Add($"連接失?。簕ex.Message}");
}
}
private async void btnLogin_Click(object sender, EventArgs e)
{
_username = txtUsername.Text;
try
{
await _connection.InvokeAsync("Login", _username);
listBoxMessages.Items.Add($"你已登錄為 {_username}");
}
catch (Exception ex)
{
listBoxMessages.Items.Add($"登錄失?。簕ex.Message}");
}
}
private async void btnSendToAll_Click(object sender, EventArgs e)
{
var message = txtMessage.Text;
try
{
await _connection.InvokeAsync("SendMessageToAll", _username, message);
txtMessage.Clear();
}
catch (Exception ex)
{
listBoxMessages.Items.Add($"發(fā)送失敗:{ex.Message}");
}
}
}
四、SignalR 通信技術順序
SignalR 在建立連接時,會根據(jù)瀏覽器和服務器的支持情況,自動選擇最佳的通信技術。其順序如下:
1. WebSocket
- 優(yōu)先級最高,是 SignalR 的首選通信方式。
- 全雙工通信,性能最好,延遲最低。
- 適用于大多數(shù)現(xiàn)代瀏覽器(Chrome、Firefox、Edge、Safari 等)。
- 使用標準的 WebSocket 協(xié)議(
ws://或wss://)。
2. Server-Sent Events (SSE)
- 如果 WebSocket 不可用(如瀏覽器不支持或被防火墻阻止),SignalR 會退而求其次使用 SSE。
- 單向通信,服務器可以實時向客戶端推送數(shù)據(jù),但客戶端無法向服務器推送數(shù)據(jù)。
- 基于 HTTP 協(xié)議,使用
EventSourceAPI。 - 適用于需要實時更新但不需要雙向通信的場景。
3. 長輪詢 (Long Polling)
- 如果 SSE 也不可用,SignalR 會使用長輪詢作為最后的 fallback。
- 客戶端向服務器發(fā)送一個請求,服務器保持連接打開,直到有數(shù)據(jù)要發(fā)送或超時。
- 超時后,客戶端會立即發(fā)送一個新的請求。
- 性能較差,但兼容性最好,適用于所有瀏覽器。
通信技術選擇流程
客戶端發(fā)送一個 HTTP 請求到服務器的 /chatHub/negotiate 端點,獲取可用的通信技術列表。
客戶端按照優(yōu)先級順序嘗試使用這些技術:
- 首先嘗試 WebSocket。
- 如果 WebSocket 失敗,嘗試 SSE。
- 如果 SSE 也失敗,使用長輪詢。
一旦成功建立連接,就使用該技術進行實時通信。
技術對比
| 技術 | 雙向通信 | 性能 | 兼容性 |
|---|---|---|---|
| WebSocket | 是 | 高 | 現(xiàn)代瀏覽器 |
| SSE | 否(服務器到客戶端) | 中 | 大多數(shù)現(xiàn)代瀏覽器 |
| 長輪詢 | 是 | 低 | 所有瀏覽器 |
五、注意事項
- CORS 配置:必須在服務端正確配置 CORS,允許客戶端來源并啟用
AllowCredentials。 - 連接管理:使用
OnConnectedAsync和OnDisconnectedAsync管理客戶端連接狀態(tài)。 - 用戶映射:維護用戶與
ConnectionId的映射,以便實現(xiàn)單人和多人消息發(fā)送。 - 群組管理:使用
Groups類或自定義映射管理群組。 - 錯誤處理:客戶端和服務端都需要處理連接中斷和異常情況。
- 擴展性:如果需要支持大規(guī)模部署,可以使用 Redis 作為 SignalR 的后端,實現(xiàn)多服務器之間的消息共享。
通過本文的指南,你可以快速在 .NET 8 中使用 SignalR 實現(xiàn)實時通信,并支持多種消息發(fā)送方式。無論是前端瀏覽器還是 WinForms 客戶端,都能輕松接入 SignalR 服務。
以上就是.NET8使用SignalR實現(xiàn)實時通信的完整指南的詳細內(nèi)容,更多關于.NET8 SignalR實時通信的資料請關注腳本之家其它相關文章!
相關文章
ASP.NET MVC如何使用Unity實現(xiàn)Ioc詳解
這篇文章主要給大家介紹了關于ASP.NET MVC如何使用Unity實現(xiàn)Ioc的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2018-07-07
ASP.net與SQLite數(shù)據(jù)庫通過js和ashx交互(連接和操作)
這篇文章主要介紹了ASP.net與SQLite數(shù)據(jù)庫通過js和ashx交互(連接和操作),具有一定的參考價值,有興趣的可以了解一下。2017-01-01

