116 lines
3.5 KiB
C#
116 lines
3.5 KiB
C#
|
|
using Core.WcfProtocol;
|
|||
|
|
using System.Collections.Concurrent;
|
|||
|
|
|
|||
|
|
namespace SHH.MjpegPlayer;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 服务器会话集合 (线程安全重构版)
|
|||
|
|
/// </summary>
|
|||
|
|
public class MjpegSessions
|
|||
|
|
{
|
|||
|
|
// 核心改变:使用字典建立索引,Key = DeviceId#TypeCode
|
|||
|
|
// 这样可以将查找特定摄像头的复杂度从 O(N) 降低到 O(1)
|
|||
|
|
private readonly ConcurrentDictionary<string, List<MjpegSession>> _sessionMap
|
|||
|
|
= new ConcurrentDictionary<string, List<MjpegSession>>();
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 构造函数
|
|||
|
|
/// </summary>
|
|||
|
|
public MjpegSessions()
|
|||
|
|
{
|
|||
|
|
PrismMsg<UploadImageRequest>.Subscribe(ProcUploadImageRequest);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 优化后的图片分发逻辑 (解决 O(N) 和 线程安全问题)
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="req"></param>
|
|||
|
|
public void ProcUploadImageRequest(UploadImageRequest req)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
string key = $"{req.Id}#{req.Type}";
|
|||
|
|
|
|||
|
|
// 1. 更新通道信息 (ImageChannels 现已是线程安全的)
|
|||
|
|
var chn = MjpegStatics.ImageChannels.Do(req, key);
|
|||
|
|
|
|||
|
|
// 2. O(1) 快速查找关注该流的会话列表
|
|||
|
|
if (_sessionMap.TryGetValue(key, out var targetSessions))
|
|||
|
|
{
|
|||
|
|
// 必须加锁,防止遍历时 List 被其他线程(如 AddSession/RemoveSession) 修改
|
|||
|
|
lock (targetSessions)
|
|||
|
|
{
|
|||
|
|
// 倒序遍历,方便在需要时移除失效会话
|
|||
|
|
for (var i = targetSessions.Count - 1; i >= 0; i--)
|
|||
|
|
{
|
|||
|
|
var session = targetSessions[i];
|
|||
|
|
session.DoImageProc(req);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (chn != null) chn.IsPlaying = targetSessions.Count > 0;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
if (chn != null) chn.IsPlaying = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
//Logs.LogWarning<MjpegSessions>(ex.Message, ex.StackTrace);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 添加会话
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="session"></param>
|
|||
|
|
public void AddSession(MjpegSession session)
|
|||
|
|
{
|
|||
|
|
if (session?.Info?.Key == null) return;
|
|||
|
|
|
|||
|
|
// 使用 GetOrAdd 确保线程安全地获取或创建 List
|
|||
|
|
var list = _sessionMap.GetOrAdd(session.Info.Key, _ => new List<MjpegSession>());
|
|||
|
|
|
|||
|
|
lock (list)
|
|||
|
|
{
|
|||
|
|
list.Add(session);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 移除会话
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="session"></param>
|
|||
|
|
public void RemoveSession(MjpegSession session)
|
|||
|
|
{
|
|||
|
|
if (session?.Info?.Key == null) return;
|
|||
|
|
|
|||
|
|
if (_sessionMap.TryGetValue(session.Info.Key, out var list))
|
|||
|
|
{
|
|||
|
|
lock (list)
|
|||
|
|
{
|
|||
|
|
list.Remove(session);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取当前所有会话信息的快照 (用于 HTTP API 统计与展示)
|
|||
|
|
/// [新增] 此方法替代旧版直接访问 Sessions 列表,防止 HTTP 线程与 MJPEG 线程发生冲突
|
|||
|
|
/// </summary>
|
|||
|
|
public List<SessionInfo> GetAllSessionInfos()
|
|||
|
|
{
|
|||
|
|
var result = new List<SessionInfo>();
|
|||
|
|
// 遍历字典,线程安全地收集所有 Info
|
|||
|
|
foreach (var kvp in _sessionMap)
|
|||
|
|
{
|
|||
|
|
// 对内部 List 加锁,确保复制过程不被打断
|
|||
|
|
lock (kvp.Value)
|
|||
|
|
{
|
|||
|
|
result.AddRange(kvp.Value.Select(s => s.Info));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
}
|