signalr即时通讯库 #

什么是即时通讯 #

即时通讯(Instant messaging,简称 IM)通常是指互联网上用以进行实时通讯的系统,允许两人或多人使用网络即时的传递文字信息、文档、语音与视频交流。

即时通讯不同于 e-mail 在于它的交谈是实时的。大部分的即时通讯服务提供了状态信息的特性 ── 显示联络人名单,联络人是否在线上与能否与联络人交谈。

在互联网上目前使用较广的即时通讯服务包括 Windows Live Messenger、AOL Instant Messenger、skype、Yahoo! Messenger、NET Messenger Service、Jabber、ICQ 与 QQ 等。

即时通讯应用场景 #

即时通讯应用场景非常广泛,需要实时交互消息的都需要。如:

  • 聊天工具:QQ、WeChat、在线客服等
  • 手游网游:王者荣耀、魔兽等
  • 网络直播:腾讯课堂、抖音直播等
  • 订单推送:美团、餐饮下单系统等
  • 协同办公:公司内部文件分享、工作安排、在线会议等。

以上只是列举了比较常用的应用场景,但即时通讯的作用远不止于此。

文档紧急编写中,可以先看官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/signalr/introduction?view=aspnetcore-5.0

关于 SignalR #

即时通讯技术实现是复杂且过于底层化,所以微软为了简化即时通讯应用程序,开发出了一个强大且简易使用的通信库:SignalR,通过该库我们可以轻松实现类似 QQ、微信这类 IM 聊天工具,也能快速实现消息推送、订单推送这样的系统。

微软官方介绍 #

ASP.NET Core SignalR 是一种开放源代码库,可简化将实时 web 功能添加到应用程序的功能。 实时 web 功能使服务器端代码可以立即将内容推送到客户端。

适用于 SignalR :

  • 需要从服务器进行高频率更新的应用。 示例包括游戏、社交网络、投票、拍卖、地图和 GPS 应用。
  • 仪表板和监视应用。 示例包括公司仪表板、即时销售更新或旅行警报。
  • 协作应用。 协作应用的示例包括白板应用和团队会议软件。
  • 需要通知的应用。 社交网络、电子邮件、聊天、游戏、旅行警报和很多其他应用都需使用通知。

目前 SignalR 已经内置在 .NET 5 SDK 中。同时 SignalR 支持 Web、App、Console、Desktop 等多个应用平台。

注册 SignalR 服务 #

Mall3s 框架中,任何服务功能都需要先注册后再使用,SignalR 也不例外。只需要在 Startup.cs 中添加注册即可:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Mall3s.Web.Core
{
    public sealed class Startup : AppStartup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            // 其他代码...

            // 添加即时通讯
            services.AddSignalR();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            // 其他代码...

            app.UseEndpoints(endpoints =>
            {
                // 注册集线器
                endpoints.MapHubs();

                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

SignalR 长连接和集线器 #

SignalR 包含两种用于在客户端和服务器之间进行通信的模型:持久性连接集线器 中心。

持久性连接 #

连接表示用于发送单接收方、分组或广播消息的简单终结点。 持久性连接 (在 .NET 代码中由 PersistentConnection 类表示,在 ASP.NET Core SignalR 中 ,PersistentConnection 类已被删除。) 使开发人员能够直接访问 SignalR 公开的低级别通信协议。 使用基于连接的 Api (如 Windows Communication Foundation)的开发人员将对使用连接通信模型非常熟悉。

集线器 #

集线器是一种基于连接 API 构建的更高级别管道,它允许客户端和服务器直接调用方法SignalR 就像魔术一样处理跨机器边界的调度,使客户端能够像本地方法一样轻松地调用服务器上的方法,反之亦然。 如果开发人员已使用远程调用 (如 .NET 远程处理),则将对使用中心通信模型非常熟悉。 使用集线器还可以将强类型参数传递给方法,从而启用模型绑定。

小知识

想了解更多关于 持久性连接集线器中心 可查阅 SignalR 官方文档 (opens new window)

集线器 Hub 定义 #

**在本章节中主要推荐使用集线器通信模型方式。**这里主要说明 Hub 定义,如果无法理解该通信模型的作用也没关系,接下来的例子会带大家慢慢熟悉并使用。

两种定义方式 #

定义集线器只需要继承 HubHub<TStrongType> 泛型基类即可,如:

  • Hub 方式
using Mall3s.InstantMessaging;
using Microsoft.AspNetCore.SignalR;

namespace Mall3s.Core
{
    /// <summary>
    /// 聊天集线器
    /// </summary>
    public class ChatHub : Hub
    {
        // 定义一个方法供客户端调用
        public Task SendMessage(string user, string message)
        {
            // 触发客户端定义监听的方法
            return Clients.All.SendAsync("ReceiveMessage", user, message);
        }
    }
}
  • Hub<TStrongType> 类型方式
public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}
public class StronglyTypedChatHub : Hub<IChatClient>
{
    // 定义一个方法供客户端调用
    public async Task SendMessage(string user, string message)
    {
        // 触发客户端定义监听的方法
        await Clients.All.ReceiveMessage(user, message);
    }
}

通过使用 Hub<IChatClient> 可以对客户端方法进行编译时检查。 这可以防止由于使用神奇字符串而导致的问题,因为 Hub<T> 只能提供对在接口中定义的方法的访问。

[MapHub] 配置连接地址 #

SignalR 库中要求每一个公开的集线器都需要配置客户端连接地址,所以,Mall3s 框架提供了更加 [MapHub] 配置,如:

using Mall3s.InstantMessaging;
using Microsoft.AspNetCore.SignalR;
using System;
using System.Threading.Tasks;

namespace Mall3s.Core
{
    /// <summary>
    /// 聊天集线器
    /// </summary>
    [MapHub("/hubs/chathub")]
    public class ChatHub : Hub
    {
        // ...
    }
}

`SignalR` 原生配置方式

Mall3s 中推荐使用 [MapHub] 方式配置集线器客户端连接地址,当然也可以使用 SignalR 提供的方式,如在 Startup.cs 配置:

public sealed class Startup : AppStartup
{
   // 其他代码
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // 其他代码...
        app.UseEndpoints(endpoints =>
        {
            // 注册集线器
            endpoints.MapHub<ChatHub>("/hubs/chathub");
        });
    }
}

Hub 注册更多配置 #

有些时候,我们需要注册 Hub 时配置更多参数,比如权限、跨域等,这时只需要在 Hub 派生类中编写以下静态方法即可:

using Mall3s.InstantMessaging;
using Microsoft.AspNetCore.SignalR;
using System;
using System.Threading.Tasks;

namespace Mall3s.Core
{
    [MapHub("/hubs/chathub")]
    public class ChatHub : Hub
    {
        // 其他代码

        public static void HttpConnectionDispatcherOptionsSettings(HttpConnectionDispatcherOptions options)
        {
            // 配置
        }

        public static void HubEndpointConventionBuilderSettings(HubEndpointConventionBuilder Builder)
        {
            // 配置
        }
    }
}

以上配置等价于 SignalRStartup.cs 中的配置:

app.UseEndpoints(endpoints =>
{
    var builder = endpoints.MapHub<ChatHub>("/hubs/chathub", options =>
       {
           // 配置
       });
});

获取 Hub 实例方式 #

SignalR 提供了几种方式进行获取 Hub 实例。

IHubContext 注入方式 #

IHubContext 默认注册为单例模式,可在任何地方直接获取实例。

public class HomeController : Controller
{
    private readonly IHubContext<NotificationHub> _hubContext;

    public HomeController(IHubContext<NotificationHub> hubContext)
    {
        _hubContext = hubContext;
    }

    public async Task<IActionResult> Index()
    {
        await _hubContext.Clients.All.SendAsync("Notify", $"Home page loaded at: {DateTime.Now}");
        return View();
    }
}

HttpContext 解析方式 #

 var hubContext = context.RequestServices
                         .GetRequiredService<IHubContext<ChatHub>>();

IHost 中解析方式 #

public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();
        var hubContext = host.Services.GetService(typeof(IHubContext<ChatHub>));
        host.Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder => {
                webBuilder.UseStartup<Startup>();
            });
}

强类型 IHubContext 注入方式 #

默认情况下,IHubContext 非泛型实例返回的是 dynamic 动态类型对象,该类型对象无法获得编译期语法检查和 IDE 智能提示,所以我们可以传入一个和自定义 Hub 一样的方法签名接口,如:

public class ChatController : Controller
{
    public IHubContext<ChatHub, IChatClient> _strongChatHubContext { get; }

    public ChatController(IHubContext<ChatHub, IChatClient> chatHubContext)
    {
        _strongChatHubContext = chatHubContext;
    }

    public async Task SendMessage(string user, string message)
    {
        await _strongChatHubContext.Clients.All.ReceiveMessage(user, message);
    }
}

IHubContext 泛型转换 #

正常情况下,我们获取的是 IHubContext<> 的实例,但在一些反射场景下,可以将 IHubContext<> 强制转换成 IHubContext 从而更易于操作,如:

var myHubContext = context.RequestServices
                          .GetRequiredService<IHubContext<MyHub>>();
var myOtherHubContext = context.RequestServices
                               .GetRequiredService<IHubContext<MyOtherHub>>();

await CommonHubContextMethod((IHubContext)myHubContext);
await CommonHubContextMethod((IHubContext)myOtherHubContext);

服务端和客户端双工通信 #

触发所有客户端代码 #

Clients.All.客户端方法(参数);

触发调用者客户端 #

Clients.Caller.客户端方法(参数);

触发除了调用者以外的客户端 #

Clients.Others.客户端方法(参数);

触发特定用户客户端 #

Clients.User("用户").客户端方法(参数);

触发多个用户客户端 #

Clients.Users("用户","用户2",...).客户端方法(参数);

触发分组内客户端 #

Clients.Group("分组").客户端方法(参数);

触发多个分组客户端 #

Clients.Groups("分组","分组2",...).客户端方法(参数);

触发分组外的客户端 #

Clients.GroupExcept("分组").客户端方法(参数);

自定义用户唯一标识 #

默认情况下 SignalR 会为每一个链接创建 ConnectionId,但是这个 ConnectionId 并没有和我们系统的用户绑关联起来,所以需要采用自定义 ConnectionId,如:

public class YourUserIdProvider : IUserIdProvider
{
    public virtual string GetUserId(HubConnectionContext connection)
    {
        // 你如何获取 UserId,可以通过 connection.User 获取 JWT 授权的用户
    }
}

然后在 Startup.cs 中注册即可:

builder.Services.AddSingleton<IUserIdProvider, YourUserIdProvider>();

之后就可以通过自定义 UserId 发送消息:

Clients.User(userId).客户端方法(参数);

查看更多文档 https://docs.microsoft.com/zh-cn/aspnet/core/signalr/authn-and-authz?view=aspnetcore-6.0#use-claims-to-customize-identity-handling (opens new window)

分组管理 #

各个客户端连接 API #

Javascript 客户端 #

Typescript 客户端 #

vue3.2+ 中使用 #

  1. 安装微软的 signalr typescript 客户端包,主要用于调用服务端方法,如( Hub 中的 SendMessage 方法):
npm i @microsoft/signalr @types/node
  1. 示例代码
import { HubConnectionBuilder } from "@microsoft/signalr";

<script setup lang="ts">
    import { HubConnectionBuilder } from "@microsoft/signalr";
    import { ref } from "vue";

    const messages = ref('');
    const reciveMessage = (msg: string) => {
        console.log("msg", msg);
    }

    //初始化signalr HubConnection对象
    const connection = new HubConnectionBuilder()
        .withUrl("<你的signalr连接地址>")//https://localhost:7260/chatHub
        .build();

    //启动连接并发送消息测试
    connection.start()
              .then(() => connection.send("SendMessage", "Hello"));

    //注册web端方法以供后端调用
    connection.on("ReciveMessage", reciveMessage);

    const sendMsg = async () => {
        console.log(messages.value);
        await connection.send("SendMessage", messages.value).catch(function (err) {
            console.log(err);
        });
    }
</script>

参考文档 (opens new window)


了解更多

想了解更多 SignalR 知识可查阅 SignalR 官方文档 (opens new window)ASP.NET Core SignalR (opens new window) 章节。

上次更新: 3/10/2023, 5:09:25 PM