海康摄像头取流示例初始签入
This commit is contained in:
231
SHH.CameraSdk/Core/Resilience/CameraCoordinator.cs
Normal file
231
SHH.CameraSdk/Core/Resilience/CameraCoordinator.cs
Normal file
@@ -0,0 +1,231 @@
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// [调度协调层] 视频自愈调度器 (V3.3.4 流量削峰版)
|
||||
/// 核心职责:监控所有相机设备的运行状态,实现断线自动重连、僵死状态复位,保障视频流稳定
|
||||
/// 核心修复:
|
||||
/// <para>1. [Bug τ] 线程池保护:引入并发节流阀,限制同时重连/探测的任务数,防止线程池饥饿</para>
|
||||
/// </summary>
|
||||
public class CameraCoordinator
|
||||
{
|
||||
#region --- 私有资源与配置 (Private Resources & Configurations) ---
|
||||
|
||||
/// <summary> 已注册的相机设备集合(线程安全,支持并发添加与遍历) </summary>
|
||||
private readonly ConcurrentBag<BaseVideoSource> _cameras = new();
|
||||
|
||||
/// <summary> 全局登录单行道锁 </summary>
|
||||
/// <remarks> 限制同一时刻仅允许一个相机执行登录操作,避免 SDK 登录冲突 </remarks>
|
||||
private readonly SemaphoreSlim _sdkLoginLock = new(1, 1);
|
||||
|
||||
/// <summary> 并发节流阀:限制同时进行探测/重连的任务数 </summary>
|
||||
/// <remarks> 最大并发数 8,避免百路相机同时重连导致 CPU 峰值过高、UI 卡顿 </remarks>
|
||||
private readonly SemaphoreSlim _concurrencyLimiter = new(8);
|
||||
|
||||
/// <summary> 相机流存活判定阈值(秒):超过该时间无帧则判定为流中断 </summary>
|
||||
private const int StreamAliveThresholdSeconds = 5;
|
||||
|
||||
/// <summary> Ping 探测超时时间(毫秒) </summary>
|
||||
private const int PingTimeoutMs = 800;
|
||||
|
||||
/// <summary> TCP 探测超时时间(毫秒) </summary>
|
||||
private const int TcpTimeoutMs = 1000;
|
||||
|
||||
/// <summary> 调度循环间隔(毫秒):每 5 秒执行一次全量设备状态校验 </summary>
|
||||
private const int CoordinationLoopIntervalMs = 5000;
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 相机注册 (Camera Registration) ---
|
||||
|
||||
/// <summary>
|
||||
/// 注册相机设备到调度器
|
||||
/// 功能:将相机纳入全局状态监控与自愈管理
|
||||
/// </summary>
|
||||
/// <param name="camera">待注册的相机设备实例</param>
|
||||
public void Register(BaseVideoSource camera) => _cameras.Add(camera);
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 核心调度循环 (Core Coordination Loop) ---
|
||||
|
||||
/// <summary>
|
||||
/// 启动调度协调循环(长期运行任务)
|
||||
/// 功能:周期性校验所有相机状态,执行自愈逻辑,支持取消
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌:用于终止调度循环</param>
|
||||
public async Task RunCoordinationLoopAsync(CancellationToken token)
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 削峰填谷式调度:通过并发节流阀控制任务并发数
|
||||
var tasks = _cameras.Select(async cam =>
|
||||
{
|
||||
// 申请“重连/探测许可证”,无可用许可时阻塞等待
|
||||
await _concurrencyLimiter.WaitAsync(token).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
// 安全执行状态调和(隔离单个相机的异常)
|
||||
await SafeReconcileAsync(cam, token).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 释放许可,允许其他相机执行任务
|
||||
_concurrencyLimiter.Release();
|
||||
}
|
||||
});
|
||||
|
||||
// 等待所有相机的调和任务完成
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 收到取消信号,退出循环
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 捕获调度层全局异常,避免循环终止
|
||||
Console.WriteLine($"[CoordinatorCritical] 调度循环异常: {ex.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 等待下一个调度周期(支持响应取消)
|
||||
await Task.Delay(CoordinationLoopIntervalMs, token).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 延迟过程中收到取消信号,退出循环
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 安全调和包装 (Safe Reconciliation Wrapper) ---
|
||||
|
||||
/// <summary>
|
||||
/// 安全执行相机状态调和
|
||||
/// 功能:隔离单个相机的异常,避免影响其他相机的调和逻辑
|
||||
/// </summary>
|
||||
/// <param name="cam">待调和的相机设备</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
private async Task SafeReconcileAsync(BaseVideoSource cam, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ReconcileAsync(cam, token).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 吞没单个相机的异常,确保其他相机正常调度
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 状态调和逻辑 (Reconciliation Logic) ---
|
||||
|
||||
/// <summary>
|
||||
/// 相机状态调和(核心自愈逻辑)
|
||||
/// 功能:校验相机物理连接、流状态,执行启动/停止/复位操作,确保状态一致性
|
||||
/// </summary>
|
||||
/// <param name="cam">待调和的相机设备</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
private async Task ReconcileAsync(BaseVideoSource cam, CancellationToken token)
|
||||
{
|
||||
// 1. 计算距离上次收到帧的时间(秒)
|
||||
long nowTick = Environment.TickCount64;
|
||||
double secondsSinceLastFrame = (nowTick - cam.LastFrameTick) / 1000.0;
|
||||
|
||||
// 2. 判定流是否正常:设备在线 + 5秒内有帧
|
||||
bool isFlowing = cam.IsOnline && secondsSinceLastFrame < StreamAliveThresholdSeconds;
|
||||
|
||||
// 3. 判定物理连接是否正常:流正常则直接判定在线;否则执行 Ping+TCP 探测
|
||||
bool isPhysicalOk = isFlowing ? true : await ProbeHardwareAsync(cam).ConfigureAwait(false);
|
||||
|
||||
// 4. 状态调和决策:根据物理状态与设备状态的差异执行对应操作
|
||||
if (isPhysicalOk && !cam.IsOnline && cam.IsRunning)
|
||||
{
|
||||
// 物理在线 + 设备离线 + 需运行 → 执行启动(加登录锁防止冲突)
|
||||
bool lockTaken = false;
|
||||
try
|
||||
{
|
||||
await _sdkLoginLock.WaitAsync(token).ConfigureAwait(false);
|
||||
lockTaken = true;
|
||||
// 双重校验:防止等待锁期间状态已变更
|
||||
if (!cam.IsOnline)
|
||||
{
|
||||
await cam.StartAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken)
|
||||
{
|
||||
_sdkLoginLock.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!isPhysicalOk && cam.IsOnline)
|
||||
{
|
||||
// 物理离线 + 设备在线 → 执行停止
|
||||
await cam.StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
else if (isPhysicalOk && cam.IsOnline && !isFlowing)
|
||||
{
|
||||
// 物理在线 + 设备在线 + 流中断 → 判定为僵死,执行复位
|
||||
Console.WriteLine($"[自愈] 设备 {cam.Id} 僵死({secondsSinceLastFrame:F1}秒无帧),复位中...");
|
||||
await cam.StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 硬件探测 (Hardware Probing) ---
|
||||
|
||||
/// <summary>
|
||||
/// 硬件连接探测:通过 Ping + TCP 双探测判定设备物理可达性
|
||||
/// </summary>
|
||||
/// <param name="cam">待探测的相机设备</param>
|
||||
/// <returns>物理可达返回 true,否则返回 false</returns>
|
||||
private async Task<bool> ProbeHardwareAsync(BaseVideoSource cam)
|
||||
{
|
||||
// 1. 优先执行 Ping 探测(快速判定网络连通性)
|
||||
try
|
||||
{
|
||||
using var ping = new Ping();
|
||||
PingReply reply = await ping.SendPingAsync(cam.Config.IpAddress, PingTimeoutMs).ConfigureAwait(false);
|
||||
if (reply.Status == IPStatus.Success)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ping 探测失败,执行 TCP 探测兜底
|
||||
}
|
||||
|
||||
// 2. TCP 探测:尝试连接设备端口(更精准的服务可达性判定)
|
||||
try
|
||||
{
|
||||
using var tcpClient = new TcpClient();
|
||||
using var cts = new CancellationTokenSource(TcpTimeoutMs);
|
||||
await tcpClient.ConnectAsync(cam.Config.IpAddress, cam.Config.Port, cts.Token).ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// TCP 探测失败,判定为物理不可达
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user