ASP.NET SignalR 基础逐步教程(第二部分)
介绍 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 调用时,不区分大小写。
请注意,客户端应使用驼峰式命名——myChatHub
和 broadcastMessage
来指代 MyChatHub
和 BroadcastMessage
。此规则尊重 JavaScript 命名约定。但您仍然可以强制客户端代码使用 Pascal 命名。实际上,您可以强制客户端代码使用任何您想要的大小写敏感名称。让我们创建另一个简单的 Hub,它返回当前服务器时间
[HubName("PascalCasedMyDateTimeHub")]
public class MyDateTimeHub : Hub
{
[HubMethodName("PascalCasedGetServerDateTime")]
public async Task<DateTime> GetServerDateTime()
{
return await Task.Run<DateTime>(() => DateTime.Now);
}
}
通过使用 HubNameAttribute
和 HubMethodNameAttribute
,您可以为您的 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 Server、Redis 或 Windows Azure Service Bus 实现了出色的横向扩展。如第一部分所述,NuGet 上提供了这些不同技术的不同包:
Microsoft.AspNet.SignalR.SqlServer
:SQL Server 消息回传。Microsoft.AspNet.SignalR.Redis
:Redis 消息回传。Microsoft.AspNet.SignalR.ServiceBus
:Windows 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());
}
}
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。