65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET SignalR 基础逐步教程(第二部分)

starIconstarIconstarIconstarIconstarIcon

5.00/5 (7投票s)

2013年9月3日

CPOL

6分钟阅读

viewsIcon

86894

downloadIcon

5203

介绍 SignalR 中的 Hub、横向扩展和可扩展性

引言

第一部分介绍了 SignalR 的基本知识以及如何在演示中使用 PersistentConnection。在本文中,我将介绍如何使用 Hub、如何横向扩展 SignalR 服务以及如何通过您自己的代码扩展 SignalR 功能。更多详细信息,请参阅 SignalR 官方网站:http://signalr.net

要使用下载的示例,请从 NuGet 重新获取相应的 SignalR 包,因为我在上传之前删除了这些包以减小 zip 文件大小。

Hub

Hub 主要针对服务器到客户端和客户端到服务器的 RPC。它是基于 PersistentConnection 实现的。如果需要,您可以在一个连接中创建多个 Hub。将所有方法定义在一个 Hub 中或多个 Hub 中,性能没有差异。

让我们创建一个空的 Web 应用程序并定义一个简单的 Hub,它将客户端消息广播到所有连接的客户端:

    public class MyChatHub : Hub
    {
        public async Task BroadcastMessage(string callerName, string message)
        {
            // Case-insensitive when the server RPC the client's methods
            await Clients.All.appendnewmessage(callerName, message);
        }
    } 

BroadcastMessage 方法定义用于客户端代码向服务器发出 RPC 调用。并且 BroadcastMessage 向所有客户端的 appendNewMessage 方法发出 RPC 调用。正如代码注释所示,当服务器向客户端发出 RPC 调用时,不区分大小写。

请注意,客户端应使用驼峰式命名——myChatHubbroadcastMessage 来指代 MyChatHubBroadcastMessage。此规则尊重 JavaScript 命名约定。但您仍然可以强制客户端代码使用 Pascal 命名。实际上,您可以强制客户端代码使用任何您想要的大小写敏感名称。让我们创建另一个简单的 Hub,它返回当前服务器时间

    [HubName("PascalCasedMyDateTimeHub")]
    public class MyDateTimeHub : Hub
    {
        [HubMethodName("PascalCasedGetServerDateTime")]
        public async Task<DateTime> GetServerDateTime()
        {
            return await Task.Run<DateTime>(() => DateTime.Now);
        }
    } 

通过使用 HubNameAttributeHubMethodNameAttribute,您可以为您的 Hub 和 Hub 方法指定任何客户端名称。

为了注册我的 Hub,我在 Global.asax.cs 中使用了以下代码片段:

    public class Global : System.Web.HttpApplication 
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            RouteTable.Routes.MapHubs("/myhubs", new HubConfiguration());
        }
    }  

在这里,我将“/myhubs”指定为所有 Hub 的根 URL,并使用默认配置。在一个 Web 应用程序中,您必须将所有 Hub 映射到一个根 URL。MapHubs 是 SignalR 中定义的一个扩展方法。如果您不为 MapHubs 指定参数,它将把所有 Hub 映射到“/signalr”。但我仍然建议使用您自己的 URL,以避免不确定性和潜在的名称冲突。

在客户端 JavaScript 代码中访问服务器 Hub 有两种方法:使用自动生成的代理或不使用它。

我的第一个示例是使用自动生成的代理。实际上,我不需要在服务器端做任何事情。我只需要在网页中添加一行:

<head> 
    ...... 
    <script type="text/javascript" src="myhubs/hubs"></script>
</head> 

"myhubs/hubs" 告诉服务器发送自动生成的代理——一个流中的 JavaScript 文件。URL 必须由您已映射的 Hub URL 和单词“hubs”组合而成。如果您使用默认映射的 URL——“/signalr”,则自动生成的代理 URL 应该是“signalr/hubs”。

来自 index.html 的以下代码片段表明了如何使用服务器 Hub

        $(function () {
            // Connect Hubs without the generated proxy
            var timeHubProxy = $.connection.PascalCasedMyDateTimeHub;
            var chatHubProxy = $.connection.myChatHub;
            // Register client function to be called by server
            chatHubProxy.client.appendNewMessage = function (clientName, message) {
                addMsg(clientName + ": " + message);
            };
            // Start the hub connection
            addMsg("Connecting Hub...");
            $.connection.hub.start().done(function () {
                addMsg("Hub connected.");
                $("#refreshServerTime").click(function () {
                    timeHubProxy.server.PascalCasedGetServerDateTime().done(function (serverTime) {
                        $("#serverTime").text(serverTime);
                    });
                });
                $("#send").click(function () {
                    chatHubProxy.server.broadcastMessage($("#name").val(), $("#msg").val());
                });
            }).fail(function () {
                addMsg("Could not connect to Hub.");
            });
        });

您可以看到 JavaScript 代码调用服务器 Hub 的方法,就像使用自动生成的代理调用本地方法一样。请注意,我在客户端定义了 appendNewMessage 方法。此方法由服务器调用,并且不区分大小写。

我的第二个示例在没有自动生成代理的情况下使用服务器 Hub。它变得更复杂,因为我必须使用 Invoke 方法,如下面来自 index_no_generated_proxy.html 的代码所示

$(function () {
    // Connect Hubs without the generated proxy
    var connection = $.hubConnection("/myhubs");
    var timeHubProxy = connection.createHubProxy("PascalCasedMyDateTimeHub");
    var chatHubProxy = connection.createHubProxy("myChatHub");
    // Register client function to be called by server
    chatHubProxy.on("appendNewMessage", function (clientName, message) {
        addMsg(clientName + ": " + message);
    });
    // Start the hub connection
    addMsg("Connecting Hub...");
    connection.start().done(function () {
        addMsg("Hub connected.");
        $("#refreshServerTime").click(function () {
            timeHubProxy.invoke("PascalCasedGetServerDateTime").done(function (serverTime) {
                $("#serverTime").text(serverTime);
            });
        });
        $("#send").click(function () {
            chatHubProxy.invoke("broadcastMessage", $("#name").val(), $("#msg").val());
        });
    }).fail(function () {
        addMsg("Could not connect to Hub.");
    });
});

如果您使用默认映射——“/signalr”,则不需要参数“/myhubs”来初始化连接。

您还可以编写 .NET 客户端来调用服务器 Hub。Program.cs 中的代码与没有自动生成代理的 JavaScript 代码非常相似

static void Main(string[] args)
{
    // Almost the same usage as JavaScript without generated proxy
    var hubConn = new HubConnection("https://:6473/myhubs");
    var timeHubProxy = hubConn.CreateHubProxy("PascalCasedMyDateTimeHub");
    var chatHubProxy = hubConn.CreateHubProxy("myChatHub");
    chatHubProxy.On("appendNewMessage", delegate(string name, string message)
    {
        Console.WriteLine("{0}: {1}", name, message);
    });
    hubConn.Start().Wait();
    string inputLine;
    while (!string.IsNullOrEmpty(inputLine = Console.ReadLine()))
    {
        Task<DateTime> t = timeHubProxy.Invoke<DateTime>("PascalCasedGetServerDateTime");
        t.Wait();
        Console.WriteLine((DateTime)t.Result);
        chatHubProxy.Invoke("broadcastMessage", "dzy", inputLine).Wait();
    }
}

初始化 Hub 连接需要完整的 URL。并请注意 appendNewMessage 的定义方式。

横向扩展

SignalR 在 Web 场中借助 SQL ServerRedisWindows Azure Service Bus 实现了出色的横向扩展。如第一部分所述,NuGet 上提供了这些不同技术的不同包:

  • Microsoft.AspNet.SignalR.SqlServerSQL Server 消息回传。
  • Microsoft.AspNet.SignalR.Redis:Redis 消息回传。
  • Microsoft.AspNet.SignalR.ServiceBusWindows Azure Service Bus 消息回传。

由于开发环境的限制,这里我只提供一个 SQL Server 的示例。使用其他两种技术也非常相似。

让我们以前面的 Hub 演示为例。要启用 SQL Server 横向扩展,我们可以按照以下步骤操作:

在 SQL Server 中创建任意名称的新数据库。我们假设数据库名称为 SignalRScaleOut。当 Hub 演示首次运行时,SignalR 将创建必要的表。

在 SQL Server 中创建一个新登录名。我们将登录名和密码都设置为“signalr”,并授予它 SignalRScaleOut 数据库的 db_owner 权限。这一步不是强制性的,但为该数据库创建一个特定用户会更安全。创建新登录名时,请不要忘记取消选中“强制密码策略”和“强制密码过期”。

配置 Windows 防火墙入站规则以允许外部连接到 SQL Server。

通过 NuGet 将 Microsoft.AspNet.SignalR.SqlServer 包安装到演示 Hub 服务器项目。

在 Global.asax.cs 中,修改 Application_Start 方法,如下代码片段所示

public class Global : System.Web.HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        string connectionString = "server=127.0.0.1;uid=signalr;pwd=signalr;database=SignalRScaleOut";
        GlobalHost.DependencyResolver.UseSqlServer(connectionString);
        RouteTable.Routes.MapHubs("/myhubs", new HubConfiguration());
    }
}  

在这里,我配置 SignalR 使用 SQL Server 并指定连接字符串以启用横向扩展。IP 地址“127.0.0.1”应更改为您的 SQL Server 机器的精确 IP 地址。

现在横向扩展工作已完成。您可以将修改后的 Hub 服务器演示复制到 2 台或更多安装了 IIS 的不同机器上。当您在浏览器中运行应用程序并连接到不同的服务器时,您会发现所有 Hub 服务器共享相同的数据。

如果您在 SQL Server 中启用 Service Broker,性能会更好。要检查您的数据库是否启用了 Service Broker,您可以在 Management Studio 中编写以下查询

select [name],[service_broker_guid],[is_broker_enabled] 
from [master].[sys].[databases]  

在返回的数据集中,检查 is_broker_enabled 列的值。“1”表示启用,“0”表示禁用。以下 SQL 命令为 SignalRScaleOut 数据库启用 Service Broker:

ALTER DATABASE SignalRScaleOut SET ENABLE_BROKER  

可扩展性

为了方便可扩展性,SignalR 遵循依赖注入规则进行设计。您可以通过 GlobalHost.DependencyResolver 替换大多数 SignalR 组件的实现,如前面的代码片段所示。例如,如果我想使用自己的 JSON 序列化器,我可以实现 IJsonSerializer 接口并像以下代码片段一样注册它

public class Global : System.Web.HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        GlobalHost.DependencyResolver.Register(
            typeof(IJsonSerializer), 
            () => new MyJsonSerializer());
        RouteTable.Routes.MapHubs("/myhubs", new HubConfiguration());
    }
}  
请注意,尽管我们可以使用自己的 JSON 序列化器实现,但我们仍然只能使用文本序列化,因为 SignalR 内部硬编码使用 TextReader 和 TextWriter。

GlobalHost.DependencyResolver 实现了 IDependencyResolver 接口。因此,您甚至可以用自己的实现替换它,如下代码片段所示

public class Global : System.Web.HttpApplication
{ 
    protected void Application_Start(object sender, EventArgs e)
    {
        GlobalHost.DependencyResolver = new MyDependencyResolver();
        RouteTable.Routes.MapHubs("/myhubs", new HubConfiguration());
    }
}  

总结 

SignalR 的基本介绍到此结束。有关高级主题,请访问 SignalR 官方网站 - SignalR.Net

© . All rights reserved.