去除已经移除的项目
This commit is contained in:
@@ -1,3 +0,0 @@
|
|||||||
<Solution>
|
|
||||||
<Project Path="SHH.AIServer/SHH.AIServer.csproj" />
|
|
||||||
</Solution>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
namespace SHH.AIServer
|
|
||||||
{
|
|
||||||
internal class Program
|
|
||||||
{
|
|
||||||
static void Main(string[] args)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Hello, World!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
using System;
|
|
||||||
using NetMQ;
|
|
||||||
using NetMQ.Sockets;
|
|
||||||
using SHH.Contracts;
|
|
||||||
|
|
||||||
namespace SHH.NetMQ
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 视频分发服务端 (Publisher)
|
|
||||||
/// 特性:非阻塞、防内存溢出
|
|
||||||
/// </summary>
|
|
||||||
public class DistributorServer : IDisposable
|
|
||||||
{
|
|
||||||
private PublisherSocket _pubSocket;
|
|
||||||
private readonly object _lock = new object();
|
|
||||||
|
|
||||||
// 配置:高水位限制 (HWM)
|
|
||||||
// 假设 25fps,设置 50 意味着内存只缓存 2 秒的视频。
|
|
||||||
// 如果断网超过 2 秒,新来的视频帧直接丢弃,优先保证恢复后的实时性。
|
|
||||||
private const int HWM_LIMIT = 50;
|
|
||||||
|
|
||||||
public DistributorServer(string connectionString)
|
|
||||||
{
|
|
||||||
_pubSocket = new PublisherSocket();
|
|
||||||
|
|
||||||
// 1. 设置发送缓冲区大小 (防爆内存关键)
|
|
||||||
_pubSocket.Options.SendHighWatermark = HWM_LIMIT;
|
|
||||||
|
|
||||||
// 2. 绑定地址 (如 tcp://*:5555)
|
|
||||||
_pubSocket.Bind(connectionString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Broadcast(VideoPayload payload)
|
|
||||||
{
|
|
||||||
if (payload == null) return;
|
|
||||||
|
|
||||||
// 补充发送时间
|
|
||||||
payload.DispatchTimestamp = DateTime.Now.Ticks;
|
|
||||||
|
|
||||||
// 准备数据帧
|
|
||||||
string jsonMeta = payload.GetMetadataJson();
|
|
||||||
byte[] rawBytes = payload.OriginalImageBytes ?? new byte[0];
|
|
||||||
byte[] targetBytes = payload.TargetImageBytes ?? new byte[0];
|
|
||||||
|
|
||||||
// 使用 NetMQMessage 封装多帧消息
|
|
||||||
// 这样比手动调三次 Send 更容易管理原子性
|
|
||||||
var msg = new NetMQMessage();
|
|
||||||
msg.Append(jsonMeta); // 第1帧
|
|
||||||
msg.Append(rawBytes); // 第2帧
|
|
||||||
msg.Append(targetBytes); // 第3帧
|
|
||||||
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
// 3. 非阻塞发送 (核心防卡死代码)
|
|
||||||
// TimeSpan.Zero 表示:如果缓冲区满了或者发不出去,立即放弃,不等待,返回 false
|
|
||||||
// 这样你的主线程(海康SDK回调)永远不会被卡住
|
|
||||||
bool sent = _pubSocket.TrySendMultipartMessage(TimeSpan.Zero, msg);
|
|
||||||
|
|
||||||
if (!sent)
|
|
||||||
{
|
|
||||||
// 这里可以打个日志:Console.WriteLine("警告:网络拥堵,丢帧中...");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_pubSocket?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
using System;
|
|
||||||
using NetMQ;
|
|
||||||
using NetMQ.Sockets;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using SHH.Contracts;
|
|
||||||
|
|
||||||
namespace SHH.NetMQ
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 视频转发客户端 (Fusion Version)
|
|
||||||
/// <para>核心特性:</para>
|
|
||||||
/// <para>1. 主动推送 (Push Mode)</para>
|
|
||||||
/// <para>2. 带宽保护 (HWM=2, 拥堵丢弃)</para>
|
|
||||||
/// <para>3. 高效传输 (Multipart, 避免 Base64)</para>
|
|
||||||
/// <para>4. 线程安全 (Lock 保护)</para>
|
|
||||||
/// </summary>
|
|
||||||
public class ForwarderClient : IDisposable
|
|
||||||
{
|
|
||||||
#region --- 字段与配置 ---
|
|
||||||
|
|
||||||
private readonly PushSocket _pushSocket;
|
|
||||||
private readonly object _lock = new object();
|
|
||||||
private bool _isDisposed = false;
|
|
||||||
|
|
||||||
// ★★★ 核心策略:高水位线 (High Water Mark) ★★★
|
|
||||||
// 设置为 2,意味着内存队列中最多只允许堆积 2 帧图片。
|
|
||||||
// 第 3 帧到来时,如果网速不够发不出去,这第 3 帧会被直接丢弃。
|
|
||||||
// 收益:永远只发最新的图,永远不挤占物理带宽,彻底杜绝延迟累积。
|
|
||||||
private const int HWM_LIMIT = 2;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region --- 构造函数 ---
|
|
||||||
|
|
||||||
public ForwarderClient(string remoteAddress)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(remoteAddress))
|
|
||||||
throw new ArgumentNullException(nameof(remoteAddress));
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_pushSocket = new PushSocket();
|
|
||||||
|
|
||||||
// 1. 配置防堆积策略 (必须在 Connect 之前设置)
|
|
||||||
_pushSocket.Options.SendHighWatermark = HWM_LIMIT;
|
|
||||||
|
|
||||||
// 2. 配置 Linger (逗留时间)
|
|
||||||
// 设为 0 表示:当 Dispose 时,如果队列里还有没发完的数据,直接扔掉,不要等待。
|
|
||||||
// 避免关程序时卡死。
|
|
||||||
_pushSocket.Options.Linger = TimeSpan.Zero;
|
|
||||||
|
|
||||||
// 3. 建立连接 (NetMQ 会自动在后台处理断线重连)
|
|
||||||
_pushSocket.Connect(remoteAddress);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// 构造函数异常通常是致命的,向上抛出让启动流程感知
|
|
||||||
throw new InvalidOperationException($"[ForwarderClient] 初始化失败: {ex.Message}", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region --- 核心发送逻辑 ---
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 推送视频帧 (非阻塞,线程安全)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="payload">视频帧载荷</param>
|
|
||||||
public void Push(VideoPayload payload)
|
|
||||||
{
|
|
||||||
if (_isDisposed || payload == null) return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 1. 准备多帧消息 (Multipart Message)
|
|
||||||
// 这种方式比把 byte[] 转成 Base64 字符串塞进 JSON 要高效得多 (减少 33% 体积,且无 GC 压力)
|
|
||||||
var msg = new NetMQMessage();
|
|
||||||
|
|
||||||
// --- 第一帧:元数据 (JSON) ---
|
|
||||||
// 我们使用匿名对象来生成 JSON,刻意排除 byte[] 数组
|
|
||||||
// 这样生成的 JSON 非常小,只有几十字节
|
|
||||||
var metaJson = JsonConvert.SerializeObject(new
|
|
||||||
{
|
|
||||||
payload.CameraId,
|
|
||||||
payload.CaptureTimestamp,
|
|
||||||
payload.DispatchTimestamp,
|
|
||||||
payload.OriginalWidth,
|
|
||||||
payload.OriginalHeight,
|
|
||||||
// 如果有订阅者ID列表也带上
|
|
||||||
payload.SubscriberIds
|
|
||||||
});
|
|
||||||
msg.Append(metaJson);
|
|
||||||
|
|
||||||
// --- 第二帧:原始图像数据 (Binary) ---
|
|
||||||
// 直接追加二进制数据,实现 Zero-Copy (零拷贝)
|
|
||||||
// NetMQ 底层会直接搬运这段内存,不会产生临时的 Base64 字符串
|
|
||||||
msg.Append(payload.OriginalImageBytes ?? Array.Empty<byte>());
|
|
||||||
|
|
||||||
// 2. 线程安全发送
|
|
||||||
// NetMQ 的 Socket 实例不是线程安全的,多线程同时调用 Push 必须加锁
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
// 3. 非阻塞尝试发送 (TrySend)
|
|
||||||
// TimeSpan.Zero 表示:如果队列满了 (超过 HWM),立刻返回 false,不要等待。
|
|
||||||
// 这实现了 "拥堵即丢弃" 的保护机制。
|
|
||||||
if (!_pushSocket.TrySendMultipartMessage(TimeSpan.Zero, msg))
|
|
||||||
{
|
|
||||||
// 返回 false 说明触发了 HWM 保护
|
|
||||||
// 此时我们选择静默丢弃,或者仅在调试模式下打印日志
|
|
||||||
// Console.WriteLine($"[Drop] 网络拥堵,丢弃帧: {payload.CameraId}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// 发送过程中的异常 (通常是 ObjectDisposedException 或 NetMQException)
|
|
||||||
// 捕获它以防止单个帧的发送失败导致整个服务崩溃
|
|
||||||
Console.WriteLine($"[ForwarderClient] 推送异常: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region --- 资源释放 ---
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_isDisposed) return;
|
|
||||||
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
if (_isDisposed) return;
|
|
||||||
_isDisposed = true;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_pushSocket?.Close();
|
|
||||||
_pushSocket?.Dispose();
|
|
||||||
}
|
|
||||||
catch { /* 忽略关闭时的异常 */ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
|
||||||
<NoWarn>$(NoWarn);NU1701</NoWarn>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="NetMQ" Version="4.0.2.2" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\SHH.Contracts\SHH.Contracts.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
Reference in New Issue
Block a user