using System.Text; using MessagePack; using Microsoft.Extensions.Hosting; using NetMQ; using NetMQ.Monitoring; // ★ 1. 必须引用 Monitoring 命名空间 using NetMQ.Sockets; using SHH.CameraSdk; using SHH.Contracts; namespace SHH.CameraService; public class CommandClientWorker : BackgroundService { private readonly ServiceConfig _config; private readonly CommandDispatcher _dispatcher; private readonly InterceptorPipeline _pipeline; // 管理多个 Socket private readonly List _sockets = new(); // ★ 2. 新增:保存 Monitor 列表,防止被 GC 回收 private readonly List _monitors = new(); private NetMQPoller? _poller; public CommandClientWorker( ServiceConfig config, CommandDispatcher dispatcher, InterceptorPipeline pipeline) { _config = config; _dispatcher = dispatcher; _pipeline = pipeline; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await Task.Yield(); if (!_config.ShouldConnect || _config.CommandEndpoints.Count == 0) return; _poller = new NetMQPoller(); // ------------------------------------------------------------- // 核心修改区:建立连接并挂载监控器 // ------------------------------------------------------------- foreach (var ep in _config.CommandEndpoints) { try { var socket = new DealerSocket(); socket.Options.Identity = Encoding.UTF8.GetBytes(_config.AppId); var monitorUrl = $"inproc://monitor_{Guid.NewGuid():N}"; var monitor = new NetMQMonitor(socket, monitorUrl, SocketEvents.Connected); monitor.Connected += async (s, args) => { Console.WriteLine($"[指令] 网络连接建立: {ep.Uri} -> 正在补发注册包..."); await SendRegisterAsync(socket); }; // ★★★ 修正点:使用 AttachToPoller 代替 Add ★★★ // 错误写法: _poller.Add(monitor); monitor.AttachToPoller(_poller); // 依然需要保存引用,防止被 GC 回收 _monitors.Add(monitor); socket.Connect(ep.Uri); socket.ReceiveReady += OnSocketReceiveReady; _sockets.Add(socket); _poller.Add(socket); Console.WriteLine($"[指令] 通道初始化完成: {ep.Uri} (带自动重连监控)"); } catch (Exception ex) { Console.WriteLine($"[指令] 连接初始化异常: {ex.Message}"); } } if (_sockets.Count == 0) return; // ================================================================= // 6. 绑定 ACK 逻辑 (保持不变) // ================================================================= _dispatcher.OnResponseReady += async (result) => { try { byte[] resultBytes = MessagePackSerializer.Serialize(result); var ctx = await _pipeline.ExecuteSendAsync(ProtocolHeaders.CommandResult, resultBytes); if (ctx != null) { foreach (var socket in _sockets) { socket.SendMoreFrame(ctx.Protocol).SendFrame(ctx.Data); } Console.WriteLine($"[指令] ACK 已广播 (ID: {result.RequestId})"); } } catch (Exception ex) { Console.WriteLine($"[ACK] 发送失败: {ex.Message}"); } }; // ================================================================= // 7. 启动 Poller // ================================================================= // 注意:我们不需要手动发第一次注册包了, // 因为 Poller 启动后,底层 TCP 会建立连接,从而触发 monitor.Connected 事件, // 事件里会自动发送注册包。这就是“自动档”的好处。 _poller.RunAsync(); // 阻塞直到取消 while (!stoppingToken.IsCancellationRequested) { await Task.Delay(1000, stoppingToken); } // 清理 _poller.Stop(); _poller.Dispose(); foreach (var m in _monitors) m.Dispose(); // 释放监控器 foreach (var s in _sockets) s.Dispose(); } // ================================================================= // ★ 8. 抽离出的注册包发送逻辑 (供 Monitor 调用) // ================================================================= private async Task SendRegisterAsync(DealerSocket targetSocket) { try { var registerPayload = new RegisterPayload { Protocol = ProtocolHeaders.ServerRegister, InstanceId = _config.AppId, ProcessId = Environment.ProcessId, Version = "1.0.0", ServerIp = "127.0.0.1", // 建议优化:获取本机真实IP WebApiPort = _config.BasePort, StartTime = DateTime.Now }; byte[] regData = MessagePackSerializer.Serialize(registerPayload); // 执行拦截器 var ctx = await _pipeline.ExecuteSendAsync(ProtocolHeaders.ServerRegister, regData); if (ctx != null) { // 直接向触发事件的那个 Socket 发送 // DealerSocket 允许在连接未完全就绪时 Send,它会缓存直到网络通畅 targetSocket.SendMoreFrame(ctx.Protocol).SendFrame(ctx.Data); // Console.WriteLine($"[指令] 身份注册包已推入队列: {targetSocket.Options.Identity}"); } } catch (Exception ex) { Console.WriteLine($"[指令] 注册包发送失败: {ex.Message}"); } } private async void OnSocketReceiveReady(object? sender, NetMQSocketEventArgs e) { NetMQMessage incomingMsg = new NetMQMessage(); if (e.Socket.TryReceiveMultipartMessage(ref incomingMsg)) { if (incomingMsg.FrameCount >= 2) { try { string rawProtocol = incomingMsg[0].ConvertToString(); byte[] rawData = incomingMsg[1].ToByteArray(); var ctx = await _pipeline.ExecuteReceiveAsync(rawProtocol, rawData); if (ctx != null) { await _dispatcher.DispatchAsync(ctx.Protocol, ctx.Data); } } catch (Exception ex) { Console.WriteLine($"[指令] 处理异常: {ex.Message}"); } } } } }