2018 年 7 月

第 33 卷,第 7 期

领先技术 - 联机用户、流式处理和其他 SignalR 利器

通过Dino Esposito |2018 年 7 月

Dino Esposito如果您曾经使用任何版本的 SignalR,适用于经典 ASP.NET 平台,你应非常熟悉的中心概念。在 SignalR 集线器是使兼容客户端和服务器应用程序可以排列双向远程过程调用,从服务器到客户端和服务器返回到连接的客户端的组件。

具体而言,根据软件中心是一个类,继承自系统提供的基本类并公开客户端调用的终结点。从概念上讲,它具有一些共同点 ASP.NET 控制器。具体而言,它是外观的接收客户端调用和响应,并围绕相对较少的相关函数。在 ASP.NET Core SignalR 集线器和控制器之间的相似性是,在某种程度上,更紧密。这是我第三篇文章有关 ASP.NET Core SignalR 领先技术列,既不以前的两篇文章中我使用了非空 hub 类。有关 SignalR 集线器和 ASP.NET Core 中如何实现这些更解释是本文的目的之一。但是,我还将介绍 SignalR,尤其是流式处理的数据和联机的用户数的其他有趣的方面。

在 ASP.NET Core SignalR 集线器

中心是通过该连接的客户端和服务器交换消息的消息管道中的入口点。中心公开其方法为 Url,并以大致相同的方式规范控制器会处理通过模型绑定任何接收的参数。在某种程度上,中心是运行在两个内置的协议的专用的控制器。默认协议包含一个 JSON 对象,但另一个二进制协议可以使用基于 MessagePack。请注意,为了支持 MessagePack,浏览器必须支持 XHR 级别 2。由于在 2012年引入了 2 级,这可能不会太大的问题,但如果你的应用程序需要支持非常旧的浏览器可能值得一提。可以在此处进行快速浏览器检查: caniuse.com/#feat=xhr2

如果有任何连接的客户端请求 SignalR 终结点,在中心直接由 SignalR 运行时引擎调用。会话都通过所选的协议,主要是可能的 Websocket。为了调用服务器,客户端需要一个连接对象。Web 客户端将按以下方式获得:

var clockConnection = new signalR.HubConnection("/clockDemo");
clockConnection.start().then(() => {
     clockConnection.invoke("now");
  });

客户端调用通过连接对象上的"调用"方法的服务器终结点。请注意,确切语法可能会有所不同,具体取决于为客户端使用的实际技术。在服务器回复通过中心基类上定义的方法和会话都通过选择,最常 Websocket,像这样的传输协议:

public class ClockHub : Hub
{
  public Task Now()
   {
    var now = DateTime.UtcNow.ToShortTimeString();
    return Clients.All.SendAsync("now", now);
  }
}

请注意,您将无法监视使用 Fiddler 之类的 HTTP 工具的各种调用。您需要类似于 Chrome 开发人员工具。在我为我之前的专栏编写 SignalR 的所有示例,但是,我始终使用空的中心类。

Hub 类是用于接收客户端调用和客户端/服务器通信,才能开始由于专用管道的最快方法正式的 SignalR 外观。通过中心在调用时,SignalR 运行时可以跟踪并公开通过中心基类的属性的所有可用信息。在这种方式,SignalR 连接 ID 和调用,包括任何声明的身份验证的用户的整个上下文都可用。

此外,通过中心,开发人员能够处理连接和断开事件。设置,或时间,新的连接时返回调用集线器方法。如果仅作为一种方法来监视远程运行时间较长操作使用 SignalR,然后还可以触发该任务通过简单的控制器并注入通知中的集线器上下文。或者,作为替代方法,即可调用该中心并触发从中心内的任务。它是你选择。SignalR 是一个端到端框架,客户端和服务器之间的桥梁。编码到中心的逻辑是可接受的前提是工作不会太深入到代码的层。否则,请通过在控制器转 — 是否 MVC 或 Web API,并注入 hub 上下文。

使用集线器或控制器之间的唯一区别是 SignalR 在一个请求时通过控制器无法跟踪的连接 ID。如果与服务器任务相关联的连接 ID,然后它必须通过 URL 以某种方式传递。可以通过控制器通过 HTTP 请求上下文检索窗体 SignalR 调用方上下文的所有其他信息。

对联机用户进行计数

某些 Web 应用程序很有用,或只是具有吸引力的用户,显示连接数是当前处于活动状态。问题不太跟踪用户在连接时,有许多终结点,通过它可以检测 — 但而不是用户断开连接时从应用程序。

您可以审核登录页或后身份验证步骤。在某些基本控制器类中或在任何用户可以访问的页面中,可以将检查。一般情况下,您始终可以找到一种方法来检测在用户连接到应用程序时。问题是如何用户可以退出该应用程序,通过注销 (可轻松地跟踪) 或离开页面或关闭浏览器窗口。没有可靠的方法来检测当用户关闭浏览器窗口。是的浏览器通常激发 beforeunload 事件时浏览器已关闭,但只要你单击其中的链接,也会激发此相同的事件,即使该链接是在同一应用程序。因此它不是一个完美的解决方案。

更可靠的方式进行计数的用户是要跟踪的 ASP.NET Core SignalR 连接。若要执行此操作,需要设置通过它的连接完全正常运行的中心。当用户离开时浏览器中或只是应用程序时,连接为已发布且正在侦听客户端收到通知。与 ASP.NET Core SignalR 是自动重新连接,不支持过程,因此更容易以下事项。您所要做了中心中定义的全局静态变量和增加或减少时,用户连接或断开连接,其值如中所示图 1。ASP.NET Core 中的 SignalR 运行时可确保在某一时刻,关闭每个连接和任何新的连接有效地引用新的连接。简单地说,您得到的数字是高度可靠的。

图 1 计数连接

public class SampleHub : Hub
{
  private static int Count = 0;
  public override Task OnConnectedAsync()
  {
    Count++;
    base.OnConnectedAsync();
    Clients.All.SendAsync("updateCount", Count);
    return Task.CompletedTask;
  }
  public override Task OnDisconnectedAsync(Exception exception)
  {
    Count--;
    base.OnDisconnectedAsync(exception);
    Clients.All.SendAsync("updateCount", Count);
    return Task.CompletedTask;
  }
}

没有对用户使用 SignalR 进行计数的一个缺点:它仅适用于用户访问建立的连接到中心的页计数,会发生。为了安全起见,您需要在用户可以访问几乎任何页面中有 SignalR 客户端。这是特别是当您考虑正常的在线用户数目是全局可见值很可能你的视图所基于的所有布局中。

请注意,中心代码示例中,类会调用重新连接的客户端每次创建或关闭连接。另请注意,在这种方式仅有的用户总数,但不是列表的连接 Id 或的情况下进行身份验证的用户,用户名称的列表。若要实现此目的,您更好地使用字典而不是一个全局计数器并向其添加具有连接 Id 或声明,如用户名称的条目。

要引用中的代码,请考虑的另一点图 1是一个静态变量以计数用户使用。静态变量是每个服务器,这意味着,当向外扩展您需要考虑如何在全局访问的位置,例如数据库或分布式的缓存中存储的共享的状态。

将信息推送回

从中心或中心上下文中连接到后端通过控制器方法,如果您有许多不同方法来调用重新连接的客户端。所有方法都是由忽略名称,而不是一个集合,但 IClientProxy 类的实例的客户端对象公开的成员。中的表达式图 2指示从中调用 SendAsync 方法的对象。SendAsync 方法采用要回调的客户端方法和传递的参数的名称。

图 2 方法调用的服务器重新连接客户端

Expression 说明
Clients.All 通知是广播到所有已连接客户端,而不考虑所采用的技术 (Web。.NET,.NET Core,Xamarin)。
Clients.Client(connectionId) 以独占方式向指定连接上侦听客户端发送通知。
Clients.User(userId) 通知发送到其身份验证的用户与提供的用户名称相匹配的所有客户端。
Clients.Groups(group) 通知发送到属于指定组的所有客户端。

 

组是共同收集在名称下的相关客户端的集合。更自然的 SignalR 中的组方式是思维的聊天室。以编程方式只需将连接 Id 添加到具有给定名称的组创建一个组。以下是具体方式:

hub.Groups.AddAsync(connectionId, nameOfGroup);

连接的客户端接收通过回调数据。这是最常用的技术。在 ASP.NET Core SignalR,您还可以使用流式处理。

流式处理的数据

可能的 SignalR 最有趣的新方面是流式处理的支持。流式处理是类似于广播,但它遵循略有不同的模型,并且是实质上是略有不同的方式实现相同的广播样式通信。使用 SignalR 流式处理,在中心仍需要轮询和 / 或为了返回流侦听的数据。在经典广播,服务器将新数据可用时告知客户端方法。

在新的流式处理模型中,客户端订阅类型通道的新的 server 对象和服务器 — 中心,实际上 — 它们是捕获产生新的项。目前,没有什么能比真正的流式传输所有连接的客户端向该流字节可用,但在将来支持此模型。请注意,通道类型与 preview2 引入了在早期版本中不支持。在早期版本中,您必须使用可观察量相反,这需要对 System.Reactive.Linq NuGet 包的引用。可观察量和通道与基元 IObservable 中缺少用于处理网络反压 (即告诉客户端不处理消息的速度不够快时速度下降的服务器) 的新类型之间切换。

图 3展示了中心的代码。

图 3 Hub 类,流式传输

public class ClockHub : Hub
{
  private static bool _clockRunning = false;
  public void Start()
  {
    _clockRunning = true;
    Clients.All.SendAsync("clockStarted");
  }
  public void Stop()
  {
    _clockRunning = false;
    Clients.All.SendAsync("clockStopped");
  }
  public ChannelReader<string> Tick()
  {    var channel = Channel.CreateUnbounded<string>();
    Task.Run(async() => {
      while(_clockRunning)
      {
        var time = DateTime.UtcNow.ToString("HH:mm:ss");
        await channel.Writer.WriteAsync(time);
        await Task.Delay(1000);
      }
      channel.Writer.TryComplete();    });
  }
}

中心提供三种方法来启动、 停止和运行时钟。全局变量控制正在运行状态的流式处理,以及开始和停止方法设置的控制变量,并通知后的客户端方法中的 SignalR hub 像往常一样。最为棘手的部分是刻度线方法。方法名称与客户端将订阅的流的名称。该方法返回给定类型的通道对象。在示例中,类型是一个简单的字符串,但它可以是任何内容更复杂。

每个调用,从客户端到服务器或服务器到客户端,包含一个参与方发送调用消息,并最终响应一条完成消息携带结果或错误的其他参与方。在 SignalR 流式处理方案中,而是另一方响应多条消息,每个数据项,执行之前最终结束与一条完成消息的通信。因此,客户端成为甚至之前收到完成消息处理多个项。

缩放到多个实例

SignalR 将所有连接 Id 都保留在内存中,这意味着,当前应用程序可以扩展到多个实例,广播 (但也流式处理,如后面所述) 受到如每个实例只会跟踪所有连接的客户端的一部分。若要避免这种情况,SignalR 支持基于 Redis 缓存,以确保它们自动实例之间共享的新连接。若要启用 Redis,需要 SignalR.Redis 包和到启动类的 ConfigureServices 方法中的 AddRedis 方法的调用如下所示:

services.AddSignalR()
        .AddRedis("connection string");

选项参数用于指定正在运行的 Redis 实例的连接字符串的用途。

总结

ASP.NET Core SignalR 进行了两个重大更改来自非 Core 版本。一个是缺少的自动重新连接,这会影响连接/断开连接,然后联机用户计数如何以编程方式处理。这意味着,现在每个应用程序必须处理连接/断开连接逻辑,并可能具有确定第一次连接的用户和因出错而重新连接的用户之间的差异。其他更改是对数据进行流式处理支持。数据进行流式处理基于通道,目前仅支持特定的数据项,而不是原始流。

最后,我浏览 SignalR 缺少另一段,我将在以后的专栏中简要介绍: 身份验证的用户和组。


Dino Esposito在他 25 年的职业生涯中撰写了超过 20 本书籍和 1,000 篇文章。Esposito 不仅是舞台剧《事业中断》的作者,还是 BaxEnergy 的数字策略分析师,正忙于编写有助于建设环保世界的软件。可以在 Twitter 上关注他 (@despos)。

衷心感谢以下 Microsoft 专家对本文的审阅:Andrew Stanton-Nurse


在 MSDN 杂志论坛讨论这篇文章