9.2 SignalR (实时通信) 9.2 SignalR (实时通信) SignalR 是一个 ASP.NET 库,简化了向 ASP.NET 应用程序添加实时 Web 功能的过程。实时 Web 功能是指服务器可以将内容推送到连接的客户端,而无需客户端持续轮询服务器。SignalR 解决了构建此类功能的复杂性,例如管理持久连接、处理不同的浏览器和传输机制以及处理服务器端扩展。 9.2.1 SignalR 的优势 简化实时开发: SignalR 抽象了底层传输细节,使开发人员能够专注于应用程序的逻辑。 多种传输方式支持: SignalR 自动选择最佳传输方式,包括 WebSockets、Server-Sent Events (SSE) 和 Long Polling。
SignalR 是一个 ASP.NET 库,简化了向 ASP.NET 应用程序添加实时 Web 功能的过程。实时 Web 功能是指服务器可以将内容推送到连接的客户端,而无需客户端持续轮询服务器。SignalR 解决了构建此类功能的复杂性,例如管理持久连接、处理不同的浏览器和传输机制以及处理服务器端扩展。
简化实时开发: SignalR 抽象了底层传输细节,使开发人员能够专注于应用程序的逻辑。
多种传输方式支持: SignalR 自动选择最佳传输方式,包括 WebSockets、Server-Sent Events (SSE) 和 Long Polling。它会根据客户端和服务器的能力进行协商。
双向通信: SignalR 支持服务器向客户端推送数据,以及客户端向服务器发送数据。
扩展性: SignalR 可以扩展以支持大量的并发连接,例如使用 Redis Backplane。
易于集成: SignalR 可以轻松集成到现有的 ASP.NET 应用程序中。
Hubs: Hub 是 SignalR 的核心组件。它是一个服务器端类,客户端可以直接调用其方法。Hub 充当服务器和客户端之间的管道,允许双方相互通信。
Connections: Connection 代表客户端和 SignalR 服务器之间的持久连接。SignalR 管理这些连接,并在连接断开时尝试重新建立连接。
Transport: Transport 是 SignalR 用于在客户端和服务器之间发送数据的底层机制。SignalR 自动选择最佳传输方式。
Groups: Group 允许将连接组织成逻辑单元。服务器可以向特定组发送消息,而不是向所有连接的客户端发送消息。
SignalR 的工作原理可以概括为以下步骤:
客户端连接: 客户端使用 SignalR 客户端库建立与 SignalR 服务器的连接。
传输协商: SignalR 客户端和服务器协商最佳传输方式。
持久连接: 建立持久连接后,客户端可以调用 Hub 上的方法,服务器也可以向客户端推送消息。
消息传递: 消息在客户端和服务器之间以 JSON 格式进行传递。
连接管理: SignalR 管理连接的生命周期,并在连接断开时尝试重新建立连接。
以下是一个简单的流程图,展示了 SignalR 的基本工作流程:
以下是一些 SignalR 的代码示例,展示了如何使用 SignalR 构建实时应用程序。
9.2.4.1 创建 SignalR Hub
首先,需要创建一个 Hub 类。Hub 类继承自 Microsoft.AspNetCore.SignalR.Hub 类,并包含客户端可以调用的方法。
using Microsoft.AspNetCore.SignalR; using System.Threading.Tasks; public class ChatHub : Hub { public async Task SendMessage(string user, string message) { await Clients.All.SendAsync("ReceiveMessage", user, message); } public async Task JoinGroup(string groupName) { await Groups.AddToGroupAsync(Context.ConnectionId, groupName); await Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId} has joined the group {groupName}."); } public async Task LeaveGroup(string groupName) { await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName); await Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId} has left the group {groupName}."); } }
SendMessage 方法:接收用户名和消息,并将消息发送给所有连接的客户端。
JoinGroup 方法:将客户端添加到指定的组,并向该组发送一条消息。
LeaveGroup 方法:将客户端从指定的组中移除,并向该组发送一条消息。
Clients.All.SendAsync:将消息发送给所有连接的客户端。
Clients.Group(groupName).SendAsync:将消息发送给指定组中的所有客户端。
Context.ConnectionId:获取当前客户端的连接 ID。
9.2.4.2 配置 SignalR 服务
在 Startup.cs 文件中,需要配置 SignalR 服务。
public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddSignalR(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); endpoints.MapHub<ChatHub>("/chatHub"); }); }
services.AddSignalR():将 SignalR 服务添加到依赖注入容器中。
endpoints.MapHub<ChatHub>("/chatHub"):将 ChatHub 映射到 /chatHub 路由。
9.2.4.3 创建 SignalR 客户端
在客户端,需要使用 SignalR 客户端库建立与 SignalR 服务器的连接,并调用 Hub 上的方法。
const connection = new signalR.HubConnectionBuilder() .withUrl("/chatHub") .configureLogging(signalR.LogLevel.Information) .build(); async function start() { try { await connection.start(); console.log("SignalR Connected."); } catch (err) { console.log(err); setTimeout(() => start(), 5000); } }; connection.onclose(async () => { await start(); }); // Receive messages connection.on("ReceiveMessage", (user, message) => { const li = document.createElement("li"); li.textContent = `${user} says ${message}`; document.getElementById("messagesList").appendChild(li); }); // Send Message document.getElementById("sendButton").addEventListener("click", async (event) => { const user = document.getElementById("userInput").value; const message = document.getElementById("messageInput").value; try { await connection.invoke("SendMessage", user, message); } catch (err) { console.error(err); } event.preventDefault(); }); // Join Group document.getElementById("joinGroupButton").addEventListener("click", async (event) => { const groupName = document.getElementById("groupNameInput").value; try { await connection.invoke("JoinGroup", groupName); } catch (err) { console.error(err); } event.preventDefault(); }); // Leave Group document.getElementById("leaveGroupButton").addEventListener("click", async (event) => { const groupName = document.getElementById("groupNameInput").value; try { await connection.invoke("LeaveGroup", groupName); } catch (err) { console.error(err); } event.preventDefault(); }); start();
new signalR.HubConnectionBuilder().withUrl("/chatHub").build():创建一个与 /chatHub 路由的 SignalR 连接。
connection.start():启动连接。
connection.on("ReceiveMessage", ...):注册一个回调函数,当服务器向客户端发送 ReceiveMessage 消息时,该回调函数将被调用。
connection.invoke("SendMessage", user, message):调用服务器端 ChatHub 上的 SendMessage 方法。
9.2.4.4 HTML 客户端界面
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>SignalR Chat</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> </head> <body> <div class="container"> <h1>SignalR Chat</h1> <div class="row"> <div class="col-md-12"> <ul id="messagesList"></ul> </div> </div> <div class="row"> <div class="col-md-4"> <input type="text" id="userInput" class="form-control" placeholder="User Name" /> </div> <div class="col-md-4"> <input type="text" id="messageInput" class="form-control" placeholder="Message" /> </div> <div class="col-md-4"> <button type="button" id="sendButton" class="btn btn-primary">Send</button> </div> </div> <hr /> <div class="row"> <div class="col-md-4"> <input type="text" id="groupNameInput" class="form-control" placeholder="Group Name" /> </div> <div class="col-md-4"> <button type="button" id="joinGroupButton" class="btn btn-success">Join Group</button> </div> <div class="col-md-4"> <button type="button" id="leaveGroupButton" class="btn btn-danger">Leave Group</button> </div> </div> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script> <script src="js/chat.js"></script> </body> </html>
这个 HTML 代码创建了一个简单的聊天界面,包括:
一个用于显示消息的 <ul> 元素 (messagesList)。
一个用于输入用户名的 <input> 元素 (userInput)。
一个用于输入消息的 <input> 元素 (messageInput)。
一个用于发送消息的 <button> 元素 (sendButton)。
一个用于输入组名的 <input> 元素 (groupNameInput)。
一个用于加入组的 <button> 元素 (joinGroupButton)。
一个用于离开组的 <button> 元素 (leaveGroupButton)。
9.2.4.5 代码详解
Hub 类: Hub 类是 SignalR 应用的核心。它定义了客户端可以调用的方法,以及服务器可以推送给客户端的消息。
ConfigureServices 方法: AddSignalR() 方法将 SignalR 服务添加到 ASP.NET Core 依赖注入容器中。
Configure 方法: MapHub<ChatHub>("/chatHub") 方法将 ChatHub 类映射到指定的 URL 路径。客户端将使用此 URL 路径建立与 Hub 的连接。
SignalR 客户端库: SignalR 客户端库提供了一组 API,用于建立与 SignalR 服务器的连接,调用 Hub 方法,以及接收服务器推送的消息。
HubConnectionBuilder: HubConnectionBuilder 类用于创建 HubConnection 对象,该对象代表与 SignalR 服务器的连接。
connection.start(): 启动与 SignalR 服务器的连接。
connection.on(): 注册一个回调函数,当服务器向客户端发送指定的消息时,该回调函数将被调用。
connection.invoke(): 调用服务器端 Hub 类上的方法。
身份验证和授权: SignalR 支持身份验证和授权,可以使用 ASP.NET Core 的身份验证和授权机制来保护 Hub 方法。
错误处理: SignalR 提供了错误处理机制,可以捕获 Hub 方法中的异常,并将错误信息发送给客户端。
诊断: SignalR 提供了诊断功能,可以跟踪连接、消息和传输的性能。
扩展性: SignalR 可以扩展以支持大量的并发连接,例如使用 Redis Backplane。
聊天应用程序: SignalR 可以用于构建实时聊天应用程序,允许用户实时发送和接收消息。
在线游戏: SignalR 可以用于构建在线游戏,允许玩家实时互动。
监控仪表板: SignalR 可以用于构建监控仪表板,实时显示服务器和应用程序的性能指标。
协作应用程序: SignalR 可以用于构建协作应用程序,允许多个用户同时编辑文档或进行其他协作活动。
通知系统: SignalR 可以用于构建实时通知系统,例如在社交媒体应用中显示新消息或好友请求。
总而言之,SignalR 是一个功能强大的库,可以简化在 ASP.NET 应用程序中构建实时 Web 功能的过程。通过抽象底层传输细节并提供易于使用的 API,SignalR 使开发人员能够专注于构建出色的实时用户体验。