SignalR 完全指南:.NET 及时通信的终极解决方案

打印 上一主题 下一主题

主题 1838|帖子 1838|积分 5514

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
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 不可用时使用
  • 长轮询:作为最后的后备方案
  1. // 示例:在 Startup.cs 中配置传输方式
  2. services.AddSignalR(hubOptions => {
  3.     hubOptions.Transports = HttpTransportType.WebSockets |
  4.                           HttpTransportType.ServerSentEvents;
  5.     hubOptions.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
  6. });
复制代码
2.2 Hub 模式

Hub 是 SignalR 的焦点概念,它允许客户端和服务器直接调用相互的方法:
  1. // 服务器端 Hub 示例
  2. public class ChatHub : Hub
  3. {
  4.     public async Task SendMessage(string user, string message)
  5.     {
  6.         // 调用所有客户端的 ReceiveMessage 方法
  7.         await Clients.All.SendAsync("ReceiveMessage", user, message);
  8.     }
  9.    
  10.     // 客户端可以调用的方法
  11.     public Task JoinGroup(string groupName)
  12.     {
  13.         return Groups.AddToGroupAsync(Context.ConnectionId, groupName);
  14.     }
  15. }
复制代码
2.3 客户端支持

SignalR 提供多种客户端支持:

  • JavaScript 客户端:用于 Web 前端
  • .NET 客户端:用于 WPF、Xamarin 等应用
  • Java 客户端:用于 Android 应用
  • C++ 客户端:用于原生应用
  1. // JavaScript 客户端示例
  2. const connection = new signalR.HubConnectionBuilder()
  3.     .withUrl("/chatHub")
  4.     .configureLogging(signalR.LogLevel.Information)
  5.     .build();
  6. connection.on("ReceiveMessage", (user, message) => {
  7.     console.log(`${user}: ${message}`);
  8. });
  9. async function start() {
  10.     try {
  11.         await connection.start();
  12.         console.log("SignalR Connected.");
  13.     } catch (err) {
  14.         console.log(err);
  15.         setTimeout(start, 5000);
  16.     }
  17. }
复制代码
3. SignalR 的详细实现

3.1 服务器端设置

3.1.1 基本设置

  1. // Startup.cs 中的 ConfigureServices 方法
  2. public void ConfigureServices(IServiceCollection services)
  3. {
  4.     services.AddSignalR();
  5.    
  6.     // 配置 CORS(如果需要)
  7.     services.AddCors(options => {
  8.         options.AddPolicy("CorsPolicy", builder => builder
  9.             .WithOrigins("http://example.com")
  10.             .AllowAnyHeader()
  11.             .AllowAnyMethod()
  12.             .AllowCredentials());
  13.     });
  14. }
  15. // Startup.cs 中的 Configure 方法
  16. public void Configure(IApplication app)
  17. {
  18.     app.UseRouting();
  19.    
  20.     app.UseCors("CorsPolicy");
  21.    
  22.     app.UseEndpoints(endpoints => {
  23.         endpoints.MapHub<ChatHub>("/chatHub");
  24.     });
  25. }
复制代码
3.1.2 高级设置

  1. services.AddSignalR(hubOptions => {
  2.     // 启用详细错误消息(开发环境)
  3.     hubOptions.EnableDetailedErrors = true;
  4.    
  5.     // 配置保持活动状态
  6.     hubOptions.KeepAliveInterval = TimeSpan.FromSeconds(15);
  7.    
  8.     // 限制最大消息大小
  9.     hubOptions.MaximumReceiveMessageSize = 65536;
  10. });
复制代码
3.2 客户端实现

3.2.1 JavaScript 客户端

  1. // 创建连接
  2. const connection = new signalR.HubConnectionBuilder()
  3.     .withUrl("/chatHub", {
  4.         // 配置传输回退顺序
  5.         transport: signalR.HttpTransportType.WebSockets |
  6.                   signalR.HttpTransportType.ServerSentEvents,
  7.         
  8.         // 访问令牌(如果需要认证)
  9.         accessTokenFactory: () => {
  10.             return localStorage.getItem('authToken');
  11.         },
  12.         
  13.         // 跳过协商(直接使用WebSocket)
  14.         skipNegotiation: true
  15.     })
  16.     .configureLogging(signalR.LogLevel.Information)
  17.     .withAutomaticReconnect({
  18.         // 自定义重试策略
  19.         nextRetryDelayInMilliseconds: retryContext => {
  20.             return Math.min(retryContext.elapsedMilliseconds * 2, 10000);
  21.         }
  22.     })
  23.     .build();
  24. // 定义服务器可调用的方法
  25. connection.on("ReceiveMessage", (user, message) => {
  26.     displayMessage(user, message);
  27. });
  28. // 启动连接
  29. async function startConnection() {
  30.     try {
  31.         await connection.start();
  32.         console.log("Connected successfully");
  33.     } catch (err) {
  34.         console.log("Connection failed: ", err);
  35.         setTimeout(startConnection, 5000);
  36.     }
  37. }
  38. // 调用服务器方法
  39. async function sendMessage(user, message) {
  40.     try {
  41.         await connection.invoke("SendMessage", user, message);
  42.     } catch (err) {
  43.         console.error(err);
  44.     }
  45. }
复制代码
3.2.2 .NET 客户端

  1. // 创建连接
  2. var connection = new HubConnectionBuilder()
  3.     .WithUrl("https://example.com/chatHub", options => {
  4.         options.AccessTokenProvider = () => Task.FromResult(_authToken);
  5.         options.SkipNegotiation = true;
  6.         options.Transports = HttpTransportType.WebSockets;
  7.     })
  8.     .WithAutomaticReconnect(new[] {
  9.         TimeSpan.Zero,    // 立即重试
  10.         TimeSpan.FromSeconds(2),
  11.         TimeSpan.FromSeconds(10),
  12.         TimeSpan.FromSeconds(30) // 之后每30秒重试一次
  13.     })
  14.     .ConfigureLogging(logging => {
  15.         logging.SetMinimumLevel(LogLevel.Debug);
  16.         logging.AddConsole();
  17.     })
  18.     .Build();
  19. // 注册处理方法
  20. connection.On<string, string>("ReceiveMessage", (user, message) => {
  21.     Console.WriteLine($"{user}: {message}");
  22. });
  23. // 启动连接
  24. try {
  25.     await connection.StartAsync();
  26.     Console.WriteLine("Connection started");
  27. } catch (Exception ex) {
  28.     Console.WriteLine($"Error starting connection: {ex.Message}");
  29. }
  30. // 调用服务器方法
  31. try {
  32.     await connection.InvokeAsync("SendMessage",
  33.         "ConsoleUser", "Hello from .NET client!");
  34. } catch (Exception ex) {
  35.     Console.WriteLine($"Error sending message: {ex.Message}");
  36. }
复制代码
4. SignalR 高级特性

4.1 组管理

SignalR 提供了强大的组管理功能,允许将连接分组并向特定组广播消息:
  1. public class ChatHub : Hub
  2. {
  3.     // 加入组
  4.     public async Task JoinGroup(string groupName)
  5.     {
  6.         await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
  7.         await Clients.Group(groupName).SendAsync("SystemMessage",
  8.             $"{Context.ConnectionId} 加入了 {groupName}");
  9.     }
  10.    
  11.     // 离开组
  12.     public async Task LeaveGroup(string groupName)
  13.     {
  14.         await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
  15.         await Clients.Group(groupName).SendAsync("SystemMessage",
  16.             $"{Context.ConnectionId} 离开了 {groupName}");
  17.     }
  18.    
  19.     // 向组发送消息
  20.     public async Task SendToGroup(string groupName, string message)
  21.     {
  22.         await Clients.Group(groupName).SendAsync("ReceiveMessage",
  23.             Context.ConnectionId, message);
  24.     }
  25. }
复制代码
4.2 用户标识

SignalR 可以集成 ASP.NET Core 的身份认证体系:
  1. [Authorize]
  2. public class ChatHub : Hub
  3. {
  4.     public override async Task OnConnectedAsync()
  5.     {
  6.         var user = Context.User;
  7.         var username = user.Identity.Name;
  8.         
  9.         await Clients.All.SendAsync("SystemMessage",
  10.             $"{username} 加入了聊天");
  11.         
  12.         await base.OnConnectedAsync();
  13.     }
  14.    
  15.     public async Task SendMessage(string message)
  16.     {
  17.         var user = Context.User;
  18.         await Clients.All.SendAsync("ReceiveMessage",
  19.             user.Identity.Name, message);
  20.     }
  21. }
复制代码
4.3 流式传输

SignalR 支持从服务器到客户端的流式数据传输:
  1. public class DataStreamHub : Hub
  2. {
  3.     // 服务器到客户端流
  4.     public async IAsyncEnumerable<int> CounterStream(int count,
  5.         [EnumeratorCancellation] CancellationToken cancellationToken)
  6.     {
  7.         for (var i = 0; i < count; i++)
  8.         {
  9.             cancellationToken.ThrowIfCancellationRequested();
  10.             yield return i;
  11.             await Task.Delay(1000, cancellationToken);
  12.         }
  13.     }
  14.    
  15.     // 客户端到服务器流
  16.     public async Task UploadStream(IAsyncEnumerable<string> stream)
  17.     {
  18.         await foreach (var item in stream)
  19.         {
  20.             Console.WriteLine($"Received: {item}");
  21.         }
  22.     }
  23. }
复制代码
客户端调用流方法:
  1. // 消费服务器流
  2. connection.on("CounterStream", async (count) => {
  3.     const stream = connection.stream("CounterStream", 10);
  4.    
  5.     for await (const item of stream) {
  6.         console.log(item);
  7.     }
  8. });
  9. // 发送客户端流
  10. async function * getDataStream() {
  11.     for (let i = 0; i < 10; i++) {
  12.         yield i.toString();
  13.         await new Promise(resolve => setTimeout(resolve, 1000));
  14.     }
  15. }
  16. const streamResult = connection.send("UploadStream", getDataStream());
复制代码
5. SignalR 性能优化

5.1 横向扩展

当部署多个服务器时,SignalR 需要后端服务来同步消息:
  1. // 使用 Redis 作为背板
  2. services.AddSignalR().AddStackExchangeRedis("localhost", options => {
  3.     options.Configuration.ChannelPrefix = "MyApp";
  4. });
  5. // 或者使用 Azure SignalR 服务
  6. services.AddSignalR().AddAzureSignalR("Endpoint=...;AccessKey=...");
复制代码
5.2 消息压缩

  1. services.AddSignalR()
  2.     .AddMessagePackProtocol(options => {
  3.         options.FormatterResolvers = new List<MessagePack.IFormatterResolver>() {
  4.             MessagePack.Resolvers.StandardResolver.Instance
  5.         };
  6.     });
复制代码
5.3 连接过滤

  1. public class CustomHubFilter : IHubFilter
  2. {
  3.     public async ValueTask<object> InvokeMethodAsync(
  4.         HubInvocationContext invocationContext,
  5.         Func<HubInvocationContext, ValueTask<object>> next)
  6.     {
  7.         // 记录方法调用
  8.         Console.WriteLine($"调用 {invocationContext.HubMethodName}");
  9.         
  10.         // 检查权限等
  11.         
  12.         return await next(invocationContext);
  13.     }
  14. }
  15. // 注册全局过滤器
  16. services.AddSignalR(options => {
  17.     options.AddFilter<CustomHubFilter>();
  18. });
复制代码
6. SignalR 最佳实践


  • 连接管理

    • 始终处置惩罚断开连接和重连
    • 在客户端检测网络状态变革
    • 使用 withAutomaticReconnect 设置合理的重试计谋

  • 安全性

    • 始终验证输入
    • 使用 HTTPS
    • 实现得当的授权
    • 限制消息大小

  • 性能

    • 对于高频率消息,思量批处置惩罚
    • 使用二进制协议(MessagePack)淘汰负载
    • 得当设置保持活动间隔

  • 监控

    • 记载连接和断开事件
    • 监控消息速率
    • 设置警报阈值

7. SignalR 与其他技术的对比

特性SignalR原生 WebSocketSSE长轮询协议主动选择最佳WebSocketHTTPHTTP双向通信是是否否主动重连是需手动实现是需手动实现传输效率高高中低服务器负载低低中高复杂度低高中低.NET集成完善需手动处置惩罚需手动处置惩罚需手动处置惩罚 8. 实际应用场景

8.1 及时聊天应用

  1. public class ChatHub : Hub
  2. {
  3.     private static readonly Dictionary<string, string> _users = new();
  4.    
  5.     public async Task RegisterUser(string username)
  6.     {
  7.         _users[Context.ConnectionId] = username;
  8.         await Clients.All.SendAsync("UserJoined", username);
  9.     }
  10.    
  11.     public async Task SendMessage(string message)
  12.     {
  13.         if (_users.TryGetValue(Context.ConnectionId, out var username))
  14.         {
  15.             await Clients.All.SendAsync("ReceiveMessage", username, message);
  16.         }
  17.     }
  18.    
  19.     public override async Task OnDisconnectedAsync(Exception exception)
  20.     {
  21.         if (_users.TryGetValue(Context.ConnectionId, out var username))
  22.         {
  23.             _users.Remove(Context.ConnectionId);
  24.             await Clients.All.SendAsync("UserLeft", username);
  25.         }
  26.         await base.OnDisconnectedAsync(exception);
  27.     }
  28. }
复制代码
8.2 及时数据仪表盘

  1. public class DashboardHub : Hub
  2. {
  3.     private readonly IDataService _dataService;
  4.    
  5.     public DashboardHub(IDataService dataService)
  6.     {
  7.         _dataService = dataService;
  8.     }
  9.    
  10.     public async Task SubscribeToUpdates()
  11.     {
  12.         var data = await _dataService.GetInitialData();
  13.         await Clients.Caller.SendAsync("InitialData", data);
  14.         
  15.         // 开始推送更新
  16.         var cancellationToken = Context.GetHttpContext().RequestAborted;
  17.         await foreach (var update in _dataService.GetDataUpdates(cancellationToken))
  18.         {
  19.             await Clients.Caller.SendAsync("DataUpdate", update);
  20.         }
  21.     }
  22. }
复制代码
8.3 多人协作编辑

  1. public class CollaborationHub : Hub
  2. {
  3.     private readonly ICollaborationService _collabService;
  4.    
  5.     public CollaborationHub(ICollaborationService collabService)
  6.     {
  7.         _collabService = collabService;
  8.     }
  9.    
  10.     public async Task JoinDocument(string docId)
  11.     {
  12.         await Groups.AddToGroupAsync(Context.ConnectionId, docId);
  13.         
  14.         var document = await _collabService.GetDocument(docId);
  15.         var users = await _collabService.GetDocumentUsers(docId);
  16.         
  17.         await Clients.Caller.SendAsync("DocumentLoaded", document);
  18.         await Clients.Group(docId).SendAsync("UsersUpdated", users);
  19.     }
  20.    
  21.     public async Task EditDocument(string docId, DocumentEdit edit)
  22.     {
  23.         await _collabService.ApplyEdit(docId, edit);
  24.         await Clients.OthersInGroup(docId).SendAsync("DocumentEdited", edit);
  25.     }
  26. }
复制代码
9. 常见标题解决方案

9.1 连接标题排查


  • 查抄传输协议
    1. connection.onclose(error => {
    2.     console.log("Connection closed due to error: ", error);
    3.     console.log("Last transport: ", connection.connection.transport.name);
    4. });
    复制代码
  • 启用详细日记
    1. services.AddSignalR()
    2.     .AddHubOptions<ChatHub>(options => {
    3.         options.EnableDetailedErrors = true;
    4.     });
    复制代码
  • 查抄 CORS 设置
    1. services.AddCors(options => {
    2.     options.AddPolicy("SignalRCors", builder => {
    3.         builder.WithOrigins("https://yourdomain.com")
    4.                .AllowAnyHeader()
    5.                .AllowAnyMethod()
    6.                .AllowCredentials();
    7.     });
    8. });
    复制代码
9.2 性能标题优化


  • 使用 MessagePack
    1. services.AddSignalR()
    2.     .AddMessagePackProtocol();
    复制代码
  • 限制消息大小
    1. services.AddSignalR(options => {
    2.     options.MaximumReceiveMessageSize = 32768; // 32KB
    3. });
    复制代码
  • 批处置惩罚消息
    1. // 在客户端
    2. let batch = [];
    3. setInterval(() => {
    4.     if (batch.length > 0) {
    5.         connection.send("SendBatch", batch);
    6.         batch = [];
    7.     }
    8. }, 100);
    复制代码
9.3 横向扩展标题


  • 使用 Azure SignalR 服务
    1. services.AddSignalR()
    2.     .AddAzureSignalR("Endpoint=...;AccessKey=...");
    复制代码
  • 实现自定义背板
    1. public class CustomBackplane : IHubLifetimeManager
    2. {
    3.     // 实现必要接口方法
    4. }
    5. services.AddSingleton<IHubLifetimeManager, CustomBackplane>();
    复制代码
10. 未来展望

SignalR 作为 .NET 及时通信的焦点组件,未来可能会:

  • 集成更高效的二进制协议
  • 改进移动端支持
  • 增强与 WebRTC 的集成
  • 提供更好的离线消息处置惩罚
  • 优化大规模集群支持
结论

SignalR 是 .NET 生态中最强大、最成熟的及时通信解决方案,它抽象了底层传输细节,提供了简单易用的 API,并主动处置惩罚了连接管理、重连等复杂标题。无论是构建聊天应用、及时仪表盘照旧协作体系,SignalR 都能提供稳固高效的及时通信能力。
文章读了终究是文章,一定要自己手敲一遍,才华吸收获为自己的知识!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

欢乐狗

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表