.NET8使用SignalR实现实时通信的完整指南
作者:幌才_loong
SignalR 是一个强大的实时通信库,能够在服务器和客户端之间建立双向通信,本文将详细介绍如何在 .NET 8 中使用 SignalR并深入探讨 SignalR 在使用通信技术的顺序
SignalR 是一个强大的实时通信库,能够在服务器和客户端之间建立双向通信。本文将详细介绍如何在 .NET 8 中使用 SignalR,包括服务端配置、CORS 设置、前端调用以及 WinForms 客户端实现,并深入探讨 SignalR 在使用通信技术的顺序:WebSocket -> Server-Sent Events (SSE)。
一、服务端实现
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. 创建 Hub 类
创建一个继承自 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>
/// 客户端连接时触发
/// </summary>
public override async Task OnConnectedAsync()
{
Console.WriteLine($"客户端 {Context.ConnectionId} 已连接");
await base.OnConnectedAsync();
}
/// <summary>
/// 客户端断开连接时触发
/// </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>
/// 发送消息给所有人
/// </summary>
public async Task SendMessageToAll(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
/// <summary>
/// 发送消息给单个人
/// </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>
/// 发送消息给多个人
/// </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>
/// 发送消息给群组
/// </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} 不存在");
}
}
}
二、前端调用(浏览器)
1. 安装 SignalR 客户端
在前端项目中安装 SignalR 客户端库:
npm install @microsoft/signalr
2. 前端代码示例
创建一个简单的 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()">发送给所有人</button>
<button onclick="sendToUser()">发送给单个人</button>
<button onclick="sendToGroup()">发送给群组</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());
});
}
// 发送给所有人
function sendToAll() {
const message = document.getElementById("message").value;
connection.invoke("SendMessageToAll", username, message).catch(err => {
console.error(err.toString());
});
}
// 发送给单个人
function sendToUser() {
const targetUser = prompt("请输入目标用户名:");
const message = document.getElementById("message").value;
connection.invoke("SendMessageToUser", targetUser, username, message).catch(err => {
console.error(err.toString());
});
}
// 发送给群组
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 客户端调用
1. 安装 SignalR 客户端包
dotnet add package Microsoft.AspNetCore.SignalR.Client
2. WinForms 客户端代码
在窗体中添加以下控件:
TextBox:用于输入用户名TextBox:用于输入消息Button:登录按钮Button:发送给所有人按钮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($"发送失败:{ex.Message}");
}
}
}
四、SignalR 通信技术顺序
SignalR 在建立连接时,会根据浏览器和服务器的支持情况,自动选择最佳的通信技术。其顺序如下:
1. WebSocket
- 优先级最高,是 SignalR 的首选通信方式。
- 全双工通信,性能最好,延迟最低。
- 适用于大多数现代浏览器(Chrome、Firefox、Edge、Safari 等)。
- 使用标准的 WebSocket 协议(
ws://或wss://)。
2. Server-Sent Events (SSE)
- 如果 WebSocket 不可用(如浏览器不支持或被防火墙阻止),SignalR 会退而求其次使用 SSE。
- 单向通信,服务器可以实时向客户端推送数据,但客户端无法向服务器推送数据。
- 基于 HTTP 协议,使用
EventSourceAPI。 - 适用于需要实时更新但不需要双向通信的场景。
3. 长轮询 (Long Polling)
- 如果 SSE 也不可用,SignalR 会使用长轮询作为最后的 fallback。
- 客户端向服务器发送一个请求,服务器保持连接打开,直到有数据要发送或超时。
- 超时后,客户端会立即发送一个新的请求。
- 性能较差,但兼容性最好,适用于所有浏览器。
通信技术选择流程
客户端发送一个 HTTP 请求到服务器的 /chatHub/negotiate 端点,获取可用的通信技术列表。
客户端按照优先级顺序尝试使用这些技术:
- 首先尝试 WebSocket。
- 如果 WebSocket 失败,尝试 SSE。
- 如果 SSE 也失败,使用长轮询。
一旦成功建立连接,就使用该技术进行实时通信。
技术对比
| 技术 | 双向通信 | 性能 | 兼容性 |
|---|---|---|---|
| WebSocket | 是 | 高 | 现代浏览器 |
| SSE | 否(服务器到客户端) | 中 | 大多数现代浏览器 |
| 长轮询 | 是 | 低 | 所有浏览器 |
五、注意事项
- CORS 配置:必须在服务端正确配置 CORS,允许客户端来源并启用
AllowCredentials。 - 连接管理:使用
OnConnectedAsync和OnDisconnectedAsync管理客户端连接状态。 - 用户映射:维护用户与
ConnectionId的映射,以便实现单人和多人消息发送。 - 群组管理:使用
Groups类或自定义映射管理群组。 - 错误处理:客户端和服务端都需要处理连接中断和异常情况。
- 扩展性:如果需要支持大规模部署,可以使用 Redis 作为 SignalR 的后端,实现多服务器之间的消息共享。
通过本文的指南,你可以快速在 .NET 8 中使用 SignalR 实现实时通信,并支持多种消息发送方式。无论是前端浏览器还是 WinForms 客户端,都能轻松接入 SignalR 服务。
以上就是.NET8使用SignalR实现实时通信的完整指南的详细内容,更多关于.NET8 SignalR实时通信的资料请关注脚本之家其它相关文章!
