SignalR 完全指南:.NET 实时通信的终极解决方案
1. SignalR 概述
SignalR 是一个为 ASP.NET 开发者提供的开源库,它极大地简化了向应用程序添加实时 Web 功能的过程。所谓"实时 Web"功能,是指服务器代码能够即时将内容推送到连接的客户端,而不需要服务器等待客户端请求新数据。
1.1 SignalR 的核心价值
SignalR 为开发者提供了三大核心价值:
抽象化传输层:自动选择客户端和服务器之间最佳可用传输方法(WebSocket、Server-Sent Events 或长轮询),无需开发者关心底层实现。
连接管理:自动处理连接、断开连接和重新连接的复杂逻辑,提供稳定的通信通道。
简化的 API:通过 Hub 模式提供简单易用的高级 API,使开发者可以像调用本地方法一样进行远程调用。
1.2 SignalR 的发展历程
- 2013年:SignalR 1.0 发布,作为 ASP.NET 的扩展
- 2018年:SignalR for ASP.NET Core 2.1 发布,完全重写
- 2020年:SignalR 成为 .NET 5 的核心组件
- 2023年:.NET 8 中的 SignalR 性能提升 40%
1.3 SignalR 的架构组成
SignalR 由以下几个关键组件构成:
- Hubs:高级管道,允许客户端和服务器直接相互调用方法
- Persistent Connections:低级管道,用于需要更精细控制的场景
- 传输层:自动处理 WebSocket、Server-Sent Events 和长轮询
- 横向扩展支持:通过 Redis、Azure SignalR 等服务支持大规模部署
2. SignalR 的核心功能
2.1 自动传输选择
SignalR 会自动从以下几种传输方式中选择最佳方案:
- WebSocket:首选,提供全双工通信
- Server-Sent Events (SSE):当 WebSocket 不可用时使用
- 长轮询:作为最后的后备方案
// 示例:在 Startup.cs 中配置传输方式
services.AddSignalR(hubOptions => {
hubOptions.Transports = HttpTransportType.WebSockets |
HttpTransportType.ServerSentEvents;
hubOptions.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
});
2.2 Hub 模式
Hub 是 SignalR 的核心概念,它允许客户端和服务器直接调用彼此的方法:
// 服务器端 Hub 示例
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
// 调用所有客户端的 ReceiveMessage 方法
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
// 客户端可以调用的方法
public Task JoinGroup(string groupName)
{
return Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}
}
2.3 客户端支持
SignalR 提供多种客户端支持:
- JavaScript 客户端:用于 Web 前端
- .NET 客户端:用于 WPF、Xamarin 等应用
- Java 客户端:用于 Android 应用
- C++ 客户端:用于原生应用
// JavaScript 客户端示例
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chatHub")
.configureLogging(signalR.LogLevel.Information)
.build();
connection.on("ReceiveMessage", (user, message) => {
console.log(`${user}: ${message}`);
});
async function start() {
try {
await connection.start();
console.log("SignalR Connected.");
} catch (err) {
console.log(err);
setTimeout(start, 5000);
}
}
3. SignalR 的详细实现
3.1 服务器端配置
3.1.1 基本配置
// Startup.cs 中的 ConfigureServices 方法
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
// 配置 CORS(如果需要)
services.AddCors(options => {
options.AddPolicy("CorsPolicy", builder => builder
.WithOrigins("http://example.com")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials());
});
}
// Startup.cs 中的 Configure 方法
public void Configure(IApplication app)
{
app.UseRouting();
app.UseCors("CorsPolicy");
app.UseEndpoints(endpoints => {
endpoints.MapHub<ChatHub>("/chatHub");
});
}
3.1.2 高级配置
services.AddSignalR(hubOptions => {
// 启用详细错误消息(开发环境)
hubOptions.EnableDetailedErrors = true;
// 配置保持活动状态
hubOptions.KeepAliveInterval = TimeSpan.FromSeconds(15);
// 限制最大消息大小
hubOptions.MaximumReceiveMessageSize = 65536;
});
3.2 客户端实现
3.2.1 JavaScript 客户端
// 创建连接
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chatHub", {
// 配置传输回退顺序
transport: signalR.HttpTransportType.WebSockets |
signalR.HttpTransportType.ServerSentEvents,
// 访问令牌(如果需要认证)
accessTokenFactory: () => {
return localStorage.getItem('authToken');
},
// 跳过协商(直接使用WebSocket)
skipNegotiation: true
})
.configureLogging(signalR.LogLevel.Information)
.withAutomaticReconnect({
// 自定义重试策略
nextRetryDelayInMilliseconds: retryContext => {
return Math.min(retryContext.elapsedMilliseconds * 2, 10000);
}
})
.build();
// 定义服务器可调用的方法
connection.on("ReceiveMessage", (user, message) => {
displayMessage(user, message);
});
// 启动连接
async function startConnection() {
try {
await connection.start();
console.log("Connected successfully");
} catch (err) {
console.log("Connection failed: ", err);
setTimeout(startConnection, 5000);
}
}
// 调用服务器方法
async function sendMessage(user, message) {
try {
await connection.invoke("SendMessage", user, message);
} catch (err) {
console.error(err);
}
}
3.2.2 .NET 客户端
// 创建连接
var connection = new HubConnectionBuilder()
.WithUrl("https://example.com/chatHub", options => {
options.AccessTokenProvider = () => Task.FromResult(_authToken);
options.SkipNegotiation = true;
options.Transports = HttpTransportType.WebSockets;
})
.WithAutomaticReconnect(new[] {
TimeSpan.Zero, // 立即重试
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(10),
TimeSpan.FromSeconds(30) // 之后每30秒重试一次
})
.ConfigureLogging(logging => {
logging.SetMinimumLevel(LogLevel.Debug);
logging.AddConsole();
})
.Build();
// 注册处理方法
connection.On<string, string>("ReceiveMessage", (user, message) => {
Console.WriteLine($"{user}: {message}");
});
// 启动连接
try {
await connection.StartAsync();
Console.WriteLine("Connection started");
} catch (Exception ex) {
Console.WriteLine($"Error starting connection: {ex.Message}");
}
// 调用服务器方法
try {
await connection.InvokeAsync("SendMessage",
"ConsoleUser", "Hello from .NET client!");
} catch (Exception ex) {
Console.WriteLine($"Error sending message: {ex.Message}");
}
4. SignalR 高级特性
4.1 组管理
SignalR 提供了强大的组管理功能,允许将连接分组并向特定组广播消息:
public class ChatHub : Hub
{
// 加入组
public async Task JoinGroup(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
await Clients.Group(groupName).SendAsync("SystemMessage",
$"{Context.ConnectionId} 加入了 {groupName}");
}
// 离开组
public async Task LeaveGroup(string groupName)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
await Clients.Group(groupName).SendAsync("SystemMessage",
$"{Context.ConnectionId} 离开了 {groupName}");
}
// 向组发送消息
public async Task SendToGroup(string groupName, string message)
{
await Clients.Group(groupName).SendAsync("ReceiveMessage",
Context.ConnectionId, message);
}
}
4.2 用户标识
SignalR 可以集成 ASP.NET Core 的身份认证系统:
[Authorize]
public class ChatHub : Hub
{
public override async Task OnConnectedAsync()
{
var user = Context.User;
var username = user.Identity.Name;
await Clients.All.SendAsync("SystemMessage",
$"{username} 加入了聊天");
await base.OnConnectedAsync();
}
public async Task SendMessage(string message)
{
var user = Context.User;
await Clients.All.SendAsync("ReceiveMessage",
user.Identity.Name, message);
}
}
4.3 流式传输
SignalR 支持从服务器到客户端的流式数据传输:
public class DataStreamHub : Hub
{
// 服务器到客户端流
public async IAsyncEnumerable<int> CounterStream(int count,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
for (var i = 0; i < count; i++)
{
cancellationToken.ThrowIfCancellationRequested();
yield return i;
await Task.Delay(1000, cancellationToken);
}
}
// 客户端到服务器流
public async Task UploadStream(IAsyncEnumerable<string> stream)
{
await foreach (var item in stream)
{
Console.WriteLine($"Received: {item}");
}
}
}
客户端调用流方法:
// 消费服务器流
connection.on("CounterStream", async (count) => {
const stream = connection.stream("CounterStream", 10);
for await (const item of stream) {
console.log(item);
}
});
// 发送客户端流
async function * getDataStream() {
for (let i = 0; i < 10; i++) {
yield i.toString();
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
const streamResult = connection.send("UploadStream", getDataStream());
5. SignalR 性能优化
5.1 横向扩展
当部署多个服务器时,SignalR 需要后端服务来同步消息:
// 使用 Redis 作为背板
services.AddSignalR().AddStackExchangeRedis("localhost", options => {
options.Configuration.ChannelPrefix = "MyApp";
});
// 或者使用 Azure SignalR 服务
services.AddSignalR().AddAzureSignalR("Endpoint=...;AccessKey=...");
5.2 消息压缩
services.AddSignalR()
.AddMessagePackProtocol(options => {
options.FormatterResolvers = new List<MessagePack.IFormatterResolver>() {
MessagePack.Resolvers.StandardResolver.Instance
};
});
5.3 连接过滤
public class CustomHubFilter : IHubFilter
{
public async ValueTask<object> InvokeMethodAsync(
HubInvocationContext invocationContext,
Func<HubInvocationContext, ValueTask<object>> next)
{
// 记录方法调用
Console.WriteLine($"调用 {invocationContext.HubMethodName}");
// 检查权限等
return await next(invocationContext);
}
}
// 注册全局过滤器
services.AddSignalR(options => {
options.AddFilter<CustomHubFilter>();
});
6. SignalR 最佳实践
连接管理:
- 始终处理断开连接和重连
- 在客户端检测网络状态变化
- 使用
withAutomaticReconnect
配置合理的重试策略
安全性:
- 始终验证输入
- 使用 HTTPS
- 实现适当的授权
- 限制消息大小
性能:
- 对于高频率消息,考虑批处理
- 使用二进制协议(MessagePack)减少负载
- 适当配置保持活动间隔
监控:
- 记录连接和断开事件
- 监控消息速率
- 设置警报阈值
7. SignalR 与其他技术的对比
特性 | SignalR | 原生 WebSocket | SSE | 长轮询 |
---|---|---|---|---|
协议 | 自动选择最佳 | WebSocket | HTTP | HTTP |
双向通信 | 是 | 是 | 否 | 否 |
自动重连 | 是 | 需手动实现 | 是 | 需手动实现 |
传输效率 | 高 | 高 | 中 | 低 |
服务器负载 | 低 | 低 | 中 | 高 |
复杂度 | 低 | 高 | 中 | 低 |
.NET集成 | 完美 | 需手动处理 | 需手动处理 | 需手动处理 |
8. 实际应用场景
8.1 实时聊天应用
public class ChatHub : Hub
{
private static readonly Dictionary<string, string> _users = new();
public async Task RegisterUser(string username)
{
_users[Context.ConnectionId] = username;
await Clients.All.SendAsync("UserJoined", username);
}
public async Task SendMessage(string message)
{
if (_users.TryGetValue(Context.ConnectionId, out var username))
{
await Clients.All.SendAsync("ReceiveMessage", username, message);
}
}
public override async Task OnDisconnectedAsync(Exception exception)
{
if (_users.TryGetValue(Context.ConnectionId, out var username))
{
_users.Remove(Context.ConnectionId);
await Clients.All.SendAsync("UserLeft", username);
}
await base.OnDisconnectedAsync(exception);
}
}
8.2 实时数据仪表盘
public class DashboardHub : Hub
{
private readonly IDataService _dataService;
public DashboardHub(IDataService dataService)
{
_dataService = dataService;
}
public async Task SubscribeToUpdates()
{
var data = await _dataService.GetInitialData();
await Clients.Caller.SendAsync("InitialData", data);
// 开始推送更新
var cancellationToken = Context.GetHttpContext().RequestAborted;
await foreach (var update in _dataService.GetDataUpdates(cancellationToken))
{
await Clients.Caller.SendAsync("DataUpdate", update);
}
}
}
8.3 多人协作编辑
public class CollaborationHub : Hub
{
private readonly ICollaborationService _collabService;
public CollaborationHub(ICollaborationService collabService)
{
_collabService = collabService;
}
public async Task JoinDocument(string docId)
{
await Groups.AddToGroupAsync(Context.ConnectionId, docId);
var document = await _collabService.GetDocument(docId);
var users = await _collabService.GetDocumentUsers(docId);
await Clients.Caller.SendAsync("DocumentLoaded", document);
await Clients.Group(docId).SendAsync("UsersUpdated", users);
}
public async Task EditDocument(string docId, DocumentEdit edit)
{
await _collabService.ApplyEdit(docId, edit);
await Clients.OthersInGroup(docId).SendAsync("DocumentEdited", edit);
}
}
9. 常见问题解决方案
9.1 连接问题排查
检查传输协议:
connection.onclose(error => { console.log("Connection closed due to error: ", error); console.log("Last transport: ", connection.connection.transport.name); });
启用详细日志:
services.AddSignalR() .AddHubOptions<ChatHub>(options => { options.EnableDetailedErrors = true; });
检查 CORS 配置:
services.AddCors(options => { options.AddPolicy("SignalRCors", builder => { builder.WithOrigins("https://yourdomain.com") .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); });
9.2 性能问题优化
使用 MessagePack:
services.AddSignalR() .AddMessagePackProtocol();
限制消息大小:
services.AddSignalR(options => { options.MaximumReceiveMessageSize = 32768; // 32KB });
批处理消息:
// 在客户端 let batch = []; setInterval(() => { if (batch.length > 0) { connection.send("SendBatch", batch); batch = []; } }, 100);
9.3 横向扩展问题
使用 Azure SignalR 服务:
services.AddSignalR() .AddAzureSignalR("Endpoint=...;AccessKey=...");
实现自定义背板:
public class CustomBackplane : IHubLifetimeManager { // 实现必要接口方法 } services.AddSingleton<IHubLifetimeManager, CustomBackplane>();
10. 未来展望
SignalR 作为 .NET 实时通信的核心组件,未来可能会:
- 集成更高效的二进制协议
- 改进移动端支持
- 增强与 WebRTC 的集成
- 提供更好的离线消息处理
- 优化大规模集群支持
结论
SignalR 是 .NET 生态中最强大、最成熟的实时通信解决方案,它抽象了底层传输细节,提供了简单易用的 API,并自动处理了连接管理、重连等复杂问题。无论是构建聊天应用、实时仪表盘还是协作系统,SignalR 都能提供稳定高效的实时通信能力。
文章读了终究是文章,一定要自己手敲一遍,才能吸收成为自己的知识!