• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
netcore_vue
博客园    首页    新随笔    联系   管理    订阅  订阅

SignalR长连接在ABPVNext中应用,并结合实际业务服务使用

长轮询SignalR

     了解SignalR

统计在线人数或发通信等功能,需要长轮询机制,最佳应用就是WebSocket,首先理解一下WebSocket:

  1. WebSocket基于TCP协议,支持二进制通信,双工通信。(两端可以互相发送)
  2. 性能和并发能力更强。
  3. WebSocket独立于HTTP协议,不过一般仍然把WebSocket服务器端部署到Web服务器上,因为可以借助HTTP协议完成初始的握手(可选),并且共享HTTP服务器的端口。

注意:webSocket 和 HTTP 是同一等级的,他们都是基于TCP协议的,HTTP是文本通讯,消耗比较高,WebSocekt是二进制,消耗低。

在ABP中,我们使用SignalR,SignalR可以理解成非常简单易用的高阶的API,使服务器端可以单个或批量调用客户端上的JavaScript函数,并且非常方便地进行连接管理,例如客户端连接到服务端,或断开连接,客户端分组等使用SignalR都非常容易实现。

l  ASP.NET Core SignalR(以下简称SignalR),是.NET Core平台下对WebSocket的封装,可理解为一个WebSockets API,原理和WebSockets是一致的。

l  Hub(集线器),数据交换中心。

 

说明:可以按照标准的微软教程添加SignalR到你的应用程序,但ABP提供了简化集成的SignalR集成包。

       SignalR使用的其他场景,举个例子:

       前端需要请求一个excel文件,这个excel文件是一个由后端动态生成的,前端传很多过滤条件,后端根据过滤条件,动态地生成数据,插入到excel表格,当数据量过大,上千或过万条数据HTTP请求一直在连接中,导致报了499错误,客户端会主动断开连接。

       解决方案:使用后台作业比如HangFire或使用长轮询机制SignalR,它们都已经集成到了ABP框架中。

配置SignalR

       首先,引入ABP中的SignalR包,如下图所示。

      

       然后,在Module层进行如下配置,添加 AbpAspNetCoreSignalRModule 到你的模块的依赖列表,并配置SignalR的集线器Hub类型(继承AbpHub 基类),如下图所示:

 

集线器Hub

可以从 AbpHub 或 AbpHub<T> 继承标准的 Hub 和 Hub<T> 类,它们具有实用的基本属性,如 CurrentUser,示例代码如下图所示。

      

       检查上述配置是否成功,打开项目之后,跳转到myhub(自己定义的路径)来检测,如果页面上出现Connection ID required就证明后端配置完全正确。

      

       说明:Hub基于RPC(远程服务调用),接受从客户端发过来的消息,也同时负责把服务端的消息发送给客户端。客户端可以调用Hub里面的方法,服务端可以通过Hub调用客户端里面的方法。

      

       还要说明Caller与其他客户端通信对象的区别,比如Clients.All是给所有客户端,Clients.Group是给某个组,而Caller仅针对当前调用者,确保用户理解其针对性。

总结时,要明确Caller的核心作用是实现与发起请求的客户端进行单独通信,保证消息或状态只传递给该客户端,适用于需要向请求方返回专属信息的场景,比如验证结果、个人状态更新等。

 

集成实际ABP业务

明确一点:SignalR 的 OnConnectedAsync 是生命周期方法,无法直接添加自定义参数(连接建立时自动触发,由 SignalR 框架调用),但可通过「连接时携带参数 + 后端解析」的间接方式传递参数,再在该方法中调用 ABP vNext 业务服务。

说明:依赖注入,Hub 继承 AbpHub 后,可直接通过构造函数注入 ABP 业务服务(如 IUserAppService),无需手动获取 IServiceProvider。

      前端传参例子

      

      image 

       如果调用的业务方法需要传参数,可通过以下两种方式:

 

  1. 推荐「查询字符串」:简单直观,前端拼接方便,后端解析无需额外配置。
  2. 备选「请求头」:适合敏感参数(如自定义令牌),前端需设置 withUrl 的 headers 参数,后端通过 Context.GetHttpContext()?.Request.Headers["X-Biz-Param"] 解析。

 

     在ABPVNext中的HttpApiHostModule可进行接token认证参数配置,SignalR的接参不支持Header,可以在ABP的配置模块中进行如下配置,进行兼容操作,代码如下:

   context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
       .AddJwtBearer(options =>
       {
           options.Authority = configuration["AuthServer:Authority"];
           options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);
           options.Audience = "BusinessService";

           // 添加自定义Token获取逻辑
           options.Events = new JwtBearerEvents
           {
               OnMessageReceived = context =>
               {
                   // 从URL参数获取token(优先处理SignalR场景)
                   if (context.Request.Query.TryGetValue("access_token", out var queryToken))
                   {
                       var token = queryToken.ToString();
                       // 清理可能的双引号(如前端误加的"")
                       token = token.Trim('"');
                       // 清理可能的Bearer前缀(如前端误加的"Bearer ")
                       if (token.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
                       {
                           token = token.Substring("Bearer ".Length).Trim();
                       }
                       context.Token = token; // 赋值清理后的token
                   }
                   return Task.CompletedTask;
               }
           };
       });

         其他配置如下:

    #region SignalR配置
    Configure<AbpSignalROptions>(options =>
    {
        options.Hubs.AddOrUpdate(
            typeof(MessageHub),
            config =>
            {
                config.RoutePattern = "/messageHub";
                config.ConfigureActions.Add(hubOptions =>
                {
                    //Additional options
                    hubOptions.LongPolling.PollTimeout =
                        TimeSpan.FromSeconds(30);
                });
            }
        );
    });
    context.Services.AddSignalR(options =>
    {
        options.KeepAliveInterval = TimeSpan.FromSeconds(5);//心跳间隔
        options.EnableDetailedErrors = true;//全局启用详细错误
    });
    #endregion

     创建Hub类时,要设置一下其生命周期,如下图所示(设置成了单例模式),建议注册上ABP的生命周期使用Hub长寿一点,因为Hub对象本身的生命周期太短了。

    

     image 

      Hub集线类还可以注册到业务服务或控制器中,可以对外进行暴露接口,示例代码如下(依赖注入方式一样使用):

image

 

创建SignalR客户端

       示例代码如下(首先下载:npm install @aspnet/signalr):

      

      

       上述代码中的accessTokenFactory回调用于获取access_token的,会在每次请求时调用以保证获取最新的access_token。

       注意:WebSockets不支持自定义Header,所以不能使用Authorization,需要使用access_token参数传递令牌。

       另外,核心原则:URL 参数access_token的值必须是纯 JWT 字符串(无引号、无Bearer 前缀),如下图所示。

     

 

回落机制:

为了兼容不同浏览器(客户端)和服务端,signalR采用了回落机制,使得它可以根据情况协商使用不同的底层传输方式。假如浏览器不支持web socket,就自动降级使用sse,再不行就long polling。当然,也可以禁用这种机制,指定其中一种。

三种通信方式:

l  long polling(长轮询)

长轮询是客户端发起请求到服务端,服务器有数据就会直接返回。如果没有数据就保持连接并且等待,一直到有新的数据返回。如果请求保持到一段时间仍然没有返回,这时候就会超时,然后客户端再次发起请求。

这种方式优点就是简单,缺点就是资源消耗太多,基本是不考虑的。

l  server sent events(sse)

如果使用了sse,服务器就拥有了向客户端推送的能力,这些信息和流信息差不多,期间会保持连接。

这种方式优点还是简单,也支持自动重连,综合来讲比long polling好用。缺点也很明显,不支持旧的浏览器不说,还只能发送本文信息,而且浏览器对sse还有连接数量的限制(6个)。

l  web socket

web socket允许客户端和服务端同时向对方发送消息(也就是双工通信),而且不限制信息类型。虽然浏览器同样有连接数量限制(可能是50个),但比sse强得多。理论上最优先使用。

       简单测试的实际运行效果,如下图所示。

 

 另外,要会用前端引入的signalr.js中对signalr的操作,SignalR 的connection.on("onlineNum", ...)会监听服务端推送的onlineNum消息,回调参数data是服务端返回的 JSON 对象(已自动解析),注意:在调用 On 方法时,方法名称要与服务器上指定的一致,否则服务器回调无效!

image

   还可以在前端直接调用在hub集线器类中定义的方法,

 

image

前端使用invoke来直接调用:

//测试直接调用hub中的方法
document.getElementById("sendButton").addEventListener("click", function (event) {
    // 无需传递param,因为后端方法无参数
    var target = document.getElementById("requestInput").value;
    var param = document.getElementById("paramInput").value;
    if(param != ""){
        connection.invoke(target, param).catch(function (err) {
            return console.error(err.toString());
        });
    }
    else{
    connection.invoke(target)
        .then(count => {  // 接收后端返回的在线人数
            console.log("当前在线人数:", count);
            // 可在这里更新UI,例如显示count
        })
        .catch(function (err) {
            return console.error(err.toString());
        });
    }

    event.preventDefault();
});

 

posted @ 2025-11-14 08:53  梦想代码-0431  阅读(3)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3