.NET Core SignalR Redis底板详解(二)
接上文。
上文说到Clients.All.SendAsync实际上是调用AllClientProxy的SendCoreAsync方法。其实主要是调用IClientProxy的SendCoreAsync。在上文说到的HubClients类里。就有很多IClientProxy的实现类。比如刚刚说到的All其实是AllClientProxy对象。
public IClientProxy AllExcept(IReadOnlyList<string> excludedConnectionIds) { return new AllClientsExceptProxy<THub>(_lifetimeManager, excludedConnectionIds); } public IClientProxy Client(string connectionId) { return new SingleClientProxy<THub>(_lifetimeManager, connectionId); } public IClientProxy Group(string groupName) { return new GroupProxy<THub>(_lifetimeManager, groupName); } public IClientProxy GroupExcept(string groupName, IReadOnlyList<string> excludedConnectionIds) { return new GroupExceptProxy<THub>(_lifetimeManager, groupName, excludedConnectionIds); } public IClientProxy Clients(IReadOnlyList<string> connectionIds) { return new MultipleClientProxy<THub>(_lifetimeManager, connectionIds); } public IClientProxy Groups(IReadOnlyList<string> groupNames) { return new MultipleGroupProxy<THub>(_lifetimeManager, groupNames); } public IClientProxy User(string userId) { return new UserProxy<THub>(_lifetimeManager, userId); } public IClientProxy Users(IReadOnlyList<string> userIds) { return new MultipleUserProxy<THub>(_lifetimeManager, userIds); }
这些实现类其实里面都是千篇一律的构造函数和SendCoreAsync。真正起作用的还是HubLifetimeManager对象。上文中说了。在AdRedis的时候就已经把RedisHubLifetimeManager注入进去了。所以这里其实真正调用的还是RedisHubLifetimeManager中的SendAllAsync方法。
public override Task SendAllAsync(string methodName, object[] args, CancellationToken cancellationToken = default) { var message = _protocol.WriteInvocation(methodName, args); return PublishAsync(_channels.All, message); }
在WriteInvocation方法中完成广播的发送。(这段代码太晦涩。我没看懂,只是猜个大概)。在PublishAsync实现的是Redis的推送。
那么其他服务器是怎么收到广播的呢?
同样是在RedisHubLifetimeManager中。重写了OnConnectedAsync方法。
public override async Task OnConnectedAsync(HubConnectionContext connection) { await EnsureRedisServerConnection(); var feature = new RedisFeature(); connection.Features.Set<IRedisFeature>(feature); var connectionTask = Task.CompletedTask; var userTask = Task.CompletedTask; _connections.Add(connection); connectionTask = SubscribeToConnection(connection); if (!string.IsNullOrEmpty(connection.UserIdentifier)) { userTask = SubscribeToUser(connection); } await Task.WhenAll(connectionTask, userTask); }
在这里通过SubscribeToConnection方法完成了Redis的订阅。这个OnConnectedAsync方法在HubConnectionHandler中的OnConnectedAsync中被调用。从名字就可以看出来HubConnectionHandler是处理HubConnection的。而HubConnectionHandler的调用则是在我们一开始UseHub的时候。
public static IConnectionBuilder UseHub<THub>(this IConnectionBuilder connectionBuilder) where THub : Hub { var marker = connectionBuilder.ApplicationServices.GetService(typeof(SignalRCoreMarkerService)); if (marker == null) { throw new InvalidOperationException("Unable to find the required services. Please add all the required services by calling " + "'IServiceCollection.AddSignalR' inside the call to 'ConfigureServices(...)' in the application startup code."); } return connectionBuilder.UseConnectionHandler<HubConnectionHandler<THub>>(); }
至此。整个广播流程就讲解完毕了。
这里说一下为什么前言中的方法不好。因为用接口暴露的形式其实是走Http协议。而Redis的订阅与发布是走RESP协议。Http是超文本传输协议在子房间多的情况下会额外用掉很多带宽与IIS的处理资源。而且用Redis的订阅发布模式,Channel的方式与Signalr的Group等其他方式的思路其实是相同的。
最后贴一下微软的官方说明。
设置 ASP.NET Core SignalR 横向扩展 Redis 底板
https://docs.microsoft.com/zh-cn/aspnet/core/signalr/redis-backplane?view=aspnetcore-2.2
教程:ASP.NET Core SignalR 入门