C# SignalR实时通信开发权威指南


一、SignalR核心概念

1.1 SignalR架构原理

SignalR是ASP.NET Core提供的实时通信库,其核心机制包括:

  • 自动传输选择:在WebSocket、Server-Sent Events和长轮询间自动选择最佳传输方式
  • 连接管理:内置连接生命周期管理
  • 消息总线:支持横向扩展的消息分发系统
  • 中心(Hub):高级抽象层,简化客户端-服务器通信模式

1.2 适用场景分析

场景类型典型应用SignalR优势
即时通讯聊天应用低延迟、双向通信
实时监控仪表盘服务端主动推送
协同编辑在线文档状态同步高效
游戏交互多人在线游戏高频率消息交换
通知系统订单状态更新定向消息推送

二、服务端实现

2.1 基础服务配置

// Program.cs配置
var builder = WebApplication.CreateBuilder(args);

// 添加SignalR服务
builder.Services.AddSignalR(options => {
    options.EnableDetailedErrors = true;
    options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
    options.KeepAliveInterval = TimeSpan.FromSeconds(15);
});

var app = builder.Build();

// 配置Hub路由
app.MapHub<ChatHub>("/hubs/chat");
app.MapHub<NotificationHub>("/hubs/notifications");

app.Run();

2.2 Hub类实现范例

public class ChatHub : Hub
{
    private readonly IMessageService _messageService;

    public ChatHub(IMessageService messageService)
    {
        _messageService = messageService;
    }

    // 客户端调用方法
    public async Task SendMessage(string user, string message)
    {
        // 保存消息到数据库
        var msg = await _messageService.SaveMessageAsync(user, message);

        // 广播给所有客户端
        await Clients.All.SendAsync("ReceiveMessage", msg.User, msg.Content, msg.Timestamp);

        // 仅发送给特定组
        await Clients.Group("VIP").SendAsync("VIPMessage", msg);
    }

    // 连接生命周期方法
    public override async Task OnConnectedAsync()
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, "General");
        await base.OnConnectedAsync();
    }

    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        await Groups.RemoveFromGroupAsync(Context.ConnectionId, "General");
        await base.OnDisconnectedAsync(exception);
    }
}

三、客户端开发

3.1 JavaScript客户端

// 建立连接
const connection = new signalR.HubConnectionBuilder()
    .withUrl("/hubs/chat")
    .configureLogging(signalR.LogLevel.Information)
    .build();

// 注册接收方法
connection.on("ReceiveMessage", (user, message, timestamp) => {
    const msg = `${user}: ${message} (${new Date(timestamp).toLocaleTimeString()})`;
    appendMessage(msg);
});

// 启动连接
async function startConnection() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(startConnection, 5000);
    }
}

// 调用服务端方法
async function sendMessage() {
    const user = document.getElementById("userInput").value;
    const message = document.getElementById("messageInput").value;
    try {
        await connection.invoke("SendMessage", user, message);
    } catch (err) {
        console.error(err);
    }
}

3.2 .NET客户端

var connection = new HubConnectionBuilder()
    .WithUrl("https://localhost:5001/hubs/chat")
    .WithAutomaticReconnect(new[] {
        TimeSpan.Zero,
        TimeSpan.FromSeconds(2),
        TimeSpan.FromSeconds(10),
        TimeSpan.FromSeconds(30)
    })
    .ConfigureLogging(logging => {
        logging.SetMinimumLevel(LogLevel.Debug);
    })
    .Build();

// 注册消息处理器
connection.On<string, string>("ReceiveMessage", (user, message) => {
    Console.WriteLine($"{user}: {message}");
});

// 连接管理
async Task StartConnectionAsync()
{
    try
    {
        await connection.StartAsync();
    }
    catch (Exception ex)
    {
        Console.WriteLine($"连接失败: {ex.Message}");
        await Task.Delay(1000);
        await StartConnectionAsync();
    }
}

// 调用服务端方法
await connection.InvokeAsync("SendMessage", "user1", "Hello from .NET client");

四、高级功能实现

4.1 用户身份集成

// 认证Hub
[Authorize]
public class AuthHub : Hub
{
    public override async Task OnConnectedAsync()
    {
        var userId = Context.User?.FindFirstValue(ClaimTypes.NameIdentifier);
        await Groups.AddToGroupAsync(Context.ConnectionId, $"user-{userId}");
        await base.OnConnectedAsync();
    }

    public async Task SendPrivateMessage(string targetUserId, string message)
    {
        var sender = Context.User?.Identity?.Name;
        await Clients.Group($"user-{targetUserId}")
            .SendAsync("ReceivePrivateMessage", sender, message);
    }
}

// 带认证的JavaScript客户端
const connection = new signalR.HubConnectionBuilder()
    .withUrl("/hubs/auth", {
        accessTokenFactory: () => {
            return getAuthToken(); // 返回当前用户的JWT
        }
    })
    .build();

4.2 横向扩展方案

// 使用Redis作为背板
builder.Services.AddSignalR()
    .AddStackExchangeRedis("localhost:6379", options => {
        options.Configuration.ChannelPrefix = "MyApp_";
    });

// Azure SignalR服务
builder.Services.AddSignalR()
    .AddAzureSignalR("Endpoint=https://<resource>.service.signalr.net;AccessKey=<access-key>;");

4.3 流式传输

// 服务端流方法
public async IAsyncEnumerable<int> CounterStream(int count)
{
    for (var i = 0; i < count; i++)
    {
        yield return i;
        await Task.Delay(1000);
    }
}

// 客户端调用
const streamResult = connection.stream("CounterStream", 10);
streamResult.subscribe({
    next: (item) => console.log(item),
    complete: () => console.log("Stream completed"),
    error: (err) => console.error(err)
});

五、性能优化策略

5.1 消息压缩配置

services.AddSignalR()
    .AddMessagePackProtocol(options => {
        options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
        {
            MessagePack.Resolvers.StandardResolver.Instance
        };
    });

5.2 连接过滤

public class CustomFilter : IHubFilter
{
    public async ValueTask<object?> InvokeMethodAsync(
        HubInvocationContext invocationContext,
        Func<HubInvocationContext, ValueTask<object?>> next)
    {
        // 前置处理
        if (invocationContext.HubMethodName == "SendMessage")
        {
            var message = invocationContext.HubMethodArguments[1] as string;
            if (string.IsNullOrEmpty(message))
                throw new HubException("消息不能为空");
        }

        var result = await next(invocationContext);

        // 后置处理
        Console.WriteLine($"方法 {invocationContext.HubMethodName} 被调用");
        return result;
    }
}

// 注册全局过滤器
services.AddSignalR(options => {
    options.AddFilter<CustomFilter>();
});

六、安全防护机制

6.1 跨域策略

// 配置CORS
builder.Services.AddCors(options => {
    options.AddPolicy("SignalRPolicy", builder => {
        builder.WithOrigins("https://example.com")
               .AllowAnyHeader()
               .AllowAnyMethod()
               .AllowCredentials();
    });
});

app.UseCors("SignalRPolicy");

6.2 防伪造保护

services.AddSignalR(options => {
    options.HandshakeTimeout = TimeSpan.FromSeconds(15);
    options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
});

// 连接令牌验证
public class TokenValidationHub : Hub
{
    public override async Task OnConnectedAsync()
    {
        var token = Context.GetHttpContext()?.Request.Query["access_token"];
        if (!ValidateToken(token))
        {
            Context.Abort();
            return;
        }
        await base.OnConnectedAsync();
    }
}

七、监控与诊断

7.1 健康检查集成

// 添加SignalR健康检查
builder.Services.AddHealthChecks()
    .AddSignalRHub("https://localhost:5001/hubs/chat", "chat-hub");

// 客户端检测连接状态
connection.onclose((error) => {
    if (connection.state === signalR.HubConnectionState.Disconnected) {
        attemptReconnect();
    }
});

7.2 日志记录配置

// 服务端日志
builder.Services.AddSignalR()
    .AddHubOptions<ChatHub>(options => {
        options.EnableDetailedErrors = true;
        options.ClientTimeoutInterval = TimeSpan.FromMinutes(1);
    });

// 客户端日志(.NET)
var connection = new HubConnectionBuilder()
    .WithUrl("...")
    .ConfigureLogging(logging => {
        logging.AddConsole();
        logging.SetMinimumLevel(LogLevel.Trace);
    })
    .Build();

八、实战案例:实时协作白板

8.1 服务端实现

public class WhiteboardHub : Hub
{
    private static readonly Dictionary<string, List<DrawAction>> _boards = new();

    public async Task JoinBoard(string boardId)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, boardId);

        if (_boards.TryGetValue(boardId, out var actions))
        {
            // 同步现有绘制动作
            await Clients.Caller.SendAsync("SyncBoard", actions);
        }
        else
        {
            _boards[boardId] = new List<DrawAction>();
        }
    }

    public async Task SendDrawAction(string boardId, DrawAction action)
    {
        if (_boards.ContainsKey(boardId))
        {
            _boards[boardId].Add(action);
            await Clients.OthersInGroup(boardId).SendAsync("ReceiveDrawAction", action);
        }
    }
}

public record DrawAction(Point Start, Point End, string Color, int LineWidth);

8.2 客户端实现

// 连接白板Hub
const boardConnection = new signalR.HubConnectionBuilder()
    .withUrl("/hubs/whiteboard")
    .build();

boardConnection.on("ReceiveDrawAction", (action) => {
    drawOnCanvas(action);
});

boardConnection.on("SyncBoard", (actions) => {
    clearCanvas();
    actions.forEach(drawOnCanvas);
});

async function joinBoard(boardId) {
    await boardConnection.invoke("JoinBoard", boardId);
}

function sendDrawAction(action) {
    boardConnection.invoke("SendDrawAction", currentBoardId, action)
        .catch(err => console.error(err));
}

九、性能基准与优化

9.1 负载测试指标

指标推荐值优化方向
连接建立时间<500ms减少握手数据
消息延迟<100ms使用二进制协议
并发连接数10K+/节点横向扩展
内存占用<1GB/10K连接调优GC策略

9.2 配置调优

builder.Services.AddSignalR(options => {
    // 传输控制
    options.Transports = HttpTransportType.WebSockets | HttpTransportType.LongPolling;

    // 消息缓冲区
    options.MaximumReceiveMessageSize = 64 * 1024; // 64KB

    // 流控制
    options.StreamBufferCapacity = 10;

    // KeepAlive设置
    options.KeepAliveInterval = TimeSpan.FromSeconds(15);
    options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
});

十、常见问题解决方案

10.1 连接问题排查

  1. WebSocket无法建立
  • 检查服务器是否支持WebSocket
  • 验证代理服务器配置
  • 确保CORS策略正确
  1. 间歇性断开连接
   // 自动重连策略
   connection.onclose(async () => {
       await new Promise(resolve => setTimeout(resolve, 1000));
       startConnection();
   });

10.2 性能问题处理

  • 高延迟
  • 启用消息压缩
  • 使用MessagePack替代JSON
  • 就近部署SignalR服务器
  • 高内存占用
  • 限制消息大小
  • 优化Hub方法实现
  • 增加服务器节点

10.3 生产环境建议

  1. 部署架构
  • 使用Azure SignalR服务或Redis背板实现横向扩展
  • 为SignalR配置独立子域(如signalr.example.com)
  1. 监控指标
  • 活跃连接数
  • 消息吞吐量
  • 错误率
  • 平均延迟
  1. 灾难恢复
  • 多区域部署
  • 连接状态备份
  • 优雅降级方案

通过本指南介绍的技术方案,开发者可以构建出高性能、可靠的实时通信系统。SignalR的强大功能结合.NET平台的稳定性,使其成为企业级实时应用的首选解决方案。


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注