NetMQ 协议,支持摄像头增、删、改
This commit is contained in:
@@ -53,7 +53,7 @@ namespace SHH.CameraService
|
||||
/// </summary>
|
||||
public static VideoPayload ToVideoPayload(this NetMQMessage msg)
|
||||
{
|
||||
if (msg == null || msg.FrameCount < 4) return null;
|
||||
if (msg == null || msg.FrameCount < 2) return null;
|
||||
|
||||
// Frame 0 Check
|
||||
if (msg[0].ConvertToString() != PROTOCOL_HEADER) return null;
|
||||
|
||||
@@ -19,43 +19,65 @@ public class NetMqSenderWorker : BackgroundService
|
||||
}
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
Console.WriteLine($"[NetMqSender] 正在连接至服务端: {_target.Config.Endpoint} ...");
|
||||
|
||||
// ★★★ 修正点:必须使用 PublisherSocket 来配合接收端的 SubscriberSocket ★★★
|
||||
// 虽然是 Connect 模式,Publisher 依然可以 Connect
|
||||
using var clientSocket = new PublisherSocket();
|
||||
|
||||
// 设置高水位 (HWM)
|
||||
// 对于 Publisher,如果队列满了,默认行为就是丢弃旧数据,这非常符合视频流需求
|
||||
clientSocket.Options.SendHighWatermark = 1000;
|
||||
|
||||
// 主动连接
|
||||
clientSocket.Connect(_target.Config.Endpoint);
|
||||
|
||||
Console.WriteLine("[NetMqSender] 连接成功,开始从通道搬运数据...");
|
||||
|
||||
await foreach (var payload in _target.Channel.Reader.ReadAllAsync(stoppingToken))
|
||||
// 增加重启保护
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var msg = payload.ToNetMqMessage();
|
||||
Console.WriteLine($"[NetMqSender] 连接至: {_target.Config.Endpoint}");
|
||||
|
||||
// 发送消息
|
||||
// PublisherSocket 的 TrySend 如果没人订阅或者队列满了,通常不会阻塞,而是直接丢弃或返回
|
||||
// 注意:PUB 模式下,第一帧 ("SHH_V1") 会被当作订阅的主题 (Topic)。
|
||||
// 你的接收端订阅了 "" (空字符串),所以能收到以任何字符串开头的数据。
|
||||
bool sent = clientSocket.TrySendMultipartMessage(msg);
|
||||
using var clientSocket = new PublisherSocket();
|
||||
clientSocket.Options.SendHighWatermark = 1000;
|
||||
// 关键:增加 TCP 保活,防止防火墙静默断开长连接
|
||||
clientSocket.Options.TcpKeepalive = true;
|
||||
clientSocket.Options.TcpKeepaliveIdle = TimeSpan.FromSeconds(5);
|
||||
|
||||
if (!sent)
|
||||
clientSocket.Connect(_target.Config.Endpoint);
|
||||
|
||||
int frameCount = 0;
|
||||
|
||||
// 使用更稳健的读取方式
|
||||
await foreach (var payload in _target.Channel.Reader.ReadAllAsync(stoppingToken))
|
||||
{
|
||||
// 这种情况通常意味着网络断了且 HWM 队列也满了
|
||||
Console.WriteLine($"[NetMqSender] 警告: 发送队列已满,正在丢帧...");
|
||||
msg.Clear(); // 手动清理(可选)
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) { break; }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[NetMqSender] 异常: {ex.Message}");
|
||||
// ★★★ 核心改进:捕获异常并等待重试 ★★★
|
||||
// 防止因为一次内存溢出或网络波动导致整个 BackgroundService 永久停止
|
||||
Console.WriteLine($"[NetMqSender] 发生致命异常,5秒后尝试重建连接: {ex.Message}");
|
||||
await Task.Delay(5000, stoppingToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 确保每次循环退出(无论是异常还是正常)都清理环境
|
||||
NetMQConfig.Cleanup(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,8 +106,12 @@ public class NetworkStreamingWorker : BackgroundService
|
||||
// 实现了"物理隔离":一个管道满了(云端卡顿),不影响另一个管道(大屏流畅)。
|
||||
foreach (var target in _targets)
|
||||
{
|
||||
// WriteLog 是非阻塞的。满了就丢弃,返回 false。
|
||||
target.Channel.WriteLog(payload);
|
||||
bool ok = target.Channel.WriteLog(payload);
|
||||
if (!ok)
|
||||
{
|
||||
// 如果这里打印,说明管道由于某种原因被关闭了(通常是程序正在退出)
|
||||
Console.WriteLine($"[DEBUG] 管道写入失败,目标: {target.Config.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -26,10 +26,11 @@ namespace SHH.CameraService
|
||||
/// <summary>
|
||||
/// [生产者] 写入一个封装好的数据包 (非阻塞)
|
||||
/// </summary>
|
||||
public void WriteLog(VideoPayload payload)
|
||||
public bool WriteLog(VideoPayload payload) // 改为返回 bool
|
||||
{
|
||||
// TryWrite 永远不会等待,满了就丢旧的写入新的,返回 true
|
||||
_channel.Writer.TryWrite(payload);
|
||||
// TryWrite 在 DropOldest 模式下虽然几乎总是返回 true,
|
||||
// 但如果 Channel 被 Complete (关闭) 了,它会返回 false。
|
||||
return _channel.Writer.TryWrite(payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user