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

简单的 SignalR 服务器和客户端应用程序演示常见使用场景

starIconstarIconstarIconstarIconstarIcon

5.00/5 (17投票s)

2019 年 7 月 16 日

CPOL

6分钟阅读

viewsIcon

100833

SignalR 服务器和客户端应用程序,用于演示常见场景,如连接、断开连接、加入和离开组、向所有客户端、特定客户端或一组客户端发送消息

引言

SignalR 是一个简化网页和服务器之间实时通信的库。它根据所使用的 Web 浏览器,使用 WebSockets 或长轮询方法。

有很多关于托管 SignalR 集线器和从客户端应用程序与之通信的文章。但其中一些过于简单(只展示如何发送消息),一些又过于复杂,难以理解,还有一些代码组织得很糟糕。我想用简单的代码示例演示简单的场景,几乎没有额外的代码。

演示的场景包括:

  • 在 WindowsForms 应用程序中自托管一个具有示例集线器的 SignalR 服务器
  • 从 WindowsForms 和 JavaScript 客户端连接到 SignalR 集线器
  • 从客户端应用程序订阅集线器事件(回调方法)
  • 从客户端应用程序调用集线器上定义的方法
  • 从客户端应用程序加入和离开组
  • 从 SignalR 集线器向所有客户端、一组客户端或单个客户端广播消息

WinForms 服务器应用程序

WindowsForms 自托管 SignalR 应用程序看起来像这样。

要在 WindowsForms 应用程序中托管 SignalR 服务器,必须通过“程序包管理器控制台”或“管理 NuGet 程序包”窗口安装以下 NuGet 程序包。

  • Microsoft.Owin.SelfHost
  • Microsoft.Owin.Cors
  • Microsoft.AspNet.SignalR.Core
  • Microsoft.AspNet.SignalR.SelfHost

下一步是准备自托管环境。为此,创建一个名为 Startup 的类,并将以下代码添加到其中。Configuration 方法将被自托管环境自动调用。

class Startup
{
     public void Configuration(IAppBuilder app)
     {
          //CORS need to be enabled for calling SignalR service 
          //from external web applications/pages
          app.UseCors(CorsOptions.AllowAll);
          //Find and register SignalR hubs
          app.MapSignalR();
     }
}

要启动托管 SignalR 服务的 Web 应用程序,请调用 WebApp.Start 方法并指定所需的 SignalR 服务地址。

_signalR = WebApp.Start<Startup>(txtUrl.Text);
下一步是准备 SignalR 集线器类,它将处理与客户端的通信。每个 SignalR 集线器类都派生自 Microsoft.AspNet.SignalR.Hub 类。它包含客户端可以调用的方法。它还可以引发客户端可以监听的事件(在客户端上调用回调方法)。
public class SimpleHub : Hub
{
//Called when a client is connected
override OnConnected()

//Called when a client is disconnected
override OnDisconnected(bool stopCalled)

//Public methods inside this region are callable from any SignalR client
#region Client Methods
//A client provides a user name for himself
void SetUserName(string userName)

//Client calls this to join a group
Task JoinGroup(string groupName)

//Client calls this to leave a group
Task LeaveGroup(string groupName)

//Clients call this to send a message to the hub
//Hub then broadcasts the message to all the connected clients
void Send(string msg)

#endregion
}

类型为 HubCallerContext 的集线器的 Context 属性有几个属性,提供有关当前请求的信息,例如 ConnectionIdHeadersRequestCookies

Clients 属性可用于对所有已连接的客户端或特定客户端执行操作。每个客户端都有一个 ConnectionId,可用于对客户端执行操作。以下调用会返回动态对象,可用于在客户端上调用操作。

//For all the clients
Clients.All 

//For a specific client
Clients.Client(connectionId)

//For clients that are members of a group
Clients.Group(groupName)

以下代码块会在所有客户端上调用 addMessage 操作。如果客户端上没有定义该操作,则不会发生任何事情。

Clients.All.addMessage(senderUserName, message);

Groups 属性可用于将客户端添加到组或从中移除。

//To add a user to a group
Groups.Add(userConectionId, groupName);

//To remove a user from a group
Groups.Remove(userConectionId, groupName);

每个请求都会创建一个集线器类的新实例,因此集线器类不能包含任何状态。此外,无法向集线器类添加实例事件并通过订阅这些事件来跟踪操作。

要解决此问题,可以使用两种方法:

  1. 添加静态状态变量和事件
  2. 使用依赖注入将状态对象注入到每个创建的集线器实例中

在此示例项目中,为了简单起见,我使用了静态变量和事件方法。因此,SimpleHub 类包含一个静态字典,用于保存已连接客户端的状态数据,以及几个静态事件来通知其订阅者。

static ConcurrentDictionary<string, string> _users = new ConcurrentDictionary<string, string>();

public static event ClientConnectionEventHandler ClientConnected;

public static event ClientConnectionEventHandler ClientDisconnected;

//...

为了从服务器向客户端发送消息(或在客户端上调用操作),我们需要访问集线器的 Clients 属性,但它通常仅在 hub 方法中处理请求时可用。当然,还有另一种方法可以访问集线器相关的上下文,集线器类之外。Microsoft.AspNet.SignalR.GlobalHost 有几个静态属性可用于访问和操作 SignalR 主机。其中之一是 ConnectionManager 属性。

以下代码获取 SimpleHub 类型的集线器上下文,并在所有已连接的客户端上调用一个方法。

var hubContext = GlobalHost.ConnectionManager.GetHubContext<SimpleHub>();

hubContext.Clients.All.addMessage("SERVER", txtMessage.Text);

WinForms 客户端应用程序

WindowsForms 客户端应用程序看起来像这样。

对于 SignalR 客户端组件,请将 Microsoft.AspNet.SignalR.Client NuGet 程序包添加到项目中。

连接到 SignalR 集线器的第一步是创建一个到 SignalR 服务器的 HubConnection。然后,获取将用于与所需集线器通信的代理对象。

//Create a connection for the SignalR server
_signalRConnection = new HubConnection(txtUrl.Text);

//Get a proxy object that will be used to interact with the specific hub on the server
//There may be many hubs hosted on the server, so provide the type name for the hub
_hubProxy = _signalRConnection.CreateHubProxy("SimpleHub");

下一步是注册集线器事件(由集线器调用的方法)。以下代码为 AddMessage 事件注册了一个处理程序方法。注册的 lambda 表达式接受两个参数,并将它们写入 log 区域。

_hubProxy.On<string, string>("AddMessage", 
                     (name, message) => writeToLog($"{name}:{message}"));            

配置完集线器事件后,就可以通过调用 HubConnection 对象的 Start 方法来连接到 SignalR 集线器了。

await _signalRConnection.Start();

可以使用 IHubProxy 对象的 Invoke 方法以及远程操作的名称和所需参数来调用连接集线器上定义的公共操作。示例代码调用 SimpleHub 类的 Send 方法。

_hubProxy.Invoke("Send", txtMessage.Text);

JavaScript 客户端应用程序

JavaScript 客户端应用程序与 WinForms 客户端应用程序类似。我尝试构建一个相似的界面并实现完全相同的操作。唯一的功能差异是,用户界面元素是根据当前状态启用的。

JavaScript 客户端使用 jQueryjQuery.signalR 脚本库。通过安装以下 NuGet 程序包,可以将它们添加到项目中:

  • jQuery
  • Microsoft.AspNet.SignalR.JS

为了连接和使用远程 SignalR 集线器,必须引用另一个脚本库。但它不是一个您可以定位并在 HTML 文件中添加脚本引用的文件。此脚本由 SignalR 服务器自动生成。脚本地址是 **“signalR 地址” + “/hubs”**。

<script src="https://:8080/signalr/hubs"></script>
<!-- or relative path -->
<script src="~/signalr/hubs"></script>

测试客户端应用程序可以通过在 Url 输入框中输入服务 URL 地址来连接任何 SignalR 服务。因此,对于这个示例应用程序,脚本位置不是固定的。为了解决动态脚本位置问题,我在 connect 函数中使用 jQuery.getScript 函数加载了这个自动生成的 hubs 脚本。getScript 函数接受两个参数:要加载的 JavaScript 的地址和一个在下载脚本完成后调用的函数。正如您在示例Index.html 页面的 connect JavaScript 函数中看到的,实际的连接操作是在此自动生成的脚本成功下载后执行的。

//Connect to the SignalR server and get the proxy for simpleHub
function connect() {

     //Load auto generated hub script dynamically and perform connection 
     //operation when loading completed
     //SignalR server location is specified by 'Url' input element, 
     //hub script must be loaded from the same location
     //For production, remove this call and uncomment the script block in the header part
      $.getScript( $("#txtUrl").val() + "/hubs", function() {

                $.connection.hub.url = $("#txtUrl").val();

                // Declare a proxy to reference the hub.
                simpleHubProxy = $.connection.simpleHub;

                //Register to the "AddMessage" callback method of the hub
                //This method is invoked by the hub
                simpleHubProxy.client.addMessage = function (name, message) {
                    writeToLog(name + ":" + message);
                };

                //Connect to hub
                $.connection.hub.start().done( function () {
                    writeToLog("Connected.");
                    simpleHubProxy.server.setUserName($('#txtUserName').val());
                });
        });
} 

连接和与 SignalR 集线器交互的步骤与 WindowsForms 客户端相同。示例代码演示了如何准备到 SignalR 集线器的连接,创建用于与之交互的代理,注册服务器事件(或回调),以及在服务器端调用(执行)方法(在本例中为 setUserName)。

有一个简单的 JavaScript 函数用于每个操作。例如,以下是 sendMessage 函数,它通过调用 txtMessage 输入框中输入的文本的“send”方法,将提供的消息发送到服务器。

//Send a message to server
function sendMessage() {
    if(simpleHubProxy != null) {
        simpleHubProxy.server.send($('#txtMessage').val());
    }
}

历史

  • 2019 年 7 月 16 日:初始版本
© . All rights reserved.