❤️ 关注 Furion 微信公众号有惊喜哦!
Skip to main content

24. 即时通讯

24.1 什么是即时通讯

即时通讯(Instant messaging,简称 IM)通常是指互联网上用以进行实时通讯的系统,允许两人或多人使用网络即时的传递文字信息、文档、语音与视频交流。

即时通讯不同于 E-mail 在于它的交谈是实时的。大部分的即时通讯服务提供了状态信息的特性 ── 显示联络人名单,联络人是否在线上与能否与联络人交谈。

在互联网上目前使用较广的即时通讯服务包括 Windows Live Messenger、AOL Instant Messenger、Skype、Yahoo! Messenger、NET Messenger Service、Jabber、ICQ 与 QQ 等。

24.2 即时通讯应用场景

即时通讯应用场景非常广泛,需要实时交互消息的都需要。如:

  • 聊天工具:QQ、WeChat、在线客服等
  • 手游网游:王者荣耀、魔兽等
  • 网络直播:腾讯课堂、抖音直播等
  • 订单推送:美团、餐饮下单系统等
  • 协同办公:公司内部文件分享、工作安排、在线会议等。

以上只是列举了比较常用的应用场景,但即时通讯的作用远不止于此。

文档紧急编写中,可以先看官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/signalr/introduction?view=aspnetcore-5.0

24.3 关于 SignalR

即时通讯技术实现是复杂且过于底层化,所以微软为了简化即时通讯应用程序,开发出了一个强大且简易使用的通信库:SignalR,通过该库我们可以轻松实现类似 QQ、微信这类 IM 聊天工具,也能快速实现消息推送、订单推送这样的系统。

24.3.1 微软官方介绍

ASP.NET Core SignalR 是一种开放源代码库,可简化将实时 web 功能添加到应用程序的功能。 实时 web 功能使服务器端代码可以立即将内容推送到客户端。

适用于 SignalR :

  • 需要从服务器进行高频率更新的应用。 示例包括游戏、社交网络、投票、拍卖、地图和 GPS 应用。
  • 仪表板和监视应用。 示例包括公司仪表板、即时销售更新或旅行警报。
  • 协作应用。 协作应用的示例包括白板应用和团队会议软件。
  • 需要通知的应用。 社交网络、电子邮件、聊天、游戏、旅行警报和很多其他应用都需使用通知。

目前 SignalR 已经内置在 .NET 5 SDK 中。同时 SignalR 支持 Web、App、Console、Desktop 等多个应用平台。

24.4 注册 SignalR 服务

Furion 框架中,任何服务功能都需要先注册后再使用,SignalR 也不例外。只需要在 Startup.cs 中添加注册即可:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Furion.Web.Core
{
public sealed class Startup : AppStartup
{
public void ConfigureServices(IServiceCollection services)
{
// 其他代码...

// 添加即时通讯
services.AddSignalR();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 其他代码...

app.UseEndpoints(endpoints =>
{
// 注册集线器
endpoints.MapHubs();

endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}

24.5 SignalR 长连接和集线器

SignalR 包含两种用于在客户端和服务器之间进行通信的模型:持久性连接集线器 中心。

24.5.1 持久性连接

连接表示用于发送单接收方、分组或广播消息的简单终结点。 持久性连接 (在 .NET 代码中由 PersistentConnection 类表示,在 ASP.NET Core SignalR 中 ,PersistentConnection 类已被删除。) 使开发人员能够直接访问 SignalR 公开的低级别通信协议。 使用基于连接的 Api (如 Windows Communication Foundation)的开发人员将对使用连接通信模型非常熟悉。

24.5.2 集线器

集线器是一种基于连接 API 构建的更高级别管道,它允许客户端和服务器直接调用方法SignalR 就像魔术一样处理跨机器边界的调度,使客户端能够像本地方法一样轻松地调用服务器上的方法,反之亦然。 如果开发人员已使用远程调用 (如 .NET 远程处理),则将对使用中心通信模型非常熟悉。 使用集线器还可以将强类型参数传递给方法,从而启用模型绑定。

小知识

想了解更多关于 持久性连接集线器中心 可查阅 SignalR 官方文档

24.6 集线器 Hub 定义

**在本章节中主要推荐使用集线器通信模型方式。**这里主要说明 Hub 定义,如果无法理解该通信模型的作用也没关系,接下来的例子会带大家慢慢熟悉并使用。

24.6.1 两种定义方式

定义集线器只需要继承 HubHub<TStrongType> 泛型基类即可,如:

  • Hub 方式
using Furion.InstantMessaging;
using Microsoft.AspNetCore.SignalR;

namespace Furion.Core
{
/// <summary>
/// 聊天集线器
/// </summary>
public class ChatHub : Hub
{
// 定义一个方法供客户端调用
public Task SendMessage(string user, string message)
{
// 触发客户端定义监听的方法
return Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
}
  • Hub<TStrongType> 类型方式
public interface IChatClient
{
Task ReceiveMessage(string user, string message);
}
public class StronglyTypedChatHub : Hub<IChatClient>
{
// 定义一个方法供客户端调用
public async Task SendMessage(string user, string message)
{
// 触发客户端定义监听的方法
await Clients.All.ReceiveMessage(user, message);
}
}

通过使用 Hub<IChatClient> 可以对客户端方法进行编译时检查。 这可以防止由于使用神奇字符串而导致的问题,因为 Hub<T> 只能提供对在接口中定义的方法的访问。

24.6.2 [MapHub] 配置连接地址

SignalR 库中要求每一个公开的集线器都需要配置客户端连接地址,所以,Furion 框架提供了更加 [MapHub] 配置,如:

using Furion.InstantMessaging;
using Microsoft.AspNetCore.SignalR;
using System;
using System.Threading.Tasks;

namespace Furion.Core
{
/// <summary>
/// 聊天集线器
/// </summary>
[MapHub("/hubs/chathub")]
public class ChatHub : Hub
{
// ...
}
}
SignalR 原生配置方式

Furion 中推荐使用 [MapHub] 方式配置集线器客户端连接地址,当然也可以使用 SignalR 提供的方式,如在 Startup.cs 配置:

public sealed class Startup : AppStartup
{
// 其他代码
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 其他代码...
app.UseEndpoints(endpoints =>
{
// 注册集线器
endpoints.MapHub<ChatHub>("/hubs/chathub");
});
}
}

24.6.3 Hub 注册更多配置

有些时候,我们需要注册 Hub 时配置更多参数,比如权限、跨域等,这时只需要在 Hub 派生类中编写以下静态方法即可:

using Furion.InstantMessaging;
using Microsoft.AspNetCore.SignalR;
using System;
using System.Threading.Tasks;

namespace Furion.Core
{
[MapHub("/hubs/chathub")]
public class ChatHub : Hub
{
// 其他代码

public static void HttpConnectionDispatcherOptionsSettings(HttpConnectionDispatcherOptions options)
{
// 配置
}

public static void HubEndpointConventionBuilderSettings(HubEndpointConventionBuilder Builder)
{
// 配置
}
}
}

以上配置等价于 SignalRStartup.cs 中的配置:

app.UseEndpoints(endpoints =>
{
var builder = endpoints.MapHub<ChatHub>("/hubs/chathub", options =>
{
// 配置
});
});

24.7 获取 Hub 实例方式

SignalR 提供了几种方式进行获取 Hub 实例。

24.7.1 IHubContext 注入方式

IHubContext 默认注册为单例模式,可在任何地方直接获取实例。

public class HomeController : Controller
{
private readonly IHubContext<NotificationHub> _hubContext;

public HomeController(IHubContext<NotificationHub> hubContext)
{
_hubContext = hubContext;
}

public async Task<IActionResult> Index()
{
await _hubContext.Clients.All.SendAsync("Notify", $"Home page loaded at: {DateTime.Now}");
return View();
}
}

24.7.2 HttpContext 解析方式

 var hubContext = context.RequestServices
.GetRequiredService<IHubContext<ChatHub>>();

24.7.3 IHost 中解析方式

public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
var hubContext = host.Services.GetService(typeof(IHubContext<ChatHub>));
host.Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => {
webBuilder.UseStartup<Startup>();
});
}

24.7.4 强类型 IHubContext 注入方式

默认情况下,IHubContext 非泛型实例返回的是 dynamic 动态类型对象,该类型对象无法获得编译期语法检查和 IDE 智能提示,所以我们可以传入一个和自定义 Hub 一样的方法签名接口,如:

public class ChatController : Controller
{
public IHubContext<ChatHub, IChatClient> _strongChatHubContext { get; }

public ChatController(IHubContext<ChatHub, IChatClient> chatHubContext)
{
_strongChatHubContext = chatHubContext;
}

public async Task SendMessage(string user, string message)
{
await _strongChatHubContext.Clients.All.ReceiveMessage(user, message);
}
}

24.7.5 IHubContext 泛型转换

正常情况下,我们获取的是 IHubContext<> 的实例,但在一些反射场景下,可以将 IHubContext<> 强制转换成 IHubContext 从而更易于操作,如:

var myHubContext = context.RequestServices
.GetRequiredService<IHubContext<MyHub>>();
var myOtherHubContext = context.RequestServices
.GetRequiredService<IHubContext<MyOtherHub>>();

await CommonHubContextMethod((IHubContext)myHubContext);
await CommonHubContextMethod((IHubContext)myOtherHubContext);

24.8 服务端和客户端双工通信

24.8.1 触发所有客户端代码

Clients.All.客户端方法(参数);

24.8.2 触发调用者客户端

Clients.Caller.客户端方法(参数);

24.8.3 触发除了调用者以外的客户端

Clients.Others.客户端方法(参数);

24.8.4 触发特定用户客户端

Clients.User("用户").客户端方法(参数);

24.8.5 触发多个用户客户端

Clients.Users("用户","用户2",...).客户端方法(参数);

24.8.6 触发分组内客户端

Clients.Group("分组").客户端方法(参数);

24.8.7 触发多个分组客户端

Clients.Groups("分组","分组2",...).客户端方法(参数);

24.8.8 触发分组外的客户端

Clients.GroupExcept("分组").客户端方法(参数);

24.9 自定义用户唯一标识

默认情况下 SignalR 会为每一个链接创建 ConnectionId,但是这个 ConnectionId 并没有和我们系统的用户绑关联起来,所以需要采用自定义 ConnectionId,如:

public class YourUserIdProvider : IUserIdProvider
{
public virtual string GetUserId(HubConnectionContext connection)
{
// 你如何获取 UserId,可以通过 connection.User 获取 JWT 授权的用户
}
}

然后在 Startup.cs 中注册即可:

builder.Services.AddSingleton<IUserIdProvider, YourUserIdProvider>();

之后就可以通过自定义 UserId 发送消息:

Clients.User(userId).客户端方法(参数);

查看更多文档 https://docs.microsoft.com/zh-cn/aspnet/core/signalr/authn-and-authz?view=aspnetcore-6.0#use-claims-to-customize-identity-handling

24.10 分组管理

整理中...

24.11 各个客户端连接 API

24.11.1 JavaScript 客户端

整理中...

24.11.2 TypeScript 客户端

vue3.2+ 中使用

  1. 安装微软的 signalr typescript 客户端包,主要用于调用服务端方法,如( Hub 中的 SendMessage 方法):
npm i @microsoft/signalr @types/node
  1. 示例代码
import { HubConnectionBuilder } from "@microsoft/signalr";

<script setup lang="ts">
import { HubConnectionBuilder } from "@microsoft/signalr";
import { ref } from "vue";

const messages = ref('');
const reciveMessage = (msg: string) => {
console.log("msg", msg);
}

//初始化signalr HubConnection对象
const connection = new HubConnectionBuilder()
.withUrl("<你的signalr连接地址>")//https://localhost:7260/chatHub
.build();

//启动连接并发送消息测试
connection.start()
.then(() => connection.send("SendMessage", "Hello"));

//注册web端方法以供后端调用
connection.on("ReciveMessage", reciveMessage);

const sendMsg = async () => {
console.log(messages.value);
await connection.send("SendMessage", messages.value).catch(function (err) {
console.log(err);
});
}
</script>

参考文档

24.11.3 .NET 客户端

整理中...

24.11.4 Java 客户端

整理中...

24.12 常见例子

24.12.1 实现消息广播、推送

整理中...

24.12.2 实现聊天功能

整理中...

24.12.3 实现 你画我来猜

整理中...

24.13 反馈与建议

与我们交流

给 Furion 提 Issue


了解更多

想了解更多 SignalR 知识可查阅 SignalR 官方文档ASP.NET Core SignalR 章节。