实用技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > ASP.NET > 实用技巧 > .NET SignalR实时通信

.NET使用SignalR实现实时通信的完全指南

作者:码上有潜

SignalR 是一个为 ASP.NET 开发者提供的开源库,它极大地简化了向应用程序添加实时 Web 功能的过程,下面我们就来看看如何使用SignalR实现实时通信的功能吧

1. SignalR 概述

SignalR 是一个为 ASP.NET 开发者提供的开源库,它极大地简化了向应用程序添加实时 Web 功能的过程。所谓"实时 Web"功能,是指服务器代码能够即时将内容推送到连接的客户端,而不需要服务器等待客户端请求新数据。

1.1 SignalR 的核心价值

SignalR 为开发者提供了三大核心价值:

1.2 SignalR 的发展历程

1.3 SignalR 的架构组成

SignalR 由以下几个关键组件构成:

  1. Hubs:高级管道,允许客户端和服务器直接相互调用方法
  2. Persistent Connections:低级管道,用于需要更精细控制的场景
  3. 传输层:自动处理 WebSocket、Server-Sent Events 和长轮询
  4. 横向扩展支持:通过 Redis、Azure SignalR 等服务支持大规模部署

2. SignalR 的核心功能

2.1 自动传输选择

SignalR 会自动从以下几种传输方式中选择最佳方案:

  1. WebSocket:首选,提供全双工通信
  2. Server-Sent Events (SSE):当 WebSocket 不可用时使用
  3. 长轮询:作为最后的后备方案
// 示例:在 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 提供多种客户端支持:

  1. JavaScript 客户端:用于 Web 前端
  2. .NET 客户端:用于 WPF、Xamarin 等应用
  3. Java 客户端:用于 Android 应用
  4. 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 服务器端配置

基本配置

// 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");
    });
}

高级配置

services.AddSignalR(hubOptions => {
    // 启用详细错误消息(开发环境)
    hubOptions.EnableDetailedErrors = true;
    
    // 配置保持活动状态
    hubOptions.KeepAliveInterval = TimeSpan.FromSeconds(15);
    
    // 限制最大消息大小
    hubOptions.MaximumReceiveMessageSize = 65536;
});

3.2 客户端实现

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);
    }
}

 .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 最佳实践

连接管理

安全性

性能

监控

7. SignalR 与其他技术的对比

特性SignalR原生 WebSocketSSE长轮询
协议自动选择最佳WebSocketHTTPHTTP
双向通信
自动重连需手动实现需手动实现
传输效率
服务器负载
复杂度
.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 实时通信的核心组件,未来可能会:

  1. 集成更高效的二进制协议
  2. 改进移动端支持
  3. 增强与 WebRTC 的集成
  4. 提供更好的离线消息处理
  5. 优化大规模集群支持

结论

SignalR 是 .NET 生态中最强大、最成熟的实时通信解决方案,它抽象了底层传输细节,提供了简单易用的 API,并自动处理了连接管理、重连等复杂问题。无论是构建聊天应用、实时仪表盘还是协作系统,SignalR 都能提供稳定高效的实时通信能力。

以上就是.NET使用SignalR实现实时通信的完全指南的详细内容,更多关于.NET SignalR实时通信的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文