【ArchSummit架构师峰会】探讨数据与人工智能相互驱动的关系>>> 了解详情
写点什么

SignalR Core 尝鲜

A developer's tour of SignalR Core Alpha

  • 2018-03-19
  • 本文字数:5153 字

    阅读完需:约 17 分钟

要点

  • SignalR Core 改用 Microsoft.AspNetCore.Sockets,不再依赖 HTTP。
  • 使用 MessagePack 序列化格式,支持二进制协议。
  • TypeScript 客户端移除了第三方依赖包。
  • 支持 WebSocket 原生客户端,可以使用自己构建的客户端连接到 SignalR 服务器。
  • 伸缩方式更灵活,可以通过自己实现的方式进行横向扩展。

几个月前,SignalR Core 团队发布了一个非官方版本的ASP.NET Core SignalR 。为此,开发人员有机会了解其工作原理以及ASP.NET SignalR 与Signal Core 新架构之间的区别。

SignalR Core 中移除了哪些特性

通过对比两个版本的 SignalR 可以发现,新版本不再支持一些重要的特性。首先是移除了对 jQuery 和其他第三方类库的依赖,因为新版本的 JavaScript 客户端是使用 TypeScript 开发的。其次是自动连接后的消息重放功能,移除该功能主要是出于性能方面的考虑。服务器需要为每一个连接维护一个缓冲区,用于保存消息,以便后续重新发送。当客户端断开连接,可以尝试重新恢复连接,然后将未发送的消息发送给客户端。可以想象,如果有很多客户端断开连接,而且每个客户端都发送大量的消息,对于服务器来说是个很大的负担。另一个被 SignalR 团队移除特性是多 Hub 端点,所以,在新版本里,每个连接只有一个 Hub。

新版本的 SignalR Core 不再支持横向扩展(Scale Out)模型,原因是 MessageBus 被当成了横向扩展的“万灵丹”,但它实际上只支持 Azure Service Bus 、Redis 和 SQL Server。在实际的协作场景当中(客户端到客户端),随着客户端和消息数量的增长,通过以上三种方式进行横向扩展会有瓶颈问题。

不过,我认为,移除横向扩展功能这一决定有点太过激进,因为在某些场景下,MessageBus 仍然十分有用。例如,在将 SignalR 作为一个广播服务器时,它可以控制发送消息的数量。而在 SignalR Core 的 alpha 版本中,开发者可以根据实际情况选择是否进行横向扩展,如业务需求、系统约束或基础设施,这种设计更加“可插拔”。SignalR Core 团队提供了一个使用 Redis 进行横向扩展的示例。其他扩展方式可能会被包含在 SignalR Core 的最终版中。

最后一个被移除的功能是多服务器间的双向复制(backplane),因为这个功能会在服务器场生成太多的流量。ASP.NET SignalR 通过 MessageBus 在服务器间复制每一个消息,因为客户端无法直接连接到服务器场,而现在,SignalR 使用粘性会话来避免在所有服务器间复制消息。这样一来,SignalR Core 就可以知道哪个客户端连接到了哪台服务器上。

SignalR Core 中增加了哪些新特性

现在让我们来看一下 SignalR Core 带来了哪些新的特性。首先是使用了二进制协议来发送和接收消息。在 ASP.NET SignalR 中只能使用 JSON 格式的文本来发送和接收消息,而现在则可以使用二进制协议,该二进制协议基于 MessagePack 序列化格式,比 JSON 更快、体积更小。

主机无关性是另一个非常重要的特性,有了这个特性,就可以移除对 HTTP 的依赖。现在,我们可以在 HTTP 或 TCP 之上使用 SignalR。端点 API 也是非常重要的一个特性,它是基础的构建块,用于支持主机无关性。因为新版本是基于 Microsoft.AspNetCore.Sockets 这一底层的网络抽象层,所以可以直接使用 Socket。这么说来,SignalR Hub 其实也就是一个端点。

多格式也是一个很酷的特性,有了这个特性,我们可以处理任意格式的消息。我们可以使用多种不同的客户端连接到同一个端点,这些客户端可以使用不同的消息格式。也就是说,SignalR Core 已经实现了消息的格式无关性。这个示例在同一个端点上使用了三种格式(JSON、PIPE 和Protobuf)来读写消息,因为使用了自定义的处理器,可以无缝地处理各种格式的消息。正如之前提到的那样,可能是因为使用了Microsoft.AspNetCore.Sockets,从底层来看,消息都只是简单的二进制字节。

新版本还支持WebSocket 原生客户端,所以开发者也可以使用除SignalR Web 客户端之外的其他客户端。之前,开发者必须使用基于JavaScript 的Web 客户端连接到SignalR 服务器上。现在,开发者可以自己开发客户端,充分利用浏览器API 提供的优势。当然,开发者也可以使用最新的TypeScript 客户端,因为TypeScript 提供了很多有用的特性。另外,客户端是通过NPM 包管理器进行发布的,这样依赖管理也就变得更简单了。

最后一点是,横向扩展变得更灵活,提供了更高的可扩展性。SignalR Core 团队简化并改进了横向扩展模型,并提供了一个基于Redis 的横向扩展示例,帮助开发者了解如何进行横向扩展。

去年9 月14 号,SignalR Core 团队发布了第一个alpha 版,10 月9 号发布了第二个alpha 版,也就是SignalR Core 2.0 官方预览版。现在,我们即将探讨这一版本中包含的主要变化。

在得知有新版本后,我在第一时间去拉取代码,并试着去构建最新的代码。不过,正如预期的那样,因为代码还在开发当中,无法立马构建成功。尽管如此,我们还是能够在第一时间看到正在发生的变更,这有助于我们了解为什么要做出这些变更。接下来,我将列出我在构建过程中遇到的问题,并告诉大家我做了哪些事情来修复这些问题。

要在项目中使用SignalR Core,必须引用Microsoft.AspNetCore.SignalR,最新版本是1.0.0-alpha2-final。

HubConnectionBuilder

在之前的版本中,如果要在服务器端连接到一个 Hub,我们会使用 HubConnection 类,比如:

var connection = new HubConnection(new Uri(baseUrl), loggerFactory);而现在,我们需要使用 HubConnectionBuilder 类(实现了 Builder 设计模式)来连接 SignalR Core Hub,这也是第一个导致代码构建失败的变化。这一变化让建立连接变得更具可扩展性,不需要使用满是参数或带有 null 参数的构造函数。我很喜欢这个变化,因为它简化了建立连接的过程。

复制代码
var connection = new HubConnectionBuilder()
                        .WithUrl(baseUrl)
                        .WithConsoleLogger()
                        .Build();

在服务器端处理连接

在之前的版本中,客户端在“On”方法中处理由 SignalR Hub 广播过来的数据,这个时候需要处理一大堆参数:

复制代码
connection.On("UpdateCatalog", new[] { typeof(IEnumerable<Product>) }, data =>
{
    var products = data[0] as List<Product>;
    foreach (var item in products)
    {
        Console.WriteLine($"{item.name}: {item.quantity}");
    }
});

可以看出,这个方法有点累赘,因为不管用不用得到方法里的参数,都必须指定这些参数和它们的类型,即使用不到,也要指定一个空数组。但问题是,处理器的参数是无类型的,所以,即使在数组中指定了类型,仍然需要遍历数组,对它们进行类型转换。

而新版本的 SignalR Core 提供了最新的泛型方法重载机制,通过这种方式指定参数类型后就不需要再进行类型转换。泛型方法对原始方法进行了包装,从而简化了开发者的工作。最终的代码更简单、可读性更好。

复制代码
connection.On<List<Product>>("UpdateCatalog", products =>
{
    // now, “products” parameter is a List<Product> type.
    foreach (var item in products)
    {
        Console.WriteLine($"{item.name}: {item.quantity}");
    }
});

命名约定

我发现 Invoke 方法名发生了变化(这个变化也导致代码构建失败):

await connection.Invoke("RegisterProduct", cts.Token, product, quanity);这是一个异步方法,为了遵循命名约定,方法名被改成了 InvokeAsync,方法参数的顺序也发生了改变,令牌参数被放在了最后:

await connection.InvokeAsync("RegisterProduct", product, quanity, cts.Token);因为遵循了命名约定和标准,开发者在使用 SignalR Core API(包括 SignalR Core 的开发者团队)时就更加直观,因为它带来了代码的统一性。例如,如果开发者在他们的 IDE 中使用了 Intellisense,就可以提前知道这个方法是异步的。

另一个与命名约定有关的变化是 MapEndpoint 方法,这个方法被改成了 MapEndPoint,遵循了 Pascal 的大小写风格

之前:

复制代码
app.UseSockets(routes =>
{
    routes.MapEndpoint<MessagesEndPoint>("/message");
});

现在:

复制代码
app.UseSockets(routes =>
{
    routes.MapEndPoint<MessagesEndPoint>("message");
});

可以看到,现在不使用“/”符号了。MapHub 方法也一样。不过,我们发现这里存在一个问题,这些方法没有使用 PathString API。不过,在下一个版本中会继续使用“/”,与其他的.Net Core API 保持一致。

命名变更

命名方面发生了很多变更,其中一个是与 Connection 类有关的 ConnectionContext。ConnectionContext 包含了与连接相关的信息,如元数据、通道等。

之前:

public override async Task OnConnectedAsync(Connection connection)现在:

public override async Task OnConnectedAsync(ConnectionContext connection)另一个命名方面的变更与 ConnectionContext 中的 Transport 有关。之前,用于管理输入和输出的属性分别叫作 Input 和 Output,而现在它们被改为 In 和 Out。

之前:

复制代码
connection.Transport.Input.WaitToReadAsync()
connection.Transport.Output.WriteAsync()

现在:

复制代码
connection.Transport.In.WaitToReadAsync()
connection.Transport.Out.WriteAsync()

TryRead 和 WriteAsync

TryRead 和 WriteAsync 方法得到了简化。之前,它们接收一个 Message 对象作为参数。

之前:

复制代码
Message message;
if (connection.Transport.Input.TryRead(out message))
{
    ...
}
connection.Transport.Output.WriteAsync(new Message(payload, format, endOfMessage));

现在:

复制代码
// message is byte[]
if (connection.Transport.In.TryRead(out var message))
{
    ...
}
// payload is byte[]
connection.Transport.Out.WriteAsync(payload);

现在他们使用字节数组作为参数,因为底层的 Socket 使用了 Channel<byte[]>。SignalR Core 团队认为,将字节数据移到上层可以让 Socket 层的逻辑更清晰。之前,SignalR Core 团队在字节数据之上使用了一个底层的数据帧协议(不过 WebSocket 已经有数据帧,所以没有在 WebSocket 上使用该协议)。

因此,Microsoft.AspNetCore.Sockets 层得到了“净化”,只允许端点处理二进制数据,而端点就可以使用任何一种协议,比如 TCP 或 HTTP。

底层的数据帧协议是在 Microsoft.AspNetCore.SignalR 层实现的,所以消息类型、数据帧都是在实现了 IHubProtocol 接口的类中处理的,比如 JsonHubProtocol 和 MessagPackHubProtocol。这种设计提供了一种可扩展的方式用于实现其他的 Hub 协议。

其他变更

我们可以直接通过 NPM 管理器来安装 signalr-client,比如,我在 package.json 文件里将它作为客户端依赖:

复制代码
{
  "version": "1.0.0",
  "name": "asp.net",
  "private": true,
  "dependencies": {
    "@aspnet/signalr-client": "^1.0.0-alpha2-final",
    "jQuery.tabulator": "^1.12.0"
  }
}

Visual Studio 会在构建解决方案时自动安装这个包。当然,我们也可以使用.NET Core 内置的新特性,它会自动把 signalr-client 文件拷贝到 wwwroot 目录,这样就不需要再使用 gulp、grunt 或其他任务执行器了。

复制代码
[
    {
        "outputFileName": "wwwroot/lib/signalr/signalr-clientES5-1.0.0-alpha2-final.min.js",
        "inputFiles": [
            "node_modules/@aspnet/signalr-client/dist/browser/signalr-clientES5-1.0.0-alpha2-final.min.js"
        ],
        "minify": {
            "enabled": false
        }
    },
    {
        "outputFileName": "wwwroot/lib/signalr/signalr-client-1.0.0-alpha2-final.min.js",
        "inputFiles": [
            "node_modules/@aspnet/signalr-client/dist/browser/signalr-client-1.0.0-alpha2-final.min.js"
        ],
        "minify": {
            "enabled": false
        }
    }
]

默认情况下,.NET Core 启用 minify 选项,而我引用的文件已经被 minify 过,当它尝试再次 minify 这些文件时就会报错,于是我就把 minify 选项禁用了。

结论

以上就是我在升级到最新版 SignalR Core 时发现的一些变化。我把它们分享出来,让其他开发者也知道这些变更以及为什么要做出这些变更。我希望这些信息对大家有用,也鼓励大家在自己的项目中测试最新的 SignalR Core。

我花了几个小时解决在构建新版本代码时遇到的问题,而查看代码和理解这些变更又额外花了我几个小时时间,不过这些都是值得的。

关于作者

Geovanny Alzate Sandoval 是一名来自哥伦比亚麦德林的系统工程师,他喜欢所有与软件开发、新技术、设计模式和软件架构相关的事物。他已经在该领域工作了十多年,做过开发者、技术负责人和软件架构师。他乐于向社区做贡献,喜欢在博客上写与微软新技术有关的东西。另外,他还是麦德林.NET 开发者社区 MDE.NET 的联合组织者。

查看英文原文: First Hand Account of SignalR Core

2018-03-19 17:572147

评论

发布
暂无评论
发现更多内容

分布式锁主动续期的入门级实现-自省 | 简约而不简单

小小怪下士

Java 程序员 分布式 分布式锁

架构实战营模块二作业

张Dave

极客时间运维进阶训练营第七周作业

老曹

2022-12-11:行程和用户。以下为输出结果,请问sql语句如何写? +------------+-------------------+ | Day | Cancellation

福大大架构师每日一题

数据库 福大大

设计模式之美——单一职责(Single Responsibility Principle)

GalaxyCreater

设计模式

字节大神熬夜整理MyBatis+Redis+Kafka+spring源码与实战技术齐飞

钟奕礼

Java java编程 程序员、

小令案例 | 互联网消费分期产品引入令牌云服务,大幅提升进件转化

令牌云数字身份

身份认证 分布式数字身份 成功案例

Verilog 的连续赋值

二哈侠

Verilog Verilog语法 连续赋值

云与开源,共植数字世界的根

Apache Flink

大数据 flink 实时计算

React源码分析8-状态更新的优先级机制

flyzz177

React

React源码分析7-state计算流程和优先级

flyzz177

React

工赋开发者社区 | 架构瓶颈原则:用注意力probe估计神经网络组件提供多少句法信息

工赋开发者社区

架构训练营作业-模块2

张建闯

架构实战营

灵魂拷问,你真的了解DNS吗?

蔡农曰

互联网 前端 后端 计算机网络

浅谈如何在小红书和知乎两大平台做好引流推广

石头IT视角

学习编程必须知道的三个网站

邱比特讲编程

GitHub 编程 Google Stack Overflow 编程工具

开始用ChatGPT写作

SkyFire

ChatGPT

Compose把Text组件玩出新高度

Halifax

android 前端 kotlin Compose android jetpack

工赋开发者社区 | 65页数字化工厂规划与建设详细方案 !

工赋开发者社区

通过假设地图进行产品待办列表排序

Bruce Talk

Agile User Story Product Owner 敏捷、

HarmonyOS玩转ArkUI动效 - 水母动画

Halifax

前端 动画 HarmonyOS OpenHarmony arkui

网络编程与通信原理

Java 架构

运维进阶训练营 -W07H

赤色闪电

运维

关于ChatGPT的一切;CUDA入门之矩阵乘;PyTorch 2.0发布|AI系统前沿动态

OneFlow

人工智能 深度学习 AI

从德鲁克管理实践看服务化架构

agnostic

微服务

非一线工程管理者的一对一沟通

俞凡

领导力 管理

微信朋友圈高性能复杂度

闲人Eric

架构实战营

啃透这500页高并发笔记薪资涨了20K,并连收天猫,京东等5个Offer

钟奕礼

Java 程序员 java面试 java 编程

流处理基础概念-窗口与时间

穿过生命散发芬芳

流处理 12月月更

架构实战 - 模块 2 作业

mm

微信朋友圈 #架构实战营

记一次Mysql大数据分页优化问题

石臻臻的杂货铺

MySQL 数据库

SignalR Core尝鲜_.NET_Geovanny Alzate Sandoval_InfoQ精选文章