Asp.net SignalR 教程

1. Asp.net SignalR 介绍

ASP.NET SignalR 是一个用于实时 Web 应用程序的开源库,它使得在 .NET 环境下实现实时通信变得简单。

SignalR 提供了一种简单的方式来建立双向通信,使得服务器端和客户端之间可以实时地交换数据。

2. Asp.net SignalR 背景

当今网络应用中的实时通讯技术对于提升用户体验至关重要。

在这个领域,我们可以区分实时通讯和伪实时通讯。

实时通讯

  • WebSocketWebSocketHTML5 中的一项重要技术,它基于 TCP 协议,与 HTTP 没有直接关系,允许客户端和服务器之间建立持久性的双向通信。
  • SSE(Server-Sent Events)SSE 允许服务器向客户端推送事件,客户端通过监听这些事件来获取数据,是一种基于 HTTP 的实时通信方式。
  • 长连接:通过保持一次链接的时间来实现实时通讯,比如保持一个连接 5 秒钟。
  • Forever Frame :这种方式通过在页面中插入一个永久连接的隐藏框架(iframe) ,将服务器的数据推送到客户端。虽然实现方式有些繁琐,但在某些情况下也可行。
  • 轮询:通过不断地向服务器发送请求来获取数据,是一种通用且任何浏览器都支持的方式,但是效率相对较低。

SignalR 的出现

面对这些不同的通讯方式,出现了各种不同的实现方式。在这个百花齐放的阶段,为了解决通讯方式的多样性以及兼容性的问题,需要一个统一的高层封装。于是,微软的 ASP.NET 团队推出了 SignalR 框架。

SignalRWebSocketSSE 、长连接、Forever Frame 和轮询等方式进行了封装和优化,根据浏览器的支持情况和网络环境动态选择最佳的通讯方式,从而实现了更好的兼容性和性能,为开发者提供了更便捷、更灵活的实时通讯解决方案。

3. Asp.net SignalR 发展历程

Owin规范

在构建 SignalR 之前,微软首先参与了 Owin 规范的制定。Owin(Open Web Interface for .NET)是一个开放的规范,旨在定义 .NET Web应用程序和Web服务器之间的标准接口。通过 Owin,开发人员可以编写可在不同的 .NET Web服务器之间轻松迁移的中间件(Middleware)组件。这种规范的引入为构建 SignalR 提供了一个标准化的基础。

Katana

Katana 是微软在 Owin 规范基础上构建的一个开源项目。它为 .NET Web应用程序提供了一种实现 Owin 规范的方式。Katana 提供了一组工具和库,使开发人员能够构建和托管基于 Owin 的Web应用程序。它为 SignalR 的实现提供了重要的基础设施和支持,允许 SignalROwin 规范下进行开发和集成。

通过 Katana,开发人员可以利用 Owin 提供的灵活性和可扩展性来构建高度可定制的 .NET Web应用程序。SignalR 作为 Katana 中的一部分,可以利用 Katana 提供的中间件和托管功能来实现实时通信的功能,并与其他 Owin 中间件无缝集成。

Owin 规范为构建 SignalR 提供了一个标准化的接口,而 Katana 则提供了实现这一接口的工具和基础设施。

SignalR 可以利用 Owin 提供的灵活性和 Katana 提供的托管功能来实现实时通信的功能,从而为开发人员提供了一个方便、灵活且可定制的解决方案。

SignalR框架

Asp.net SignalR框架中,实现实时通信的两个核心组件是PersistentConnectionHub。这两个组件在实现实时通信的过程中扮演着不同的角色,具有各自的特点和用途。

PersistentConnection

PersistentConnectionSignalR框架中的一个基本组件,它属于底层的Socket模式。使用PersistentConnection,开发人员可以直接操作连接和消息传输的细节,这使得它更加接近底层,提供了更精细的控制。

虽然PersistentConnection提供了底层的操作接口,但相应地,它也更加复杂和不易上手。开发人员需要处理连接管理、消息传输等底层细节,这对于一些高度定制化或者对通信细节有特殊要求的场景可能更为适用。

Hub

PersistentConnection相比,Hub提供了更高级别的抽象,更加接近人类的思维方式。HubPersistentConnection的基础上进行了封装,提供了一种更简单、更易用的编程接口。

通过Hub,开发人员可以定义客户端和服务器之间的交互逻辑,而不需要直接处理底层的通信细节。这使得使用Hub更加方便快捷,尤其适用于快速开发和简化复杂性的场景。

总的来说,PersistentConnection更适合对通信细节有精细控制要求的场景,而Hub则更适合快速开发和简化复杂性的场景。开发人员可以根据具体需求和项目特点选择合适的组件来实现实时通信功能。

4. Owin Startup介绍以及使用

ASP.NET SignalR中,Owin Startup 扮演着非常重要的角色。Owin Startup 类负责配置应用程序并启动 Owin 管道。通过 Owin Startup,我们可以定义中间件、配置服务以及设置路由等操作。

Owin Startup 的介绍

Owin Startup 类是一个托管应用程序的入口点,它负责初始化应用程序所需的所有资源。在 SignalR 中,Owin Startup 类可以通过配置中间件、设置路由以及配置依赖项注入等方式来定制应用程序的行为。

Owin Startup如何使用

当使用 Owin Startup 时,你需要在项目中创建一个名为 Startup.cs 的类,并确保它包含一个名为 Configuration 的公共方法。在这个方法中,你可以配置应用程序所需的各种设置。以下是一个简单的示例,展示了如何在 Startup.cs 中配置 SignalR

csharp 复制代码
using Microsoft.Owin;
using Owin;
​
[assembly: OwinStartup(typeof(YourNamespace.Startup))]
namespace YourNamespace
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // 配置 SignalR
            app.MapSignalR();
​
            // 可选:配置其他中间件或服务
            // app.UseXXXX();
        }
    }
}

在这个示例中,Startup.cs 类包含了一个名为 Configuration 的方法,该方法接收一个类型为 IAppBuilder 的参数 app。在 Configuration 方法中,我们调用了 app.MapSignalR() 方法来配置 SignalR

在此之前,确保已经通过 NuGet 包管理器安装了 Microsoft.AspNet.SignalR 包,并且在 Global.asaxMain 方法中启动了 Owin 管道,例如:

csharp 复制代码
using Microsoft.Owin.Hosting;
using System;
​
namespace YourNamespace
{
    class Program
    {
        static void Main(string[] args)
        {
            string url = "http://localhost:8080";
            using (WebApp.Start<Startup>(url))
            {
                Console.WriteLine($"Server running at {url}");
                Console.ReadLine();
            }
        }
    }
}

这样,当你启动应用程序时,Owin 就会自动调用 Startup 类的 Configuration 方法,从而配置 SignalR,并使其可用于应用程序。

5. PersistentConnection快速搭建第一个案例

PersistentConnectionSignalR 框架中的一个核心组件,它允许你直接操作连接和消息传输的细节。

首先,创建一个继承自 PersistentConnection 的新类,例如 ChatConnection

csharp 复制代码
using Microsoft.AspNet.SignalR;
​
public class ChatConnection : PersistentConnection
{
    protected override Task OnConnected(IRequest request, string connectionId)
    {
        // 连接事件处理逻辑
        Console.WriteLine($"Client connected: {connectionId}");
        return base.OnConnected(request, connectionId);
    }
​
    protected override Task OnReceived(IRequest request, string connectionId, string data)
    {
        // 消息接收处理逻辑
        Console.WriteLine($"Message received from {connectionId}: {data}");
        return base.OnReceived(request, connectionId, data);
    }
​
    protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled)
    {
        // 断开连接处理逻辑
        Console.WriteLine($"Client disconnected: {connectionId}");
        return base.OnDisconnected(request, connectionId, stopCalled);
    }
}

然后,在 Owin Startup 类的 Configuration 方法中,注册你的 PersistentConnection 类,如下所示:

csharp 复制代码
using Microsoft.Owin;
using Owin;
​
[assembly: OwinStartup(typeof(YourNamespace.Startup))]
​
namespace YourNamespace
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // 注册 PersistentConnection
            app.MapSignalR<ChatConnection>("/chat");
​
            // 可选:配置其他中间件或服务
            // app.UseXXXX();
        }
    }
}

在这个示例中,我们创建了一个名为 ChatConnectionPersistentConnection 类,并重写了其中的几个方法来处理连接、消息接收和断开连接事件。

然后,在 Startup.cs 中,我们使用 app.MapSignalR<ChatConnection>("/chat") 方法来注册 ChatConnection 类,并指定了路由地址为 "/chat"。这样,SignalR 就会将 /chat 路由映射到我们的 ChatConnection 类上,从而实现了基于 PersistentConnection 的实时通信功能。

6. SignalR异步方法

SignalR 持久连接中,start() 方法是一个异步方法,即它不会立即返回结果,而是需要一段时间来完成连接的建立。因此,在调用 start() 方法后,不能立即调用 send() 方法发送消息,因为此时连接可能尚未建立完成。

为了确保在连接建立完成后再发送消息,有两种常见的做法:

  1. send() 方法放在 start() 方法的回调函数中。也就是说,在 start() 方法执行成功后,会调用一个回调函数,在这个回调函数中调用 send() 方法。这样可以确保 send() 方法在连接建立完成后执行。
  2. start() 方法的回调函数中设置一个标识(如 ready),表示连接已经建立完成。然后在 send() 方法执行之前,检查这个标识是否为 true,只有在标识为 true 时才执行 send() 方法。这种方式也能保证 send() 方法在连接建立完成后执行。

这样做的目的是为了避免在连接尚未建立完成时就尝试发送消息,从而导致消息发送失败或者连接断开等问题。

javascript 复制代码
// 示例代码 - 使用回调函数处理异步方法
connection.start().done(() => {
    // 在连接建立完成后执行的回调函数中调用 send() 方法
    connection.send("Hello, world!");
});
​
// 示例代码 - 使用标识处理异步方法
var ready = false; // 设置一个标识,表示连接是否已准备好
connection.start().done(() => {
    // 在连接建立完成后将标识设置为 true
    ready = true;
});
​
// 在其他地方,比如某个事件触发时,发送消息
$("#sendButton").click(() => {
    // 检查标识,只有在连接准备好时才发送消息
    if (ready) {
        connection.send("Hello, world!");
    } else {
        // 连接尚未准备好,可能会导致发送失败
        console.log("Connection is not ready yet.");
    }
});

7.SignalR跨域问题

两种常见的跨域解决方法:jsonpcors

Jsonp

  • Jsonp 是一种利用 <script> 标签的特性来实现跨域请求的方法。它需要服务器配合,因为在跨域请求时,服务器需要返回一段 JavaScript 代码,而不是普通的数据。Jsonp 只支持 GET 请求,且数据量有限制,通常是 4KB8KB
  • Jsonp 在现代浏览器中都得到了支持,包括 IE7IE8Chrome 等浏览器。
arduino 复制代码
// 在 OWIN Startup 类中配置 SignalR
public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        // 启用 Jsonp 支持
        var config = new HubConfiguration
        {
            EnableJSONP = true
        };
        app.MapSignalR(config);
    }
}

Cors 模式

  • Cors(跨域资源共享)是一种在服务器端设置响应头来允许跨域请求的方法。它支持 POST 请求,并且允许更大的数据量传输。在 Cors 模式中,服务器需要在响应头中增加 Access-Control-Allow-Origin:* 参数,表示允许任何域的请求访问数据。
  • Cors 模式在现代浏览器中都得到了支持,但在IE8中需要特别注意,因为IE8并不完全支持 Cors。在 IE8中,Cors可以用于 POST请求,但对于 GET 请求的支持可能存在限制。
arduino 复制代码
// 在 OWIN Startup 类中配置 SignalR
public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        // 启用 Cors 支持
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
        app.MapSignalR();
    }
}

8. SignalRgroup操作

SignalR中,group操作是指将连接(Connection)分组,以便向特定组中的所有连接发送消息。

这在需要向特定用户群发送消息时非常有用,例如实时聊天应用程序中的群组聊天功能。

对于使用 PersistentConnection 的情况

通过重写 OnConnectedAsyncOnDisconnectedAsyncOnReceivedAsync 方法来实现组操作。

csharp 复制代码
using Microsoft.AspNet.SignalR;
using System.Collections.Concurrent;
using System.Threading.Tasks;
​
public class MyConnection : PersistentConnection
{
    // 存储连接ID和组名之间的映射关系
    private static readonly ConcurrentDictionary<string, string> connectionGroups = new ConcurrentDictionary<string, string>();
​
    protected override Task OnConnected(IRequest request, string connectionId)
    {
        // 添加连接到默认组
        connectionGroups.TryAdd(connectionId, "default");
        return base.OnConnected(request, connectionId);
    }
​
    protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled)
    {
        // 从所有组中移除连接
        string groupName;
        connectionGroups.TryRemove(connectionId, out groupName);
        return base.OnDisconnected(request, connectionId, stopCalled);
    }
​
    protected override Task OnReceived(IRequest request, string connectionId, string data)
    {
        // 处理接收到的数据
        return base.OnReceived(request, connectionId, data);
    }
​
    // 将连接添加到指定的组
    public void AddToGroup(string connectionId, string groupName)
    {
        connectionGroups[connectionId] = groupName;
    }
​
    // 从指定的组中移除连接
    public void RemoveFromGroup(string connectionId)
    {
        string groupName;
        connectionGroups.TryRemove(connectionId, out groupName);
    }
​
    // 发送消息给指定组中的所有连接
    public Task SendToGroup(string groupName, string message)
    {
        return Connection.Broadcast(message, connectionId =>
        {
            string group;
            if (connectionGroups.TryGetValue(connectionId, out group) && group == groupName)
            {
                return true;
            }
            return false;
        });
    }
}

对于使用 Hub 的情况

将连接添加到组中:
csharp 复制代码
public class MyHub : Hub
{
    public async Task AddToGroup(string groupName)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
    }
}
将连接从组中移除:
csharp 复制代码
public class MyHub : Hub
{
    public async Task RemoveFromGroup(string groupName)
    {
        await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
    }
}
向特定组发送消息:
csharp 复制代码
public class MyHub : Hub
{
    public async Task SendToGroup(string groupName, string message)
    {
        await Clients.Group(groupName).SendAsync("ReceiveMessage", message);
    }
}

在上述示例中:

  • AddToGroup 方法用于将当前连接添加到指定的组中。
  • RemoveFromGroup 方法用于将当前连接从指定的组中移除。
  • SendToGroup 方法用于向指定的组发送消息。在这个例子中,ReceiveMessage 是客户端定义的接收消息的方法名,因此客户端需要实现这个方法来接收消息。

通过这些示例,您可以实现SignalR中的group操作,从而实现群组消息的发送和接收。

请注意,组名称可以是任何您选择的名称,但需要确保在发送消息时使用正确的组名称。

9. Hub模式快速上手案例

HubSignalR中的高级概念,它提供了一种更加简单、直观的方式来处理客户端和服务器之间的通信。

与持久连接相比,Hub更具人性化,更易于使用,并且提供了更丰富的功能。

主要特点

  1. 类似于RPC的调用模式Hub允许客户端和服务器之间进行远程过程调用(RPC),使得客户端可以调用服务器端定义的方法,而服务器端也可以调用客户端定义的方法。
  2. 更丰富的功能 :与持久连接相比,Hub提供了更多的功能和抽象,例如组操作、客户端代理等,使得开发者可以更轻松地构建复杂的实时应用程序。
  3. 事件处理Hub提供了多个事件处理方法,如OnConnectedOnDisconnected等,可以在客户端连接、断开连接等事件发生时执行特定的逻辑。
  4. 自动代理生成SignalR会自动生成客户端代理,使得客户端可以直接调用服务器端定义的方法,无需手动编写通信代码。

示例代码

csharp 复制代码
using Microsoft.AspNet.SignalR;
using System.Threading.Tasks;
​
public class MyHub : Hub
{
    // 客户端调用的方法
    public void SendMessage(string message)
    {
        // 在服务器端向所有客户端广播消息
        Clients.All.ReceiveMessage(message);
    }
​
    // 在客户端连接时调用
    public override Task OnConnected()
    {
        // 在连接时发送欢迎消息
        Clients.Caller.ReceiveMessage("Welcome to the chat!");
        return base.OnConnected();
    }
​
    // 在客户端断开连接时调用
    public override Task OnDisconnected(bool stopCalled)
    {
        // 在断开连接时发送告别消息
        Clients.All.ReceiveMessage("Goodbye!");
        return base.OnDisconnected(stopCalled);
    }
}

Startup.cs 文件中,您只需要调用 MapSignalR 方法来注册SignalR。该方法会自动将 /signalr 路径映射到SignalR的处理程序。

arduino 复制代码
using Microsoft.Owin;
using Owin;
​
[assembly: OwinStartup(typeof(YourNamespace.Startup))]
​
namespace YourNamespace
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // 注册SignalR
            app.MapSignalR();
            // 等价下面写法
            app.MapSignalR<YourSignalRConfiguration>("/signalr");
            // 等价
            app.MapSignalR("/signalr", map =>
            {
                var hubConfiguration = new HubConfiguration
                {
                    // 启用 Jsonp 支持
                    EnableJSONP = true
                };
                map.RunSignalR(hubConfiguration);
            });
        }
    }
}

客户端

安装SignalR客户端库 :使用NuGet包管理器或其他适当的方式,将SignalR客户端库添加到您的项目中。

arduino 复制代码
Install-Package Microsoft.AspNet.SignalR.Client

创建SignalR连接 :在您的应用程序中,创建一个SignalR连接对象以连接到服务器。您需要指定服务器的URL。例如:

ini 复制代码
var connection = new HubConnection("http://your_server_url/signalr");

创建Hub代理 :创建一个代理对象,用于调用服务器上定义的Hub方法。例如:

ini 复制代码
var hubProxy = connection.CreateHubProxy("MyHub");

在这里,"MyHub" 是服务器上的Hub类的名称。

设置接收消息的处理程序:为您感兴趣的事件设置相应的处理程序,以便在接收到消息时执行特定的逻辑。例如:

ini 复制代码
hubProxy.On<string>("ReceiveMessage", message =>
{
    Console.WriteLine("Received message: " + message);
});

启动连接 :使用 Start 方法启动连接。在连接成功之后,您可以开始与服务器进行通信。

scss 复制代码
connection.Start().Wait(); // 使用Wait()方法等待连接完成

发送消息:使用代理对象调用服务器上的方法,发送消息或执行其他操作。例如:

arduino 复制代码
hubProxy.Invoke("SendMessage", "Hello from client");

处理连接关闭:在适当的地方处理连接关闭的情况,例如连接意外断开或手动关闭连接。您可以使用事件处理程序来处理这些情况。

arduino 复制代码
connection.Closed += () =>
{
    Console.WriteLine("Connection closed");
};

通过这些步骤,您可以创建一个SignalR客户端,并与服务器进行通信。

请注意,以上示例仅为演示目的,您需要根据您的具体应用程序和需求进行适当的调整和扩展。

网页端

要在页面端使用SignalR,您需要遵循以下步骤:

引入 SignalR JavaScript 客户端库 :在您的页面中引入 SignalR JavaScript 客户端库。

您可以从 CDN 上获取 SignalR 客户端库,也可以将其下载到本地并引入。

xml 复制代码
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/5.0.9/signalr.min.js"></script>

创建 SignalR 连接 :使用 HubConnectionBuilder 创建 SignalR 连接对象,并指定服务器的 URL

csharp 复制代码
var connection = new signalR.HubConnectionBuilder()
    .withUrl("http://your_server_url/signalr")
    .build();

设置接收消息的处理程序 :使用 on 方法为您感兴趣的事件设置相应的处理程序,以便在接收到消息时执行特定的逻辑。

javascript 复制代码
connection.on("ReceiveMessage", (message) => {
    console.log("Received message: " + message);
});

启动连接 :使用 start 方法启动连接。在连接成功之后,您可以开始与服务器进行通信。

javascript 复制代码
connection.start().then(function () {
    console.log("Connection started");
}).catch(function (err) {
    console.error(err.toString());
});

发送消息 :使用连接对象的 invoke 方法调用服务器上的方法,发送消息或执行其他操作。

arduino 复制代码
connection.invoke("SendMessage", "Hello from client");

处理连接关闭:在适当的地方处理连接关闭的情况,例如连接意外断开或手动关闭连接。您可以使用事件处理程序来处理这些情况。

javascript 复制代码
connection.onclose(function () {
    console.log("Connection closed");
});

使用 TypeScript

如果您在 TypeScript 中使用 SignalR,则可以直接使用 SignalRTypeScript 客户端库,并按照上述相似的步骤来操作。

确保您的项目中已经安装了 @microsoft/signalr 包。

bash 复制代码
npm install @microsoft/signalr

然后,您可以在 TypeScript文件中引入并使用 SignalR

typescript 复制代码
import * as signalR from "@microsoft/signalr";
​
let connection = new signalR.HubConnectionBuilder()
    .withUrl("http://your_server_url/signalr")
    .build();
​
connection.on("ReceiveMessage", (message: string) => {
    console.log("Received message: " + message);
});
​
connection.start().then(() => {
    console.log("Connection started");
}).catch((err: any) => {
    console.error(err.toString());
});
​
connection.invoke("SendMessage", "Hello from client");

通过这些步骤,您可以在页面端使用 JavaScriptTypeScript 来与 SignalR 服务器进行通信。

10. 通过SignalR.Utils手工代理及VSBuilding事件加速Hub编程

待编写。。。

相关推荐
落落落sss3 分钟前
spring-data-mongoDB
java·服务器·数据库·后端·python·mongodb·spring
web135085886354 分钟前
10分钟上手DeepSeek开发:SpringBoot + Vue2快速构建AI对话系统
人工智能·spring boot·后端
爱吃烤鸡翅的酸菜鱼5 分钟前
Java【网络原理】(2)初识网络续与网络编程
java·网络·后端·java-ee
uhakadotcom16 分钟前
最新发布的Claude 3.7 Sonnet提供了什么新能力,效果如何?
后端·架构·github
计算机毕设指导633 分钟前
基于Springboot医院预约挂号小程序系统【附源码】
java·spring boot·后端·spring·小程序·apache·intellij-idea
学而不思则罔~1 小时前
Go 之 语言基本类型
开发语言·后端·golang
千年死缓1 小时前
go实现敏感词过滤
开发语言·后端·golang
张子栋1 小时前
SpringMVC(一)SpringMVC入门案例、PostMan工具的使用、请求与响应、Rest风格
java·后端
JiaJunRun1 小时前
Spring Core面试题
java·后端·spring
微臣愚钝3 小时前
JavaWeb开发入门:从前端到后端的完整流程解析
后端·web