2026-01-07 10:59:03 +08:00
|
|
|
|
using Microsoft.Extensions.Hosting;
|
|
|
|
|
|
using NetMQ;
|
|
|
|
|
|
using NetMQ.Sockets;
|
|
|
|
|
|
|
|
|
|
|
|
namespace SHH.CameraService;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// NetMQ 发送工作者
|
|
|
|
|
|
/// 职责:从指定目标的 VideoDataChannel 读取 Payload,通过 ZeroMQ 发送出去
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class NetMqSenderWorker : BackgroundService
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly StreamTarget _target;
|
|
|
|
|
|
|
|
|
|
|
|
// 构造函数注入特定的目标对象 (由 Program.cs 的工厂方法提供)
|
|
|
|
|
|
public NetMqSenderWorker(StreamTarget target)
|
|
|
|
|
|
{
|
|
|
|
|
|
_target = target;
|
|
|
|
|
|
}
|
|
|
|
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
|
|
|
|
{
|
2026-01-12 18:27:58 +08:00
|
|
|
|
// 增加重启保护
|
|
|
|
|
|
while (!stoppingToken.IsCancellationRequested)
|
2026-01-07 10:59:03 +08:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2026-01-12 18:27:58 +08:00
|
|
|
|
Console.WriteLine($"[NetMqSender] 连接至: {_target.Config.Endpoint}");
|
|
|
|
|
|
|
|
|
|
|
|
using var clientSocket = new PublisherSocket();
|
|
|
|
|
|
clientSocket.Options.SendHighWatermark = 1000;
|
|
|
|
|
|
// 关键:增加 TCP 保活,防止防火墙静默断开长连接
|
|
|
|
|
|
clientSocket.Options.TcpKeepalive = true;
|
|
|
|
|
|
clientSocket.Options.TcpKeepaliveIdle = TimeSpan.FromSeconds(5);
|
2026-01-07 10:59:03 +08:00
|
|
|
|
|
2026-01-12 18:27:58 +08:00
|
|
|
|
clientSocket.Connect(_target.Config.Endpoint);
|
2026-01-07 10:59:03 +08:00
|
|
|
|
|
2026-01-12 18:27:58 +08:00
|
|
|
|
int frameCount = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 使用更稳健的读取方式
|
|
|
|
|
|
await foreach (var payload in _target.Channel.Reader.ReadAllAsync(stoppingToken))
|
2026-01-07 10:59:03 +08:00
|
|
|
|
{
|
2026-01-12 18:27:58 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1. 构造消息 (内部执行了 MessagePack 序列化)
|
|
|
|
|
|
var msg = payload.ToNetMqMessage();
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 发送
|
|
|
|
|
|
bool sent = clientSocket.TrySendMultipartMessage(msg);
|
|
|
|
|
|
|
|
|
|
|
|
if (!sent)
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine($"[NetMqSender] 发送缓冲区满,丢弃帧: {payload.CameraId}");
|
|
|
|
|
|
// ★ 如果没有发送成功,建议显式清理消息帧,防止内存滞留
|
|
|
|
|
|
msg.Clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
frameCount++;
|
|
|
|
|
|
if (frameCount % 100 == 0)
|
|
|
|
|
|
Console.WriteLine($"[NetMqSender] 已搬运 100 帧至缓冲区.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine($"[NetMqSender] 内部循环异常: {ex.Message}");
|
|
|
|
|
|
}
|
2026-01-07 10:59:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-12 18:27:58 +08:00
|
|
|
|
catch (OperationCanceledException) { break; }
|
2026-01-07 10:59:03 +08:00
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2026-01-12 18:27:58 +08:00
|
|
|
|
// ★★★ 核心改进:捕获异常并等待重试 ★★★
|
|
|
|
|
|
// 防止因为一次内存溢出或网络波动导致整个 BackgroundService 永久停止
|
|
|
|
|
|
Console.WriteLine($"[NetMqSender] 发生致命异常,5秒后尝试重建连接: {ex.Message}");
|
|
|
|
|
|
await Task.Delay(5000, stoppingToken);
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
// 确保每次循环退出(无论是异常还是正常)都清理环境
|
|
|
|
|
|
NetMQConfig.Cleanup(false);
|
2026-01-07 10:59:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|