139 lines
5.3 KiB
C#
139 lines
5.3 KiB
C#
|
|
using Microsoft.Extensions.Hosting;
|
|||
|
|
using NetMQ;
|
|||
|
|
using NetMQ.Sockets;
|
|||
|
|
using Newtonsoft.Json;
|
|||
|
|
using SHH.CameraSdk;
|
|||
|
|
using System.Text;
|
|||
|
|
|
|||
|
|
namespace SHH.CameraService;
|
|||
|
|
|
|||
|
|
public class CommandClientWorker : BackgroundService
|
|||
|
|
{
|
|||
|
|
private readonly ServiceConfig _config;
|
|||
|
|
private readonly CommandDispatcher _dispatcher; // 注入分发器
|
|||
|
|
|
|||
|
|
public CommandClientWorker(ServiceConfig config, CommandDispatcher dispatcher)
|
|||
|
|
{
|
|||
|
|
_config = config;
|
|||
|
|
_dispatcher = dispatcher;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|||
|
|
{
|
|||
|
|
// =================================================================
|
|||
|
|
// ★★★ 核心修复:强制让出主线程 ★★★
|
|||
|
|
// 这行代码会让当前的 ExecuteAsync 立即返回一个未完成的 Task 给 Host,
|
|||
|
|
// Host 就会认为 "这个服务启动好了",然后继续去启动 WebAPI。
|
|||
|
|
// 而剩下的代码会被调度到线程池里异步执行,互不干扰。
|
|||
|
|
// =================================================================
|
|||
|
|
await Task.Yield();
|
|||
|
|
|
|||
|
|
// 1. 如果不是主动/混合模式,不需要连接
|
|||
|
|
if (!_config.ShouldConnect) return;
|
|||
|
|
|
|||
|
|
var cmdEndpoints = _config.CommandEndpoints;
|
|||
|
|
if (cmdEndpoints.Count == 0)
|
|||
|
|
{
|
|||
|
|
Console.WriteLine("[指令] 未配置指令通道,跳过注册。");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 初始化 Dealer Socket
|
|||
|
|
using var dealer = new DealerSocket();
|
|||
|
|
|
|||
|
|
// ★★★ 关键:设置身份标识 (Identity) ★★★
|
|||
|
|
// 服务端 (Router) 收到消息时,第一帧就是这个 ID
|
|||
|
|
// 如果不设,ZMQ 会随机生成一个二进制 ID,服务端就不知道你是谁了
|
|||
|
|
string myIdentity = _config.AppId;
|
|||
|
|
dealer.Options.Identity = Encoding.UTF8.GetBytes(myIdentity);
|
|||
|
|
|
|||
|
|
// 3. 连接所有目标 (遍历 ServiceEndpoint 对象)
|
|||
|
|
foreach (var ep in cmdEndpoints)
|
|||
|
|
{
|
|||
|
|
Console.WriteLine($"[指令] 连接控制端: {ep.Uri} [{ep.Description}]");
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
dealer.Connect(ep.Uri);
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Console.WriteLine($"[指令] 连接失败 {ep.Uri}: {ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 1. 获取本机 IP (简单的获取方式,用于上报给 Dashboard)
|
|||
|
|
string localIp = "127.0.0.1";
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// 简单获取首个非回环 IP,生产环境建议用更严谨的帮助类
|
|||
|
|
var host = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName());
|
|||
|
|
localIp = host.AddressList.FirstOrDefault(ip =>
|
|||
|
|
ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)?.ToString() ?? "127.0.0.1";
|
|||
|
|
}
|
|||
|
|
catch { }
|
|||
|
|
|
|||
|
|
// 4. 构建注册/登录包
|
|||
|
|
var registerPayload = new
|
|||
|
|
{
|
|||
|
|
Action = "Register",
|
|||
|
|
Payload = new
|
|||
|
|
{
|
|||
|
|
// 1. AppId (身份)
|
|||
|
|
Id = _config.AppId,
|
|||
|
|
|
|||
|
|
// 2. Version (程序集版本)
|
|||
|
|
Version = System.Reflection.Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "1.0.0",
|
|||
|
|
|
|||
|
|
// 3. 进程 ID (用于远程监控)
|
|||
|
|
Pid = Environment.ProcessId,
|
|||
|
|
|
|||
|
|
// 4. 关键端口信息
|
|||
|
|
// 告诉 Dashboard:如果你想调我的 REST API,请访问这个端口
|
|||
|
|
WebPort = _config.BasePort,
|
|||
|
|
|
|||
|
|
// 如果您有本地绑定的 ZMQ 端口也可以在这里上报
|
|||
|
|
// VideoPort = _config.BasePort + 1,
|
|||
|
|
|
|||
|
|
// 基础网络信息
|
|||
|
|
Ip = localIp,
|
|||
|
|
|
|||
|
|
// 附带信息:我是要把视频推给谁 (供 Dashboard 调试用)
|
|||
|
|
TargetVideoNodes = _config.VideoEndpoints.Select(e => e.Uri).ToList()
|
|||
|
|
},
|
|||
|
|
Time = DateTime.Now
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
string json = JsonConvert.SerializeObject(registerPayload);
|
|||
|
|
|
|||
|
|
// 5. 发送注册包
|
|||
|
|
// Dealer 连接建立是异步的,所以这里直接发,ZMQ 会在底层连接成功后自动把消息推出去
|
|||
|
|
// 为了保险,对于多个 Endpoint,Dealer 默认是负载均衡发送的(轮询)。
|
|||
|
|
// 如果想让每个 Endpoint 都收到注册包,这在 Dealer 模式下稍微有点特殊。
|
|||
|
|
// 但通常我们只需要发一次,只要有一个 Dashboard 收到并建立会话即可。
|
|||
|
|
// 或者简单粗暴:循环发送几次,确保覆盖。
|
|||
|
|
|
|||
|
|
Console.WriteLine($"[指令] 发送注册包: {json}");
|
|||
|
|
dealer.SendFrame(json);
|
|||
|
|
|
|||
|
|
// 6. 进入监听循环 (等待 ACK 或 指令)
|
|||
|
|
// 进入监听循环
|
|||
|
|
while (!stoppingToken.IsCancellationRequested)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
if (dealer.TryReceiveFrameString(TimeSpan.FromMilliseconds(500), out string msg))
|
|||
|
|
{
|
|||
|
|
Console.WriteLine($"[指令] 收到消息: {msg}");
|
|||
|
|
|
|||
|
|
// ★★★ 核心变化:直接扔给分发器 ★★★
|
|||
|
|
// 无论未来加多少指令,这里都不用改代码
|
|||
|
|
await _dispatcher.DispatchAsync(msg);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Console.WriteLine($"[指令] 异常: {ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|