写点什么

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:572723

评论

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

5 个 JavaScript “罕见”原生的 API

掘金安东尼

JavaScript 前端 8月月更

Kyligence 连续三年入选 Gartner 增强数据分析推荐厂商

Kyligence

数据分析 Gartner

音视频开发进阶|音频编解码的必要性解读与格式选取

ZEGO即构

vue高频面试题合集(四)附答案

helloworld1024fd

Vue

STM32入门开发 NEC红外线协议解码(超低成本无线传输方案)

DS小龙哥

8月月更

zzcase&接口自动化-质&效的探索

转转技术团队

测试工具 用例设计平台开发

上海前端培训学习好就业难吗

小谷哥

MYSQL最朴素的监控方式

京东科技开发者

MySQL 数据库 数据 监控数据

快的不止一点点!阿里强推的“Redis速成手册”也太香了吧

程序知音

Java 数据库 redis 程序员 后端技术

Python也许很友好,但它也容易弄得一团槽

梦想橡皮擦

Python 爬虫 8月月更

软银从阿里套现340亿美元,阿里、腾讯为何纷纷被大股东撤仓?

雨果

阿里云 软银 DaaS数据即服务

版本管理工具git的使用总结

TimeFriends

8月月更

JavaScript Promise 的使用技巧

汪子熙

JavaScript Promise 异步编程 await 8月月更

北京JAVA开发3年,拿到美团35K的offer面试心得(全干货)

程序知音

Java 程序员 java面试 后端技术 Java面试八股文

Synchronized锁升级原理与过程深入剖析:无锁>偏向锁>轻量级锁>重量级锁

Java全栈架构师

Java 程序员 面试 程序人生 多线程

视频1对1源代码——简单的搭建方式也有技术要求

开源直播系统源码

软件开发 直播系统源码 语音直播系统源码 语音直播

如何有效进行回顾会议(中)?

敏捷开发

Scrum 敏捷开发 回顾会 Scrum团队

浅谈-大数据工程师面临的困境和要学习的技术

Geek_c8a6a0

云原生(十四) | Kubernetes篇之深入万物基础-容器

Lansonli

云原生 8月月更

面试官怒了:多级缓存不了解怎么行,那可是数量级的提升?

知识浅谈

缓存 8月月更

万丈高楼平地起--java基础语法

Geek_ba5ac7

Java core

Android进阶(十一)Android系统架构讲解

No Silver Bullet

android 系统架构 8月月更

C++运算符重载(四)之赋值运算符重载

CtrlX

c++ C# 后端 函数重载 8月月更

Java编程学习好就业薪资高吗

小谷哥

vue高频面试题合集(三)附答案

helloworld1024fd

极狐GitLab冷知识:使用 Gitlab Webhook 触发 Pipeline

郭旭东

极狐GitLab JIHULAB 101

最佳实践|Apache Doris 在小米数据场景的应用实践与优化

SelectDB

数据库 数据分析 小米 Doris OLAP 场景实践

深圳大数据编程培训机构哪家比较靠谱

小谷哥

karmada调度策略想要实现,这三个组件必须了解 | K8S Internals系列第4期

BoCloud博云

容器 云原生 k8s

用好JAVA中的函数式接口,轻松从通用代码中剥离掉业务定制逻辑

程序知音

Java 编程 程序员 后端

前端线下培训的就业前景怎么样?

小谷哥

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