在 ASP.NET Core SignalR 中使用 MessagePack 中心协议

本文假定读者熟悉 ASP.NET Core SignalR 入门中涵盖的主题。

什么是 MessagePack?

MessagePack 是一种快速而紧凑的二进制序列化格式。 当担忧性能和带宽问题时,这很有用,因为它创建的消息比 JSON 创建的小。 查看网络跟踪和日志时,二进制消息不可读取,除非这些字节是通过 MessagePack 分析器传递的。 SignalR 为 MessagePack 格式提供内置支持,并提供 API 供客户端和服务器使用。

在服务器上配置 MessagePack

若要在服务器上启用 MessagePack 中心协议,请在应用中安装 Microsoft.AspNetCore.SignalR.Protocols.MessagePack 包。 在 Startup.ConfigureServices 方法中,将 AddMessagePackProtocol 添加到 AddSignalR 调用以在服务器上启用 MessagePack 支持。

services.AddSignalR()
    .AddMessagePackProtocol();

注意

JSON 默认启用。 添加 MessagePack 可同时支持 JSON 和 MessagePack 客户端。

若要自定义 MessagePack 如何设置数据格式,AddMessagePackProtocol 可接受委托来配置选项。 在该委托中,SerializerOptions 属性用于配置 MessagePack 序列化选项。 有关解析程序工作方式的详细信息,请访问位于 MessagePack-CSharp 的 MessagePack 库。 可以对要序列化的对象使用属性,以定义应如何处理这些对象。

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.SerializerOptions = MessagePackSerializerOptions.Standard
            .WithResolver(new CustomResolver())
            .WithSecurity(MessagePackSecurity.UntrustedData);
    });

警告

强烈建议查看 CVE-2020-5234 并应用推荐的修补程序。 例如,替换 SerializerOptions 时调用 .WithSecurity(MessagePackSecurity.UntrustedData)

在客户端上配置 MessagePack

注意

JSON 在支持的客户端启用上默认启用。 客户端只能支持单个协议。 添加 MessagePack 支持将会替换之前配置的任何协议。

.NET 客户端

若要在 .NET 客户端中启用 MessagePack,请安装 Microsoft.AspNetCore.SignalR.Protocols.MessagePack 包,并在 HubConnectionBuilder 上调用 AddMessagePackProtocol

using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;

var hubConnection = new HubConnectionBuilder()
                        .WithUrl("/chathub")
                        .AddMessagePackProtocol()
                        .Build();

注意

AddMessagePackProtocol 调用接受委托来配置选项,就像服务器一样。

JavaScript 客户端

@microsoft/signalr-protocol-msgpack npm 包提供对 JavaScript 客户端的 MessagePack 支持。 在命令行界面中执行以下命令来安装包:

npm install @microsoft/signalr-protocol-msgpack

安装 npm 包后,可以通过 JavaScript 模块加载程序直接使用该模块,或引用以下文件将该模块导入浏览器:

node_modules\@microsoft\signalr-protocol-msgpack\dist\browser\signalr-protocol-msgpack.js

必须按如下所示的顺序引用以下必需的 JavaScript 文件:

<script src="~/lib/signalr/signalr.js"></script>
<script src="~/lib/signalr/signalr-protocol-msgpack.js"></script>

如果将 .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol()) 添加到 HubConnectionBuilder,则会将客户端配置为在连接到服务器时使用 MessagePack 协议。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())
    .build();

目前,JavaScript 客户端上没有 MessagePack 协议的配置选项。

Java 客户端

若要使用 Java 启用 MessagePack,请安装 com.microsoft.signalr.messagepack 包。 使用 Gradle 时,将以下行添加到 build.gradle 文件的 dependencies 部分:

implementation 'com.microsoft.signalr.messagepack:signalr-messagepack:5.0.0'

使用 Maven 时,将以下行添加到 pom.xml 文件的 <dependencies> 元素中:

<dependency>
    <groupId>com.microsoft.signalr.messagepack</groupId>
    <artifactId>signalr</artifactId>
    <version>5.0.0</version>
</dependency>

HubConnectionBuilder 上调用 withHubProtocol(new MessagePackHubProtocol())

HubConnection messagePackConnection = HubConnectionBuilder.create("YOUR HUB URL HERE")
    .withHubProtocol(new MessagePackHubProtocol())
    .build();

MessagePack 注意事项

使用 MessagePack 中心协议时,需要注意几个问题。

MessagePack 区分大小写

MessagePack 协议区分大小写。 例如,请考虑以下 C# 类:

public class ChatMessage
{
    public string Sender { get; }
    public string Message { get; }
}

从 JavaScript 客户端发送时,必须使用 PascalCased 属性名称,因为大小写必须与 C# 类完全匹配。 例如:

connection.invoke("SomeMethod", { Sender: "Sally", Message: "Hello!" });

使用 camelCased 名称无法正确绑定到 C# 类。 可使用 Key 属性为 MessagePack 属性指定不同的名称来解决此问题。 有关详细信息,请参阅 MessagePack-CSharp 文档

序列化/反序列化时不保留 DateTime.Kind

MessagePack 协议不提供对 DateTimeKind 值进行编码的方法。 因此,在对日期进行反序列化时,如果 DateTime.KindDateTimeKind.Local,MessagePack 中心协议会将其转换为 UTC 格式,否则它将不会触及时间并按原样传递。 如果你使用的是 DateTime 值,建议在发送这些值之前将它们转换为 UTC。 当你接收到它们时,将它们从 UTC 转换为本地时间。

“预先”编译环境中的 MessagePack 支持

.NET 客户端和服务器使用的 MessagePack-CSharp 库使用代码生成来优化序列化。 因此,默认情况下,它在使用“预先”编译的环境(如 Xamarin iOS 或 Unity)中不受支持。 可通过“预生成”序列化程序/反序列化程序代码,在这些环境中使用 MessagePack。 有关详细信息,请参阅 MessagePack-CSharp 文档。 预生成序列化程序后,可使用传递给 AddMessagePackProtocol 的配置委托注册它们:

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        StaticCompositeResolver.Instance.Register(
            MessagePack.Resolvers.GeneratedResolver.Instance,
            MessagePack.Resolvers.StandardResolver.Instance
        );
        options.SerializerOptions = MessagePackSerializerOptions.Standard
            .WithResolver(StaticCompositeResolver.Instance)
            .WithSecurity(MessagePackSecurity.UntrustedData);
    });

MessagePack 中的类型检查更加严格

JSON 中心协议将在反序列化过程中执行类型转换。 例如,如果传入对象的属性值为数字 ({ foo: 42 }),但 .NET 类的属性类型为 string,则该值将被转换。 但是,MessagePack 不会执行此转换并将引发异常,该异常可在服务器端日志(和控制台)中查看:

InvalidDataException: Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.

有关此限制的详细信息,请参阅 GitHub 问题 aspnet/SignalR#2937

Java 中的字符和字符串

在 Java 客户端中,char 对象将被序列化为单字符 String 对象。 这与 C# 和 JavaScript 客户端相反,后者将它们序列化为 short 对象。 MessagePack 规范本身不会定义 char 对象的行为,因此由库作者决定如何序列化它们。 客户端之间的行为差异是由我们用于实现的库造成的。

其他资源

本文假定读者熟悉 ASP.NET Core SignalR 入门中涵盖的主题。

什么是 MessagePack?

MessagePack 是一种快速而紧凑的二进制序列化格式。 当担忧性能和带宽问题时,这很有用,因为它创建的消息比 JSON 创建的小。 查看网络跟踪和日志时,二进制消息不可读取,除非这些字节是通过 MessagePack 分析器传递的。 SignalR 为 MessagePack 格式提供内置支持,并提供 API 供客户端和服务器使用。

在服务器上配置 MessagePack

若要在服务器上启用 MessagePack 中心协议,请在应用中安装 Microsoft.AspNetCore.SignalR.Protocols.MessagePack 包。 在 Startup.ConfigureServices 方法中,将 AddMessagePackProtocol 添加到 AddSignalR 调用以在服务器上启用 MessagePack 支持。

注意

JSON 默认启用。 添加 MessagePack 可同时支持 JSON 和 MessagePack 客户端。

services.AddSignalR()
    .AddMessagePackProtocol();

若要自定义 MessagePack 如何设置数据格式,AddMessagePackProtocol 可接受委托来配置选项。 在该委托中,可使用 SerializerOptions 属性配置 MessagePack 序列化选项。 有关解析程序工作方式的详细信息,请访问位于 MessagePack-CSharp 的 MessagePack 库。 可以对要序列化的对象使用属性,以定义应如何处理这些对象。

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.SerializerOptions = MessagePackSerializerOptions.Standard
            .WithResolver(new CustomResolver())
            .WithSecurity(MessagePackSecurity.UntrustedData);
    });

警告

强烈建议查看 CVE-2020-5234 并应用推荐的修补程序。 例如,替换 SerializerOptions 时调用 .WithSecurity(MessagePackSecurity.UntrustedData)

在客户端上配置 MessagePack

注意

JSON 在支持的客户端启用上默认启用。 客户端只能支持单个协议。 添加 MessagePack 支持将会替换之前配置的任何协议。

.NET 客户端

若要在 .NET 客户端中启用 MessagePack,请安装 Microsoft.AspNetCore.SignalR.Protocols.MessagePack 包,并在 HubConnectionBuilder 上调用 AddMessagePackProtocol

using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;

var hubConnection = new HubConnectionBuilder()
                        .WithUrl("/chathub")
                        .AddMessagePackProtocol()
                        .Build();

注意

AddMessagePackProtocol 调用接受委托来配置选项,就像服务器一样。

JavaScript 客户端

@microsoft/signalr-protocol-msgpack npm 包提供对 JavaScript 客户端的 MessagePack 支持。 在命令行界面中执行以下命令来安装包:

npm install @microsoft/signalr-protocol-msgpack

安装 npm 包后,可以通过 JavaScript 模块加载程序直接使用该模块,或引用以下文件将该模块导入浏览器:

node_modules\@microsoft\signalr-protocol-msgpack\dist\browser\signalr-protocol-msgpack.js

在浏览器中,还必须引用 msgpack5 库。 使用 <script> 标记创建引用。 可在 node_modules\msgpack5\dist\msgpack5.js 中找到该库。

注意

使用 <script> 元素时,顺序很重要。 如果在 msgpack5.js 之前引用了 signalr-protocol-msgpack.js,则在尝试使用 MessagePack 进行连接时将出现错误。 在 signalr-protocol-msgpack.js 之前还需要 signalr.js

<script src="~/lib/signalr/signalr.js"></script>
<script src="~/lib/msgpack5/msgpack5.js"></script>
<script src="~/lib/signalr/signalr-protocol-msgpack.js"></script>

如果将 .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol()) 添加到 HubConnectionBuilder,则会将客户端配置为在连接到服务器时使用 MessagePack 协议。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())
    .build();

注意

目前,JavaScript 客户端上没有 MessagePack 协议的配置选项。

Java 客户端

若要使用 Java 启用 MessagePack,请安装 com.microsoft.signalr.messagepack 包。 使用 Gradle 时,将以下行添加到 build.gradle 文件的 dependencies 部分:

implementation 'com.microsoft.signalr.messagepack:signalr-messagepack:5.0.0'

使用 Maven 时,将以下行添加到 pom.xml 文件的 <dependencies> 元素中:

<dependency>
    <groupId>com.microsoft.signalr.messagepack</groupId>
    <artifactId>signalr</artifactId>
    <version>5.0.0</version>
</dependency>

HubConnectionBuilder 上调用 withHubProtocol(new MessagePackHubProtocol())

HubConnection messagePackConnection = HubConnectionBuilder.create("YOUR HUB URL HERE")
    .withHubProtocol(new MessagePackHubProtocol())
    .build();

MessagePack 注意事项

使用 MessagePack 中心协议时,需要注意几个问题。

MessagePack 区分大小写

MessagePack 协议区分大小写。 例如,请考虑以下 C# 类:

public class ChatMessage
{
    public string Sender { get; }
    public string Message { get; }
}

从 JavaScript 客户端发送时,必须使用 PascalCased 属性名称,因为大小写必须与 C# 类完全匹配。 例如:

connection.invoke("SomeMethod", { Sender: "Sally", Message: "Hello!" });

使用 camelCased 名称无法正确绑定到 C# 类。 可使用 Key 属性为 MessagePack 属性指定不同的名称来解决此问题。 有关详细信息,请参阅 MessagePack-CSharp 文档

序列化/反序列化时不保留 DateTime.Kind

MessagePack 协议不提供对 DateTimeKind 值进行编码的方法。 因此,在对日期进行反序列化时,如果 DateTime.KindDateTimeKind.Local,MessagePack 中心协议会将其转换为 UTC 格式,否则它将不会触及时间并按原样传递。 如果你使用的是 DateTime 值,建议在发送这些值之前将它们转换为 UTC。 当你接收到它们时,将它们从 UTC 转换为本地时间。

JavaScript 中的 MessagePack 不支持 DateTime.MinValue

SignalR JavaScript 客户端使用的 msgpack5 库不支持在 MessagePack 中使用 timestamp96 类型。 此类型用于对非常大的日期值进行编码(无论是很早的过去还是遥远的将来)。 DateTime.MinValue 值为 January 1, 0001,该值必须编码为 timestamp96 值。 因此,不支持向 JavaScript 客户端发送 DateTime.MinValue。 当 JavaScript 客户端收到 DateTime.MinValue 时,将引发以下错误:

Uncaught Error: unable to find ext type 255 at decoder.js:427

通常,DateTime.MinValue 用于编码“缺失”的值或 null 值。 如果需要在 MessagePack 中对该值进行编码,请使用可以为 null 的 DateTime 值 (DateTime?),或对指示日期是否存在的单独 bool 值进行编码。

有关此限制的详细信息,请参阅 GitHub 问题 aspnet/SignalR#2228

“预先”编译环境中的 MessagePack 支持

.NET 客户端和服务器使用的 MessagePack-CSharp 库使用代码生成来优化序列化。 因此,默认情况下,它在使用“预先”编译的环境(如 Xamarin iOS 或 Unity)中不受支持。 可通过“预生成”序列化程序/反序列化程序代码,在这些环境中使用 MessagePack。 有关详细信息,请参阅 MessagePack-CSharp 文档。 预生成序列化程序后,可使用传递给 AddMessagePackProtocol 的配置委托注册它们:

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        StaticCompositeResolver.Instance.Register(
            MessagePack.Resolvers.GeneratedResolver.Instance,
            MessagePack.Resolvers.StandardResolver.Instance
        );
        options.SerializerOptions = MessagePackSerializerOptions.Standard
            .WithResolver(StaticCompositeResolver.Instance)
            .WithSecurity(MessagePackSecurity.UntrustedData);
    });

MessagePack 中的类型检查更加严格

JSON 中心协议将在反序列化过程中执行类型转换。 例如,如果传入对象的属性值为数字 ({ foo: 42 }),但 .NET 类的属性类型为 string,则该值将被转换。 但是,MessagePack 不会执行此转换并将引发异常,该异常可在服务器端日志(和控制台)中查看:

InvalidDataException: Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.

有关此限制的详细信息,请参阅 GitHub 问题 aspnet/SignalR#2937

Java 中的字符和字符串

在 Java 客户端中,char 对象将被序列化为单字符 String 对象。 这与 C# 和 JavaScript 客户端相反,后者将它们序列化为 short 对象。 MessagePack 规范本身不会定义 char 对象的行为,因此由库作者决定如何序列化它们。 客户端之间的行为差异是由我们用于实现的库造成的。

其他资源

本文假定读者熟悉 ASP.NET Core SignalR 入门中涵盖的主题。

什么是 MessagePack?

MessagePack 是一种快速而紧凑的二进制序列化格式。 当担忧性能和带宽问题时,这很有用,因为它创建的消息比 JSON 创建的小。 查看网络跟踪和日志时,二进制消息不可读取,除非这些字节是通过 MessagePack 分析器传递的。 SignalR 为 MessagePack 格式提供内置支持,并提供 API 供客户端和服务器使用。

在服务器上配置 MessagePack

若要在服务器上启用 MessagePack 中心协议,请在应用中安装 Microsoft.AspNetCore.SignalR.Protocols.MessagePack 包。 在 Startup.ConfigureServices 方法中,将 AddMessagePackProtocol 添加到 AddSignalR 调用以在服务器上启用 MessagePack 支持。

注意

JSON 默认启用。 添加 MessagePack 可同时支持 JSON 和 MessagePack 客户端。

services.AddSignalR()
    .AddMessagePackProtocol();

若要自定义 MessagePack 如何设置数据格式,AddMessagePackProtocol 可接受委托来配置选项。 在该委托中,可使用 FormatterResolvers 属性配置 MessagePack 序列化选项。 有关解析程序工作方式的详细信息,请访问位于 MessagePack-CSharp 的 MessagePack 库。 可以对要序列化的对象使用属性,以定义应如何处理这些对象。

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
        {
            MessagePack.Resolvers.StandardResolver.Instance
        };
    });

警告

强烈建议查看 CVE-2020-5234 并应用推荐的修补程序。 例如,将 MessagePackSecurity.Active 静态属性设置为 MessagePackSecurity.UntrustedData。 设置 MessagePackSecurity.Active 需要手动安装 1.9.x 版 MessagePack。 安装 MessagePack 1.9.x 会升级 SignalR 使用的版本。 MessagePack 2.x 版引入了中断性变更,与 SignalR 3.1 及更早版本不兼容。 如果 MessagePackSecurity.Active 未设置为 MessagePackSecurity.UntrustedData,则恶意客户端可能会导致拒绝服务。 在 Program.Main 中设置 MessagePackSecurity.Active,如下面的代码所示:

using MessagePack;

public static void Main(string[] args)
{
  MessagePackSecurity.Active = MessagePackSecurity.UntrustedData;

  CreateHostBuilder(args).Build().Run();
}

在客户端上配置 MessagePack

注意

JSON 在支持的客户端启用上默认启用。 客户端只能支持单个协议。 添加 MessagePack 支持将会替换之前配置的任何协议。

.NET 客户端

若要在 .NET 客户端中启用 MessagePack,请安装 Microsoft.AspNetCore.SignalR.Protocols.MessagePack 包,并在 HubConnectionBuilder 上调用 AddMessagePackProtocol

using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;

var hubConnection = new HubConnectionBuilder()
                        .WithUrl("/chathub")
                        .AddMessagePackProtocol()
                        .Build();

注意

AddMessagePackProtocol 调用接受委托来配置选项,就像服务器一样。

JavaScript 客户端

@microsoft/signalr-protocol-msgpack npm 包提供对 JavaScript 客户端的 MessagePack 支持。 在命令行界面中执行以下命令来安装包:

npm install @microsoft/signalr-protocol-msgpack

安装 npm 包后,可以通过 JavaScript 模块加载程序直接使用该模块,或引用以下文件将该模块导入浏览器:

node_modules\@microsoft\signalr-protocol-msgpack\dist\browser\signalr-protocol-msgpack.js

在浏览器中,还必须引用 msgpack5 库。 使用 <script> 标记创建引用。 可在 node_modules\msgpack5\dist\msgpack5.js 中找到该库。

注意

使用 <script> 元素时,顺序很重要。 如果在 msgpack5.js 之前引用了 signalr-protocol-msgpack.js,则在尝试使用 MessagePack 进行连接时将出现错误。 在 signalr-protocol-msgpack.js 之前还需要 signalr.js

<script src="~/lib/signalr/signalr.js"></script>
<script src="~/lib/msgpack5/msgpack5.js"></script>
<script src="~/lib/signalr/signalr-protocol-msgpack.js"></script>

如果将 .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol()) 添加到 HubConnectionBuilder,则会将客户端配置为在连接到服务器时使用 MessagePack 协议。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())
    .build();

注意

目前,JavaScript 客户端上没有 MessagePack 协议的配置选项。

MessagePack 注意事项

使用 MessagePack 中心协议时,需要注意几个问题。

MessagePack 区分大小写

MessagePack 协议区分大小写。 例如,请考虑以下 C# 类:

public class ChatMessage
{
    public string Sender { get; }
    public string Message { get; }
}

从 JavaScript 客户端发送时,必须使用 PascalCased 属性名称,因为大小写必须与 C# 类完全匹配。 例如:

connection.invoke("SomeMethod", { Sender: "Sally", Message: "Hello!" });

使用 camelCased 名称无法正确绑定到 C# 类。 可使用 Key 属性为 MessagePack 属性指定不同的名称来解决此问题。 有关详细信息,请参阅 MessagePack-CSharp 文档

序列化/反序列化时不保留 DateTime.Kind

MessagePack 协议不提供对 DateTimeKind 值进行编码的方法。 因此,在反序列化日期时,MessagePack 中心协议假定传入日期为 UTC 格式。 如果你使用的是本地时间的 DateTime 值,建议在发送这些值之前将它们转换为 UTC。 当你接收到它们时,将它们从 UTC 转换为本地时间。

有关此限制的详细信息,请参阅 GitHub 问题 aspnet/SignalR#2632

JavaScript 中的 MessagePack 不支持 DateTime.MinValue

SignalR JavaScript 客户端使用的 msgpack5 库不支持在 MessagePack 中使用 timestamp96 类型。 此类型用于对非常大的日期值进行编码(无论是很早的过去还是遥远的将来)。 DateTime.MinValue 值为 January 1, 0001,该值必须编码为 timestamp96 值。 因此,不支持向 JavaScript 客户端发送 DateTime.MinValue。 当 JavaScript 客户端收到 DateTime.MinValue 时,将引发以下错误:

Uncaught Error: unable to find ext type 255 at decoder.js:427

通常,DateTime.MinValue 用于编码“缺失”的值或 null 值。 如果需要在 MessagePack 中对该值进行编码,请使用可以为 null 的 DateTime 值 (DateTime?),或对指示日期是否存在的单独 bool 值进行编码。

有关此限制的详细信息,请参阅 GitHub 问题 aspnet/SignalR#2228

“预先”编译环境中的 MessagePack 支持

.NET 客户端和服务器使用的 MessagePack-CSharp 库使用代码生成来优化序列化。 因此,默认情况下,它在使用“预先”编译的环境(如 Xamarin iOS 或 Unity)中不受支持。 可通过“预生成”序列化程序/反序列化程序代码,在这些环境中使用 MessagePack。 有关详细信息,请参阅 MessagePack-CSharp 文档。 预生成序列化程序后,可使用传递给 AddMessagePackProtocol 的配置委托注册它们:

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
        {
            MessagePack.Resolvers.GeneratedResolver.Instance,
            MessagePack.Resolvers.StandardResolver.Instance
        };
    });

MessagePack 中的类型检查更加严格

JSON 中心协议将在反序列化过程中执行类型转换。 例如,如果传入对象的属性值为数字 ({ foo: 42 }),但 .NET 类的属性类型为 string,则该值将被转换。 但是,MessagePack 不会执行此转换并将引发异常,该异常可在服务器端日志(和控制台)中查看:

InvalidDataException: Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.

有关此限制的详细信息,请参阅 GitHub 问题 aspnet/SignalR#2937

其他资源

本文假定读者熟悉 ASP.NET Core SignalR 入门中涵盖的主题。

什么是 MessagePack?

MessagePack 是一种快速而紧凑的二进制序列化格式。 当担忧性能和带宽问题时,这很有用,因为它创建的消息比 JSON 创建的小。 查看网络跟踪和日志时,二进制消息不可读取,除非这些字节是通过 MessagePack 分析器传递的。 SignalR 为 MessagePack 格式提供内置支持,并提供 API 供客户端和服务器使用。

在服务器上配置 MessagePack

若要在服务器上启用 MessagePack 中心协议,请在应用中安装 Microsoft.AspNetCore.SignalR.Protocols.MessagePack 包。 在 Startup.ConfigureServices 方法中,将 AddMessagePackProtocol 添加到 AddSignalR 调用以在服务器上启用 MessagePack 支持。

注意

JSON 默认启用。 添加 MessagePack 可同时支持 JSON 和 MessagePack 客户端。

services.AddSignalR()
    .AddMessagePackProtocol();

若要自定义 MessagePack 如何设置数据格式,AddMessagePackProtocol 可接受委托来配置选项。 在该委托中,可使用 FormatterResolvers 属性配置 MessagePack 序列化选项。 有关解析程序工作方式的详细信息,请访问位于 MessagePack-CSharp 的 MessagePack 库。 可以对要序列化的对象使用属性,以定义应如何处理这些对象。

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
        {
            MessagePack.Resolvers.StandardResolver.Instance
        };
    });

警告

强烈建议查看 CVE-2020-5234 并应用推荐的修补程序。 例如,将 MessagePackSecurity.Active 静态属性设置为 MessagePackSecurity.UntrustedData。 设置 MessagePackSecurity.Active 需要手动安装 1.9.x 版 MessagePack。 安装 MessagePack 1.9.x 会升级 SignalR 使用的版本。 如果 MessagePackSecurity.Active 未设置为 MessagePackSecurity.UntrustedData,则恶意客户端可能会导致拒绝服务。 在 Program.Main 中设置 MessagePackSecurity.Active,如下面的代码所示:

using MessagePack;

public static void Main(string[] args)
{
  MessagePackSecurity.Active = MessagePackSecurity.UntrustedData;

  CreateHostBuilder(args).Build().Run();
}

在客户端上配置 MessagePack

注意

JSON 在支持的客户端启用上默认启用。 客户端只能支持单个协议。 添加 MessagePack 支持将会替换之前配置的任何协议。

.NET 客户端

若要在 .NET 客户端中启用 MessagePack,请安装 Microsoft.AspNetCore.SignalR.Protocols.MessagePack 包,并在 HubConnectionBuilder 上调用 AddMessagePackProtocol

using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;

var hubConnection = new HubConnectionBuilder()
                        .WithUrl("/chathub")
                        .AddMessagePackProtocol()
                        .Build();

注意

AddMessagePackProtocol 调用接受委托来配置选项,就像服务器一样。

JavaScript 客户端

@aspnet/signalr-protocol-msgpack npm 包提供对 JavaScript 客户端的 MessagePack 支持。 在命令行界面中执行以下命令来安装包:

npm install @aspnet/signalr-protocol-msgpack

安装 npm 包后,可以通过 JavaScript 模块加载程序直接使用该模块,或引用以下文件将该模块导入浏览器:

node_modules\@aspnet\signalr-protocol-msgpack\dist\browser\signalr-protocol-msgpack.js

在浏览器中,还必须引用 msgpack5 库。 使用 <script> 标记创建引用。 可在 node_modules\msgpack5\dist\msgpack5.js 中找到该库。

注意

使用 <script> 元素时,顺序很重要。 如果在 msgpack5.js 之前引用了 signalr-protocol-msgpack.js,则在尝试使用 MessagePack 进行连接时将出现错误。 在 signalr-protocol-msgpack.js 之前还需要 signalr.js

<script src="~/lib/signalr/signalr.js"></script>
<script src="~/lib/msgpack5/msgpack5.js"></script>
<script src="~/lib/signalr/signalr-protocol-msgpack.js"></script>

如果将 .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol()) 添加到 HubConnectionBuilder,则会将客户端配置为在连接到服务器时使用 MessagePack 协议。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())
    .build();

注意

目前,JavaScript 客户端上没有 MessagePack 协议的配置选项。

MessagePack 注意事项

使用 MessagePack 中心协议时,需要注意几个问题。

MessagePack 区分大小写

MessagePack 协议区分大小写。 例如,请考虑以下 C# 类:

public class ChatMessage
{
    public string Sender { get; }
    public string Message { get; }
}

从 JavaScript 客户端发送时,必须使用 PascalCased 属性名称,因为大小写必须与 C# 类完全匹配。 例如:

connection.invoke("SomeMethod", { Sender: "Sally", Message: "Hello!" });

使用 camelCased 名称无法正确绑定到 C# 类。 可使用 Key 属性为 MessagePack 属性指定不同的名称来解决此问题。 有关详细信息,请参阅 MessagePack-CSharp 文档

序列化/反序列化时不保留 DateTime.Kind

MessagePack 协议不提供对 DateTimeKind 值进行编码的方法。 因此,在反序列化日期时,MessagePack 中心协议假定传入日期为 UTC 格式。 如果你使用的是本地时间的 DateTime 值,建议在发送这些值之前将它们转换为 UTC。 当你接收到它们时,将它们从 UTC 转换为本地时间。

有关此限制的详细信息,请参阅 GitHub 问题 aspnet/SignalR#2632

JavaScript 中的 MessagePack 不支持 DateTime.MinValue

SignalR JavaScript 客户端使用的 msgpack5 库不支持在 MessagePack 中使用 timestamp96 类型。 此类型用于对非常大的日期值进行编码(无论是很早的过去还是遥远的将来)。 DateTime.MinValue 值为 January 1, 0001,该值必须编码为 timestamp96 值。 因此,不支持向 JavaScript 客户端发送 DateTime.MinValue。 当 JavaScript 客户端收到 DateTime.MinValue 时,将引发以下错误:

Uncaught Error: unable to find ext type 255 at decoder.js:427

通常,DateTime.MinValue 用于编码“缺失”的值或 null 值。 如果需要在 MessagePack 中对该值进行编码,请使用可以为 null 的 DateTime 值 (DateTime?),或对指示日期是否存在的单独 bool 值进行编码。

有关此限制的详细信息,请参阅 GitHub 问题 aspnet/SignalR#2228

“预先”编译环境中的 MessagePack 支持

.NET 客户端和服务器使用的 MessagePack-CSharp 库使用代码生成来优化序列化。 因此,默认情况下,它在使用“预先”编译的环境(如 Xamarin iOS 或 Unity)中不受支持。 可通过“预生成”序列化程序/反序列化程序代码,在这些环境中使用 MessagePack。 有关详细信息,请参阅 MessagePack-CSharp 文档。 预生成序列化程序后,可使用传递给 AddMessagePackProtocol 的配置委托注册它们:

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
        {
            MessagePack.Resolvers.GeneratedResolver.Instance,
            MessagePack.Resolvers.StandardResolver.Instance
        };
    });

MessagePack 中的类型检查更加严格

JSON 中心协议将在反序列化过程中执行类型转换。 例如,如果传入对象的属性值为数字 ({ foo: 42 }),但 .NET 类的属性类型为 string,则该值将被转换。 但是,MessagePack 不会执行此转换并将引发异常,该异常可在服务器端日志(和控制台)中查看:

InvalidDataException: Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.

有关此限制的详细信息,请参阅 GitHub 问题 aspnet/SignalR#2937

其他资源