新增 Mjpegplayer 用来播放 Web 流
This commit is contained in:
108
SHH.MjpegPlayer/GrpcImpls/Handlers/GrpcSessionManager.cs
Normal file
108
SHH.MjpegPlayer/GrpcImpls/Handlers/GrpcSessionManager.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using Grpc.Core;
|
||||
using SHH.Contracts.Grpc;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace SHH.MjpegPlayer
|
||||
{
|
||||
/// <summary>
|
||||
/// gRpc 会话管理器
|
||||
/// 职责:专门负责维护、检索和清理所有远程客户端(分析节点)的 gRpc 指令下发物理通道 (Stream)。
|
||||
/// 它是连接“业务逻辑”与“物理传输”的桥梁,确保指令能准确投递到对应的连接流中。
|
||||
/// </summary>
|
||||
public class GrpcSessionManager
|
||||
{
|
||||
#region 单例模式
|
||||
|
||||
/// <summary>
|
||||
/// 获取会话管理器的全局单例实例。
|
||||
/// </summary>
|
||||
public static GrpcSessionManager Instance { get; } = new GrpcSessionManager();
|
||||
|
||||
/// <summary>
|
||||
/// 私有构造函数,防止外部实例化。
|
||||
/// </summary>
|
||||
private GrpcSessionManager() { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 内部存储
|
||||
|
||||
/// <summary>
|
||||
/// 物理流存储字典
|
||||
/// Key: 远程服务实例唯一 ID (InstanceId)
|
||||
/// Value: gRpc 双向流或服务端推送流的写入器句柄 (IServerStreamWriter)
|
||||
/// 使用 ConcurrentDictionary 确保在多客户端并发连接/断开时的线程安全性。
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, IServerStreamWriter<CommandPayloadProto>> _sessionStreams
|
||||
= new ConcurrentDictionary<string, IServerStreamWriter<CommandPayloadProto>>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 公共管理接口
|
||||
|
||||
/// <summary>
|
||||
/// 注册/更新物理物理通道。
|
||||
/// 当客户端调用 OpenCommandChannel 并成功建立 Server Streaming 连接时,由 GatewayService 调用此方法。
|
||||
/// </summary>
|
||||
/// <param name="instanceId">客户端实例唯一标识</param>
|
||||
/// <param name="responseStream">该客户端对应的 gRpc 响应流句柄</param>
|
||||
public void RegisterSession(string instanceId, IServerStreamWriter<CommandPayloadProto> responseStream)
|
||||
{
|
||||
// 1. 参数校验:无效 ID 不予处理
|
||||
if (string.IsNullOrEmpty(instanceId)) return;
|
||||
|
||||
// 2. 登记或覆盖物理流:
|
||||
// 如果客户端异常断开后迅速重连,此处会覆盖旧的流句柄,确保指令始终通过最新的管道下发。
|
||||
_sessionStreams[instanceId] = responseStream;
|
||||
|
||||
// 3. 记录日志:便于运维监控连接状态
|
||||
Console.WriteLine($"[Session] 物理通道就绪通知 -> 节点 ID: {instanceId}, 当前在线总数: {_sessionStreams.Count}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除物理通道。
|
||||
/// 当 gRpc 连接由于网络波动、客户端崩溃或主动关闭而断开时,由 GatewayService 的 finally 块调用。
|
||||
/// </summary>
|
||||
/// <param name="instanceId">要注销的客户端实例 ID</param>
|
||||
public void RemoveSession(string instanceId)
|
||||
{
|
||||
// 1. 参数校验
|
||||
if (string.IsNullOrEmpty(instanceId)) return;
|
||||
|
||||
// 2. 安全移除:若 ID 存在则移除并释放相关内部引用
|
||||
if (_sessionStreams.TryRemove(instanceId, out _))
|
||||
{
|
||||
Console.WriteLine($"[Session] 物理通道移除通知 -> 节点 ID: {instanceId}, 剩余在线总数: {_sessionStreams.Count}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检索目标节点的物理流句柄。
|
||||
/// 供 MessageBus 使用,它是指令下发前定位物理路径的关键步骤。
|
||||
/// </summary>
|
||||
/// <param name="instanceId">目标节点的唯一 ID</param>
|
||||
/// <returns>返回对应的 IServerStreamWriter 实例;若节点不在线则返回 null</returns>
|
||||
public IServerStreamWriter<CommandPayloadProto> GetSession(string instanceId)
|
||||
{
|
||||
// 1. 参数校验
|
||||
if (string.IsNullOrEmpty(instanceId)) return null;
|
||||
|
||||
// 2. 尝试从缓存字典中获取流句柄
|
||||
_sessionStreams.TryGetValue(instanceId, out var stream);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查指定节点是否处于物理连接状态。
|
||||
/// </summary>
|
||||
/// <param name="instanceId">实例 ID</param>
|
||||
/// <returns>True 表示物理通道已建立</returns>
|
||||
public bool IsSessionActive(string instanceId)
|
||||
{
|
||||
return !string.IsNullOrEmpty(instanceId) && _sessionStreams.ContainsKey(instanceId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user