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 两种定义方式
定义集线器只需要继承 Hub
或 Hub<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)
{
// 配置
}
}
}
以上配置等价于 SignalR
在 Startup.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",...).客户端方法(参数);