using NetMQ; using NetMQ.Sockets; using System.Diagnostics; // 用于 Debug 输出 namespace SHH.CameraDashboard.Services; public class StreamReceiverService : IDisposable { // 单例模式 public static StreamReceiverService Instance { get; } = new StreamReceiverService(); public event Action? OnFrameReceived; private SubscriberSocket? _subSocket; private Task? _receiveTask; // 【修复1】不要在这里初始化,改为在 Start 中初始化 private CancellationTokenSource? _cts; public int ListenPort { get; private set; } // 增加运行状态标记 public bool IsRunning => _receiveTask != null && !_receiveTask.IsCompleted; private StreamReceiverService() { } public void Start(int port = 6000) { // 1. 防止重复启动 if (IsRunning) return; ListenPort = port; // 【修复1】每次启动时创建新的 TokenSource _cts = new CancellationTokenSource(); try { // 2. 初始化 Socket _subSocket = new SubscriberSocket(); // 【优化】设置高水位限制 (HWM) // 如果 UI 处理不过来,积压超过 1000 帧直接丢弃,防止内存爆炸 _subSocket.Options.ReceiveHighWatermark = 1000; string bindAddr = $"tcp://*:{ListenPort}"; _subSocket.Bind(bindAddr); _subSocket.Subscribe(""); Console.WriteLine($"[Dashboard] 视频流接收服务启动: {bindAddr}"); } catch (Exception ex) { Console.WriteLine($"[Dashboard] 致命错误 - 端口绑定失败: {ex.Message}"); // 清理资源 _subSocket?.Dispose(); _subSocket = null; // 【修复4】抛出异常让上层知道启动失败了 throw new Exception($"端口 {port} 绑定失败,可能被占用。", ex); } // 3. 启动任务 _receiveTask = Task.Run(ReceiveLoop, _cts.Token); } private void ReceiveLoop() { var token = _cts?.Token ?? CancellationToken.None; while (!token.IsCancellationRequested) { try { // 【修复2】线程安全检查 if (_subSocket == null) break; // 接收 Topic if (!_subSocket.TryReceiveFrameString(TimeSpan.FromMilliseconds(500), out string cameraId)) continue; // 接收 Payload if (!_subSocket.TryReceiveFrameBytes(TimeSpan.FromMilliseconds(100), out byte[] jpgBytes)) continue; // 触发事件 OnFrameReceived?.Invoke(cameraId, jpgBytes); } catch (ObjectDisposedException) { // 【修复2】这是正常的退出流程(Socket被Dispose了),优雅退出循环 break; } catch (Exception ex) { // 记录日志,但不崩溃 Debug.WriteLine($"[ReceiverLoop Error] {ex.Message}"); } } Console.WriteLine("[Dashboard] 接收循环已停止"); } /// /// 停止服务(支持停止后重新 Start) /// public void Stop() { // 1. 发出取消信号 if (_cts != null && !_cts.IsCancellationRequested) { _cts.Cancel(); } // 2. 销毁 Socket (这会触发 ReceiveLoop 中的 ObjectDisposedException 从而退出循环) if (_subSocket != null) { try { _subSocket.Dispose(); } catch { } _subSocket = null; } // 3. 清理 Token _cts?.Dispose(); _cts = null; _receiveTask = null; } public void Dispose() { Stop(); } }