写点什么

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

评论

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

冈萨雷斯《数字图像处理》学习总结及感悟:第一章 绪论 百闻不如一见

老猿Python

图形图像处理 数字图像处理 冈萨雷斯

Rust从0到1-错误处理-panic!

rust 错误处理 Error 不可恢复错误

丰田汽车选用Mobileye和采埃孚的安全技术

E科讯

【LeetCode】找出第 K 大的异或坐标值Java题解

Albert

算法 LeetCode 5月日更

私有云解决方案

anyRTC开发者

音视频 WebRTC RTC sdk

Golang List, Ring and Map

escray

学习 极客时间 Go 语言 5月日更

android端音频采集与播放

floer rivor

android 音视频

集成学习案例一 (幸福感预测)

容光

数据处理

让人工智能成为保险行业科技基因的一部分!

百度大脑

人工智能 保险

从酷睿双核到Tiger Lake-H,英特尔如何帮游戏笔记本完成蜕变

E科讯

多线程 VS 多进程(一)

若尘

多线程 多进程 Python编程 5月日更

NumPy之:ndarray多维数组操作

程序那些事

Python Numpy 程序那些事

看MindSpore加持下,如何「炼出」首个千亿参数中文预训练语言模型?

华为云开发者联盟

框架 mindspore 盘古 NLP 大模型 中文预训练模型

深入浅出分布式存储性能优化方案

焱融科技

云计算 分布式 高性能 云存储 超融合

论Http、Socket、WebSocket、WebService(SOAP)之间的区别

Damon

5月日更

HTTP/3 初体验

运维研习社

nginx 运维 HTTP3.0 5月日更

成功产品三要素

lenka

5月日更

千万级学生管理系统的<考试试卷>存储方案

唐江

架构实战营

进程内缓存助你提高并发能力!

万俊峰Kevin

缓存 微服务 本地缓存 Go 语言

🚄【Redis 干货领域】从底层彻底吃透 AOF 重写 (源码篇)

码界西柚

redis aof Redis 协议 Redis 核心技术与实战 5月日更

详解JQuery框架的五大选择器

华为云开发者联盟

jquery 选择器 层级选择器 属性选择器 过滤选择器

飞桨前沿升级、顶级开源项目、产教融合育人,WAVE SUMMIT论坛内容先睹为快!

百度大脑

深度学习 飞桨

前端实操案例丨如何实现JS向Vue传值

华为云开发者联盟

Vue 大前端 js Promise Vuex state

智能视频云3.0全景图来了!深度融合视频应用共创行业新生态

百度大脑

云智一体 智能视频 云智技术

DEMO WORLD分论坛聊些啥?高端制造、未来出行、皮肤科技、未来产业……

创业邦

创新

“读万卷书,行万里路”,让你收获一个不平凡的人生

小天同学

读书 成长 旅行 5月日更

人人都在谈的图数据库到底是个啥?

华为云开发者联盟

大数据 数据结构 数据 图数据库 华为云图引擎图数据库GES

kafka基本概念

杨四正

大数据 kafka 架构设计 消息队列 消息队列架构

分布式事务与分布式系统

邱学喆

分布式事务 raft CAP PAXOS 副本一致性

Flutter开发:Failed to retrieve the Dart SDK…的解决方法

三掌柜

5月日更

如何成为云原生技术高阶玩家?华为云最近做了这件事

华为云开发者联盟

容器 DevOps 微服务 云原生 华为云

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