IT俱乐部 ASP.NET .NET8使用SignalR实现实时通信的完整指南

.NET8使用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");

app.Run();

3. 创建 Hub 类

创建一个继承自 Hub 的类来处理客户端连接和消息:

using Microsoft.AspNetCore.SignalR;
using System.Collections.Concurrent;

namespace SignalRDemo;

public class ChatHub : Hub
{
    // 存储用户与连接ID的映射
    private static readonly ConcurrentDictionary _userConnections = new();
    // 存储群组与连接ID的映射
    private static readonly ConcurrentDictionary> _groupConnections = new();

    /// 
    /// 客户端连接时触发
    /// 
    public override async Task OnConnectedAsync()
    {
        Console.WriteLine($"客户端 {Context.ConnectionId} 已连接");
        await base.OnConnectedAsync();
    }

    /// 
    /// 客户端断开连接时触发
    /// 
    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);
    }

    /// 
    /// 用户登录(绑定用户名和连接ID)
    /// 
    public async Task Login(string username)
    {
        _userConnections[username] = Context.ConnectionId;
        await Clients.Caller.SendAsync("LoginSuccess", $"你已登录为 {username}");
    }

    /// 
    /// 加入群组
    /// 
    public async Task JoinGroup(string groupName)
    {
        if (!_groupConnections.ContainsKey(groupName))
        {
            _groupConnections[groupName] = new HashSet();
        }
        _groupConnections[groupName].Add(Context.ConnectionId);

        await Clients.Caller.SendAsync("JoinGroupSuccess", $"你已加入群组 {groupName}");
    }

    /// 
    /// 发送消息给所有人
    /// 
    public async Task SendMessageToAll(string user, string message)
    {
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }

    /// 
    /// 发送消息给单个人
    /// 
    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} 不在线");
        }
    }

    /// 
    /// 发送消息给多个人
    /// 
    public async Task SendMessageToUsers(IReadOnlyList targetUsers, string user, string message)
    {
        var connectionIds = new List();
        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", "没有目标用户在线");
        }
    }

    /// 
    /// 发送消息给群组
    /// 
    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:



    SignalR Chat
    

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

    三、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("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 协议,使用 EventSource API。
    • 适用于需要实时更新但不需要双向通信的场景。

    3. 长轮询 (Long Polling)

    • 如果 SSE 也不可用,SignalR 会使用长轮询作为最后的 fallback。
    • 客户端向服务器发送一个请求,服务器保持连接打开,直到有数据要发送或超时。
    • 超时后,客户端会立即发送一个新的请求。
    • 性能较差,但兼容性最好,适用于所有浏览器。

    通信技术选择流程

    客户端发送一个 HTTP 请求到服务器的 /chatHub/negotiate 端点,获取可用的通信技术列表。

    客户端按照优先级顺序尝试使用这些技术:

    • 首先尝试 WebSocket。
    • 如果 WebSocket 失败,尝试 SSE。
    • 如果 SSE 也失败,使用长轮询。

    一旦成功建立连接,就使用该技术进行实时通信。

    技术对比

    技术 双向通信 性能 兼容性
    WebSocket 现代浏览器
    SSE 否(服务器到客户端) 大多数现代浏览器
    长轮询 所有浏览器

    五、注意事项

    • CORS 配置:必须在服务端正确配置 CORS,允许客户端来源并启用 AllowCredentials
    • 连接管理:使用 OnConnectedAsyncOnDisconnectedAsync 管理客户端连接状态。
    • 用户映射:维护用户与 ConnectionId 的映射,以便实现单人和多人消息发送。
    • 群组管理:使用 Groups 类或自定义映射管理群组。
    • 错误处理:客户端和服务端都需要处理连接中断和异常情况。
    • 扩展性:如果需要支持大规模部署,可以使用 Redis 作为 SignalR 的后端,实现多服务器之间的消息共享。

    通过本文的指南,你可以快速在 .NET 8 中使用 SignalR 实现实时通信,并支持多种消息发送方式。无论是前端浏览器还是 WinForms 客户端,都能轻松接入 SignalR 服务。

    以上就是.NET8使用SignalR实现实时通信的完整指南的详细内容,更多关于.NET8 SignalR实时通信的资料请关注IT俱乐部其它相关文章!

    本文收集自网络,不代表IT俱乐部立场,转载请注明出处。https://www.2it.club/code/asp-net/17015.html
    上一篇
    下一篇
    联系我们

    联系我们

    在线咨询: QQ交谈

    邮箱: 1120393934@qq.com

    工作时间:周一至周五,9:00-17:30,节假日休息

    关注微信
    微信扫一扫关注我们

    微信扫一扫关注我们

    返回顶部