Files
Ayay/SHH.CameraService/Core/CmdClients/CommandClientWorker.cs

194 lines
6.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<DealerSocket> _sockets = new();
// ★ 2. 新增:保存 Monitor 列表,防止被 GC 回收
private readonly List<NetMQMonitor> _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}");
}
}
}
}
}