ASP.NET 使用多个集线器实例的核心 SignalR

ASP.NET Core SignalR Using Multiple Hub Instances

我将 Blazor WebAssembly 与 .NET 5 一起使用,并且我有一个 MultipleCameraLive 组件,其中嵌入了 4 个 SingleCameraLive 组件。每个组件代表来自给定相机的帧流。 通过单击属于给定相机的帧,用户将跳转到一个页面(我们称之为 SingleCameraFocus),其中仅显示来自该相机的流。

一旦从相机获取图像(服务器启动的通信),服务器就会通过 SignalR 发送图像。

现在,出于模块化原因,我认为最好的方法是拥有一个集线器 class 并拥有它的“多个实例”。换句话说,在服务器中我会有这样的东西

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        [...]

        app.UseEndpoints(endpoints => {
            endpoints.MapRazorPages();
            endpoints.MapControllers();
            endpoints.MapFallbackToFile("index.html");
            endpoints.MapHub<CameraLiveModeHub>("/cameraLiveHub_0");
            endpoints.MapHub<CameraLiveModeHub>("/cameraLiveHub_1");
            endpoints.MapHub<CameraLiveModeHub>("/cameraLiveHub_2");
            endpoints.MapHub<CameraLiveModeHub>("/cameraLiveHub_3");
        });

        CameraLiveModeHub.Context = app.ApplicationServices.GetService<IHubContext<CameraLiveModeHub>>();

        [...]

客户端没有问题,因为我可以使用上面的 Uri 连接到给定的集线器。但是,当我通过集线器上下文从服务器发送消息时,我没有这种可能性。 事实上,据我所知,它不像有 4 个集线器:只有一个,有四个 Uri“指向”那个集线器。事实上here你可以阅读

Hubs are transient: Don't store state in a property on the hub class. Every hub method call is executed on a new hub instance.

这似乎表明没有办法完成我想要的。如果我能够完成上述模块化设计,我将拥有以下优势:为了设计 SingleCameraFocus 组件,我只需要重新使用 SingleCameraLive 组件,传入正确的参数(例如中心 URI)。

因此,另一种方法是为 MultipleCameraLive 组件设置一个集线器,并且每次它从服务器接收到一个帧时,它都需要充当“多路复用器”,将帧传递给正确的子组件。对于 SingleCameraFocus 组件,我需要一个单独的 Hub。此外,由于有 4 个摄像头,我可能需要 4 个集线器,4 个不同的 类 具有几乎完全相同的代码。这是因为客户端应该只从单个摄像机接收帧,即从单个集线器接收帧,因为集线器无法选择将数据发送到哪个客户端(如果通信是服务器启动的,就像在这种情况下)。

您认为解决此问题的最佳方法是什么?

这是一个示例:

    public class MyHub : Hub
    {
        private new static readonly ConcurrentDictionary<string, IList<string>> Groups = new ConcurrentDictionary<string, IList<string>>(
            new Dictionary<string, IList<string>>()
            {
                { "group_1", new List<string>() },
                { "group_2", new List<string>() },
                { "group_3", new List<string>() },
                { "group_4", new List<string>() }
            }
        );

        public bool Join(string groupName)
        {
            var connId = Context.ConnectionId;
            // Check if I already belong to this group
            if(Groups.TryGetValue(groupName, out var groupClients))
            {
                if (groupClients.Contains(connId) == false)
                    groupClients.Add(connId);

                return true;
            }
            return false;
        }

        public async Task Send(string groupName, string message)
        {
            var connId = Context.ConnectionId;
            // Check if I belong to this group
            if(Groups.TryGetValue(groupName, out var groupClients))
            {
                if(groupClients.Contains(Context.ConnectionId))
                {
                    await Clients.Users((IReadOnlyList<string>)groupClients.Where(c => c != connId)).SendAsync(message);
                }
            }
        }

        public void Disconnect()
        {
            var myGroups = GetMyGroups();

            foreach(var groupName in myGroups)
                if (Groups.TryGetValue(groupName, out var groupClients))
                    groupClients.Remove(Context.ConnectionId);
        }

        // In case you can belong to many groups at the same time
        private IList<string> GetMyGroups()
        {
            var connId = Context.ConnectionId;
            var output = new List<string>();

            foreach(var item in Groups)
            {
                if (item.Value.Contains(connId))
                    output.Add(item.Key);
            }
            return output;
        }
    }