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;
}
}
我将 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;
}
}