style: 重新调整一下后端的结构,并通过csharpier格式化
This commit is contained in:
parent
5ba71d220f
commit
7ffb15c722
|
@ -1,8 +1,5 @@
|
||||||
using System.Net;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NLog.Web;
|
using NLog.Web;
|
||||||
|
@ -38,7 +35,9 @@ try
|
||||||
// Configure Newtonsoft.Json options here
|
// Configure Newtonsoft.Json options here
|
||||||
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
||||||
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
|
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
|
||||||
}); // Add CORS policy
|
});
|
||||||
|
|
||||||
|
// Add CORS policy
|
||||||
if (builder.Environment.IsDevelopment())
|
if (builder.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
builder.Services.AddCors(options =>
|
builder.Services.AddCors(options =>
|
||||||
|
@ -56,7 +55,9 @@ try
|
||||||
.AllowAnyOrigin()
|
.AllowAnyOrigin()
|
||||||
.AllowAnyHeader()
|
.AllowAnyHeader()
|
||||||
);
|
);
|
||||||
}); // Add Swagger
|
});
|
||||||
|
|
||||||
|
// Add Swagger
|
||||||
builder.Services.AddOpenApiDocument(options =>
|
builder.Services.AddOpenApiDocument(options =>
|
||||||
{
|
{
|
||||||
options.PostProcess = document =>
|
options.PostProcess = document =>
|
||||||
|
@ -78,8 +79,10 @@ try
|
||||||
// Url = "https://example.com/license"
|
// Url = "https://example.com/license"
|
||||||
// }
|
// }
|
||||||
};
|
};
|
||||||
}; });
|
};
|
||||||
// 添加 HTTP 视频流服务 - 使用简化的类型引用
|
});
|
||||||
|
|
||||||
|
// 添加 HTTP 视频流服务
|
||||||
builder.Services.AddSingleton<HttpVideoStreamService>();
|
builder.Services.AddSingleton<HttpVideoStreamService>();
|
||||||
builder.Services.AddHostedService(provider => provider.GetRequiredService<HttpVideoStreamService>());
|
builder.Services.AddHostedService(provider => provider.GetRequiredService<HttpVideoStreamService>());
|
||||||
|
|
||||||
|
@ -96,12 +99,14 @@ try
|
||||||
logger.Info($"Use Static Files : {Path.Combine(Directory.GetCurrentDirectory(), "wwwroot")}");
|
logger.Info($"Use Static Files : {Path.Combine(Directory.GetCurrentDirectory(), "wwwroot")}");
|
||||||
app.UseDefaultFiles();
|
app.UseDefaultFiles();
|
||||||
app.UseStaticFiles(); // Serves files from wwwroot by default
|
app.UseStaticFiles(); // Serves files from wwwroot by default
|
||||||
|
|
||||||
// Assets Files
|
// Assets Files
|
||||||
app.UseStaticFiles(new StaticFileOptions
|
app.UseStaticFiles(new StaticFileOptions
|
||||||
{
|
{
|
||||||
FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "assets")),
|
FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "assets")),
|
||||||
RequestPath = "/assets"
|
RequestPath = "/assets"
|
||||||
});
|
});
|
||||||
|
|
||||||
// Log Files
|
// Log Files
|
||||||
if (!Directory.Exists(Path.Combine(Directory.GetCurrentDirectory(), "log")))
|
if (!Directory.Exists(Path.Combine(Directory.GetCurrentDirectory(), "log")))
|
||||||
{
|
{
|
||||||
|
@ -114,7 +119,7 @@ try
|
||||||
});
|
});
|
||||||
app.MapFallbackToFile("index.html");
|
app.MapFallbackToFile("index.html");
|
||||||
}
|
}
|
||||||
// Add logs
|
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
app.UseCors();
|
app.UseCors();
|
||||||
|
|
|
@ -1,16 +1,8 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Text;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace server.src
|
namespace server.src
|
||||||
{
|
{
|
||||||
|
@ -26,18 +18,18 @@ namespace server.src
|
||||||
private readonly int _frameRate = 30; // 30 FPS
|
private readonly int _frameRate = 30; // 30 FPS
|
||||||
private readonly int _frameWidth = 640;
|
private readonly int _frameWidth = 640;
|
||||||
private readonly int _frameHeight = 480;
|
private readonly int _frameHeight = 480;
|
||||||
|
|
||||||
// 模拟 FPGA 图像数据
|
// 模拟 FPGA 图像数据
|
||||||
private int _frameCounter = 0;
|
private int _frameCounter = 0;
|
||||||
private readonly List<HttpListenerResponse> _activeClients = new List<HttpListenerResponse>();
|
private readonly List<HttpListenerResponse> _activeClients = new List<HttpListenerResponse>();
|
||||||
private readonly object _clientsLock = new object();
|
private readonly object _clientsLock = new object();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取当前连接的客户端数量
|
/// 获取当前连接的客户端数量
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int ConnectedClientsCount
|
public int ConnectedClientsCount
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
lock (_clientsLock)
|
lock (_clientsLock)
|
||||||
{
|
{
|
||||||
|
@ -85,16 +77,16 @@ namespace server.src
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("启动 HTTP 视频流服务,端口: {Port}", _serverPort);
|
_logger.LogInformation("启动 HTTP 视频流服务,端口: {Port}", _serverPort);
|
||||||
// 创建 HTTP 监听器
|
// 创建 HTTP 监听器
|
||||||
_httpListener = new HttpListener();
|
_httpListener = new HttpListener();
|
||||||
_httpListener.Prefixes.Add($"http://localhost:{_serverPort}/");
|
_httpListener.Prefixes.Add($"http://localhost:{_serverPort}/");
|
||||||
_httpListener.Start();
|
_httpListener.Start();
|
||||||
|
|
||||||
_logger.LogInformation("HTTP 视频流服务已启动,监听端口: {Port}", _serverPort);
|
_logger.LogInformation("HTTP 视频流服务已启动,监听端口: {Port}", _serverPort);
|
||||||
|
|
||||||
// 开始接受客户端连接
|
// 开始接受客户端连接
|
||||||
_ = Task.Run(() => AcceptClientsAsync(stoppingToken), stoppingToken);
|
_ = Task.Run(() => AcceptClientsAsync(stoppingToken), stoppingToken);
|
||||||
|
|
||||||
// 开始生成视频帧
|
// 开始生成视频帧
|
||||||
await GenerateVideoFrames(stoppingToken);
|
await GenerateVideoFrames(stoppingToken);
|
||||||
}
|
}
|
||||||
|
@ -118,11 +110,11 @@ namespace server.src
|
||||||
var context = await _httpListener.GetContextAsync();
|
var context = await _httpListener.GetContextAsync();
|
||||||
var request = context.Request;
|
var request = context.Request;
|
||||||
var response = context.Response;
|
var response = context.Response;
|
||||||
|
|
||||||
_logger.LogInformation("新HTTP客户端连接: {RemoteEndPoint}", request.RemoteEndPoint);
|
_logger.LogInformation("新HTTP客户端连接: {RemoteEndPoint}", request.RemoteEndPoint);
|
||||||
// 处理不同的请求路径
|
// 处理不同的请求路径
|
||||||
var requestPath = request.Url?.AbsolutePath ?? "/";
|
var requestPath = request.Url?.AbsolutePath ?? "/";
|
||||||
|
|
||||||
if (requestPath == "/video-stream")
|
if (requestPath == "/video-stream")
|
||||||
{
|
{
|
||||||
// MJPEG 流请求
|
// MJPEG 流请求
|
||||||
|
@ -170,15 +162,15 @@ namespace server.src
|
||||||
response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
|
response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||||
response.Headers.Add("Pragma", "no-cache");
|
response.Headers.Add("Pragma", "no-cache");
|
||||||
response.Headers.Add("Expires", "0");
|
response.Headers.Add("Expires", "0");
|
||||||
|
|
||||||
// 跟踪活跃的客户端
|
// 跟踪活跃的客户端
|
||||||
lock (_clientsLock)
|
lock (_clientsLock)
|
||||||
{
|
{
|
||||||
_activeClients.Add(response);
|
_activeClients.Add(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogDebug("已启动MJPEG流,客户端: {RemoteEndPoint}", response.OutputStream?.GetHashCode() ?? 0);
|
_logger.LogDebug("已启动MJPEG流,客户端: {RemoteEndPoint}", response.OutputStream?.GetHashCode() ?? 0);
|
||||||
|
|
||||||
// 保持连接直到取消或出错
|
// 保持连接直到取消或出错
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -191,7 +183,7 @@ namespace server.src
|
||||||
{
|
{
|
||||||
// 预期的取消
|
// 预期的取消
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogDebug("MJPEG流已结束,客户端: {ClientId}", response.OutputStream?.GetHashCode() ?? 0);
|
_logger.LogDebug("MJPEG流已结束,客户端: {ClientId}", response.OutputStream?.GetHashCode() ?? 0);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -204,7 +196,7 @@ namespace server.src
|
||||||
{
|
{
|
||||||
_activeClients.Remove(response);
|
_activeClients.Remove(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
response.Close();
|
response.Close();
|
||||||
|
@ -223,16 +215,16 @@ namespace server.src
|
||||||
// 获取当前帧
|
// 获取当前帧
|
||||||
var imageData = await GetFPGAImageData();
|
var imageData = await GetFPGAImageData();
|
||||||
var jpegData = ConvertToJpeg(imageData);
|
var jpegData = ConvertToJpeg(imageData);
|
||||||
|
|
||||||
// 设置响应头
|
// 设置响应头
|
||||||
response.ContentType = "image/jpeg";
|
response.ContentType = "image/jpeg";
|
||||||
response.ContentLength64 = jpegData.Length;
|
response.ContentLength64 = jpegData.Length;
|
||||||
response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
|
response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||||
|
|
||||||
// 发送JPEG数据
|
// 发送JPEG数据
|
||||||
await response.OutputStream.WriteAsync(jpegData, 0, jpegData.Length, cancellationToken);
|
await response.OutputStream.WriteAsync(jpegData, 0, jpegData.Length, cancellationToken);
|
||||||
await response.OutputStream.FlushAsync(cancellationToken);
|
await response.OutputStream.FlushAsync(cancellationToken);
|
||||||
|
|
||||||
_logger.LogDebug("已发送快照图像,大小:{Size} 字节", jpegData.Length);
|
_logger.LogDebug("已发送快照图像,大小:{Size} 字节", jpegData.Length);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -286,7 +278,7 @@ namespace server.src
|
||||||
response.ContentEncoding = Encoding.UTF8;
|
response.ContentEncoding = Encoding.UTF8;
|
||||||
byte[] buffer = Encoding.UTF8.GetBytes(html);
|
byte[] buffer = Encoding.UTF8.GetBytes(html);
|
||||||
response.ContentLength64 = buffer.Length;
|
response.ContentLength64 = buffer.Length;
|
||||||
|
|
||||||
await response.OutputStream.WriteAsync(buffer, 0, buffer.Length);
|
await response.OutputStream.WriteAsync(buffer, 0, buffer.Length);
|
||||||
response.Close();
|
response.Close();
|
||||||
}
|
}
|
||||||
|
@ -320,7 +312,7 @@ namespace server.src
|
||||||
response.ContentEncoding = Encoding.UTF8;
|
response.ContentEncoding = Encoding.UTF8;
|
||||||
byte[] buffer = Encoding.UTF8.GetBytes(html);
|
byte[] buffer = Encoding.UTF8.GetBytes(html);
|
||||||
response.ContentLength64 = buffer.Length;
|
response.ContentLength64 = buffer.Length;
|
||||||
|
|
||||||
await response.OutputStream.WriteAsync(buffer, 0, buffer.Length);
|
await response.OutputStream.WriteAsync(buffer, 0, buffer.Length);
|
||||||
response.Close();
|
response.Close();
|
||||||
}
|
}
|
||||||
|
@ -328,22 +320,22 @@ namespace server.src
|
||||||
private async Task GenerateVideoFrames(CancellationToken cancellationToken)
|
private async Task GenerateVideoFrames(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var frameInterval = TimeSpan.FromMilliseconds(1000.0 / _frameRate);
|
var frameInterval = TimeSpan.FromMilliseconds(1000.0 / _frameRate);
|
||||||
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 从 FPGA 获取图像数据(模拟)
|
// 从 FPGA 获取图像数据(模拟)
|
||||||
var imageData = await GetFPGAImageData();
|
var imageData = await GetFPGAImageData();
|
||||||
|
|
||||||
// 将图像数据转换为 JPEG
|
// 将图像数据转换为 JPEG
|
||||||
var jpegData = ConvertToJpeg(imageData);
|
var jpegData = ConvertToJpeg(imageData);
|
||||||
|
|
||||||
// 向所有连接的客户端发送帧
|
// 向所有连接的客户端发送帧
|
||||||
await BroadcastFrameAsync(jpegData, cancellationToken);
|
await BroadcastFrameAsync(jpegData, cancellationToken);
|
||||||
|
|
||||||
_frameCounter++;
|
_frameCounter++;
|
||||||
|
|
||||||
// 等待下一帧
|
// 等待下一帧
|
||||||
await Task.Delay(frameInterval, cancellationToken);
|
await Task.Delay(frameInterval, cancellationToken);
|
||||||
}
|
}
|
||||||
|
@ -367,11 +359,11 @@ namespace server.src
|
||||||
{
|
{
|
||||||
// 模拟异步 FPGA 数据读取
|
// 模拟异步 FPGA 数据读取
|
||||||
await Task.Delay(1);
|
await Task.Delay(1);
|
||||||
|
|
||||||
// 简化的模拟图像数据生成
|
// 简化的模拟图像数据生成
|
||||||
var random = new Random(_frameCounter);
|
var random = new Random(_frameCounter);
|
||||||
var imageData = new byte[_frameWidth * _frameHeight * 3]; // RGB24 格式
|
var imageData = new byte[_frameWidth * _frameHeight * 3]; // RGB24 格式
|
||||||
|
|
||||||
// 生成简单的彩色噪声图案
|
// 生成简单的彩色噪声图案
|
||||||
for (int i = 0; i < imageData.Length; i += 3)
|
for (int i = 0; i < imageData.Length; i += 3)
|
||||||
{
|
{
|
||||||
|
@ -381,12 +373,12 @@ namespace server.src
|
||||||
imageData[i + 1] = (byte)((baseColor * 2 + random.Next(0, 50)) % 256); // G
|
imageData[i + 1] = (byte)((baseColor * 2 + random.Next(0, 50)) % 256); // G
|
||||||
imageData[i + 2] = (byte)((baseColor * 3 + random.Next(0, 50)) % 256); // B
|
imageData[i + 2] = (byte)((baseColor * 3 + random.Next(0, 50)) % 256); // B
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_frameCounter % 30 == 0) // 每秒更新一次日志
|
if (_frameCounter % 30 == 0) // 每秒更新一次日志
|
||||||
{
|
{
|
||||||
_logger.LogDebug("生成第 {FrameNumber} 帧", _frameCounter);
|
_logger.LogDebug("生成第 {FrameNumber} 帧", _frameCounter);
|
||||||
}
|
}
|
||||||
|
|
||||||
return imageData;
|
return imageData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -396,7 +388,7 @@ namespace server.src
|
||||||
private byte[] ConvertToJpeg(byte[] rgbData)
|
private byte[] ConvertToJpeg(byte[] rgbData)
|
||||||
{
|
{
|
||||||
using var image = new Image<Rgb24>(_frameWidth, _frameHeight);
|
using var image = new Image<Rgb24>(_frameWidth, _frameHeight);
|
||||||
|
|
||||||
// 将 RGB 数据复制到 ImageSharp 图像
|
// 将 RGB 数据复制到 ImageSharp 图像
|
||||||
for (int y = 0; y < _frameHeight; y++)
|
for (int y = 0; y < _frameHeight; y++)
|
||||||
{
|
{
|
||||||
|
@ -407,7 +399,7 @@ namespace server.src
|
||||||
image[x, y] = pixel;
|
image[x, y] = pixel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using var stream = new MemoryStream();
|
using var stream = new MemoryStream();
|
||||||
image.SaveAsJpeg(stream, new JpegEncoder { Quality = 80 });
|
image.SaveAsJpeg(stream, new JpegEncoder { Quality = 80 });
|
||||||
return stream.ToArray();
|
return stream.ToArray();
|
||||||
|
@ -427,16 +419,16 @@ namespace server.src
|
||||||
// 准备MJPEG帧数据
|
// 准备MJPEG帧数据
|
||||||
var mjpegFrameHeader = $"--boundary\r\nContent-Type: image/jpeg\r\nContent-Length: {frameData.Length}\r\n\r\n";
|
var mjpegFrameHeader = $"--boundary\r\nContent-Type: image/jpeg\r\nContent-Length: {frameData.Length}\r\n\r\n";
|
||||||
var headerBytes = Encoding.ASCII.GetBytes(mjpegFrameHeader);
|
var headerBytes = Encoding.ASCII.GetBytes(mjpegFrameHeader);
|
||||||
|
|
||||||
var clientsToRemove = new List<HttpListenerResponse>();
|
var clientsToRemove = new List<HttpListenerResponse>();
|
||||||
var clientsToProcess = new List<HttpListenerResponse>();
|
var clientsToProcess = new List<HttpListenerResponse>();
|
||||||
|
|
||||||
// 获取当前连接的客户端列表
|
// 获取当前连接的客户端列表
|
||||||
lock (_clientsLock)
|
lock (_clientsLock)
|
||||||
{
|
{
|
||||||
clientsToProcess.AddRange(_activeClients);
|
clientsToProcess.AddRange(_activeClients);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clientsToProcess.Count == 0)
|
if (clientsToProcess.Count == 0)
|
||||||
{
|
{
|
||||||
return; // 没有活跃客户端
|
return; // 没有活跃客户端
|
||||||
|
@ -449,19 +441,19 @@ namespace server.src
|
||||||
{
|
{
|
||||||
// 发送帧头部
|
// 发送帧头部
|
||||||
await client.OutputStream.WriteAsync(headerBytes, 0, headerBytes.Length, cancellationToken);
|
await client.OutputStream.WriteAsync(headerBytes, 0, headerBytes.Length, cancellationToken);
|
||||||
|
|
||||||
// 发送JPEG数据
|
// 发送JPEG数据
|
||||||
await client.OutputStream.WriteAsync(frameData, 0, frameData.Length, cancellationToken);
|
await client.OutputStream.WriteAsync(frameData, 0, frameData.Length, cancellationToken);
|
||||||
|
|
||||||
// 发送结尾换行符
|
// 发送结尾换行符
|
||||||
await client.OutputStream.WriteAsync(Encoding.ASCII.GetBytes("\r\n"), 0, 2, cancellationToken);
|
await client.OutputStream.WriteAsync(Encoding.ASCII.GetBytes("\r\n"), 0, 2, cancellationToken);
|
||||||
|
|
||||||
// 确保数据立即发送
|
// 确保数据立即发送
|
||||||
await client.OutputStream.FlushAsync(cancellationToken);
|
await client.OutputStream.FlushAsync(cancellationToken);
|
||||||
|
|
||||||
if (_frameCounter % 30 == 0) // 每秒记录一次日志
|
if (_frameCounter % 30 == 0) // 每秒记录一次日志
|
||||||
{
|
{
|
||||||
_logger.LogDebug("已向客户端 {ClientId} 发送第 {FrameNumber} 帧,大小:{Size} 字节",
|
_logger.LogDebug("已向客户端 {ClientId} 发送第 {FrameNumber} 帧,大小:{Size} 字节",
|
||||||
client.OutputStream.GetHashCode(), _frameCounter, frameData.Length);
|
client.OutputStream.GetHashCode(), _frameCounter, frameData.Length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -471,7 +463,7 @@ namespace server.src
|
||||||
clientsToRemove.Add(client);
|
clientsToRemove.Add(client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除断开连接的客户端
|
// 移除断开连接的客户端
|
||||||
if (clientsToRemove.Count > 0)
|
if (clientsToRemove.Count > 0)
|
||||||
{
|
{
|
||||||
|
@ -480,12 +472,12 @@ namespace server.src
|
||||||
foreach (var client in clientsToRemove)
|
foreach (var client in clientsToRemove)
|
||||||
{
|
{
|
||||||
_activeClients.Remove(client);
|
_activeClients.Remove(client);
|
||||||
try { client.Close(); }
|
try { client.Close(); }
|
||||||
catch { /* 忽略关闭错误 */ }
|
catch { /* 忽略关闭错误 */ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("已移除 {Count} 个断开连接的客户端,当前连接数: {ActiveCount}",
|
_logger.LogInformation("已移除 {Count} 个断开连接的客户端,当前连接数: {ActiveCount}",
|
||||||
clientsToRemove.Count, _activeClients.Count);
|
clientsToRemove.Count, _activeClients.Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -496,7 +488,7 @@ namespace server.src
|
||||||
public List<string> GetConnectedClientEndpoints()
|
public List<string> GetConnectedClientEndpoints()
|
||||||
{
|
{
|
||||||
List<string> endpoints = new List<string>();
|
List<string> endpoints = new List<string>();
|
||||||
|
|
||||||
lock (_clientsLock)
|
lock (_clientsLock)
|
||||||
{
|
{
|
||||||
foreach (var client in _activeClients)
|
foreach (var client in _activeClients)
|
||||||
|
@ -504,7 +496,7 @@ namespace server.src
|
||||||
endpoints.Add($"Client-{client.OutputStream?.GetHashCode() ?? 0}");
|
endpoints.Add($"Client-{client.OutputStream?.GetHashCode() ?? 0}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return endpoints;
|
return endpoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -523,20 +515,20 @@ namespace server.src
|
||||||
ClientEndpoints = GetConnectedClientEndpoints()
|
ClientEndpoints = GetConnectedClientEndpoints()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 停止 HTTP 视频流服务
|
/// 停止 HTTP 视频流服务
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("正在停止 HTTP 视频流服务...");
|
_logger.LogInformation("正在停止 HTTP 视频流服务...");
|
||||||
|
|
||||||
if (_httpListener != null && _httpListener.IsListening)
|
if (_httpListener != null && _httpListener.IsListening)
|
||||||
{
|
{
|
||||||
_httpListener.Stop();
|
_httpListener.Stop();
|
||||||
_httpListener.Close();
|
_httpListener.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭所有客户端连接
|
// 关闭所有客户端连接
|
||||||
lock (_clientsLock)
|
lock (_clientsLock)
|
||||||
{
|
{
|
||||||
|
@ -547,9 +539,9 @@ namespace server.src
|
||||||
}
|
}
|
||||||
_activeClients.Clear();
|
_activeClients.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
await base.StopAsync(cancellationToken);
|
await base.StopAsync(cancellationToken);
|
||||||
|
|
||||||
_logger.LogInformation("HTTP 视频流服务已停止");
|
_logger.LogInformation("HTTP 视频流服务已停止");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -566,7 +558,7 @@ namespace server.src
|
||||||
}
|
}
|
||||||
_httpListener.Close();
|
_httpListener.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (_clientsLock)
|
lock (_clientsLock)
|
||||||
{
|
{
|
||||||
foreach (var client in _activeClients)
|
foreach (var client in _activeClients)
|
||||||
|
@ -576,8 +568,8 @@ namespace server.src
|
||||||
}
|
}
|
||||||
_activeClients.Clear();
|
_activeClients.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
base.Dispose();
|
base.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
// 此接口提供获取例程目录服务
|
|
||||||
// GET /api/tutorials 返回所有可用的例程目录
|
|
||||||
|
|
||||||
import fs from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
import { Request, Response } from 'express';
|
|
||||||
|
|
||||||
// 获取当前文件的目录
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
|
||||||
const __dirname = path.dirname(__filename);
|
|
||||||
const publicDir = path.resolve(__dirname, '../public');
|
|
||||||
|
|
||||||
export function getTutorials(req: Request, res: Response) {
|
|
||||||
try {
|
|
||||||
const docDir = path.join(publicDir, 'doc');
|
|
||||||
|
|
||||||
// 读取doc目录下的所有文件夹
|
|
||||||
const entries = fs.readdirSync(docDir, { withFileTypes: true });
|
|
||||||
const dirs = entries
|
|
||||||
.filter(dirent => dirent.isDirectory())
|
|
||||||
.map(dirent => dirent.name);
|
|
||||||
|
|
||||||
// 返回文件夹列表
|
|
||||||
res.json({ tutorials: dirs });
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取例程目录失败:', error);
|
|
||||||
res.status(500).json({ error: '无法读取例程目录' });
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -180,6 +180,7 @@ public class UDPClientPool
|
||||||
/// [TODO:description]
|
/// [TODO:description]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="endPoint">[TODO:parameter]</param>
|
/// <param name="endPoint">[TODO:parameter]</param>
|
||||||
|
/// <param name="taskID">[TODO:parameter]</param>
|
||||||
/// <param name="devAddr">[TODO:parameter]</param>
|
/// <param name="devAddr">[TODO:parameter]</param>
|
||||||
/// <param name="timeout">[TODO:parameter]</param>
|
/// <param name="timeout">[TODO:parameter]</param>
|
||||||
/// <returns>[TODO:return]</returns>
|
/// <returns>[TODO:return]</returns>
|
||||||
|
@ -220,6 +221,7 @@ public class UDPClientPool
|
||||||
/// [TODO:description]
|
/// [TODO:description]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="endPoint">[TODO:parameter]</param>
|
/// <param name="endPoint">[TODO:parameter]</param>
|
||||||
|
/// <param name="taskID">[TODO:parameter]</param>
|
||||||
/// <param name="devAddr">[TODO:parameter]</param>
|
/// <param name="devAddr">[TODO:parameter]</param>
|
||||||
/// <param name="result">[TODO:parameter]</param>
|
/// <param name="result">[TODO:parameter]</param>
|
||||||
/// <param name="resultMask">[TODO:parameter]</param>
|
/// <param name="resultMask">[TODO:parameter]</param>
|
||||||
|
@ -258,6 +260,7 @@ public class UDPClientPool
|
||||||
/// [TODO:description]
|
/// [TODO:description]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="endPoint">[TODO:parameter]</param>
|
/// <param name="endPoint">[TODO:parameter]</param>
|
||||||
|
/// <param name="taskID">[TODO:parameter]</param>
|
||||||
/// <param name="devAddr">[TODO:parameter]</param>
|
/// <param name="devAddr">[TODO:parameter]</param>
|
||||||
/// <param name="result">[TODO:parameter]</param>
|
/// <param name="result">[TODO:parameter]</param>
|
||||||
/// <param name="resultMask">[TODO:parameter]</param>
|
/// <param name="resultMask">[TODO:parameter]</param>
|
||||||
|
@ -307,6 +310,7 @@ public class UDPClientPool
|
||||||
/// [TODO:description]
|
/// [TODO:description]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="endPoint">[TODO:parameter]</param>
|
/// <param name="endPoint">[TODO:parameter]</param>
|
||||||
|
/// <param name="taskID">[TODO:parameter]</param>
|
||||||
/// <param name="devAddr">[TODO:parameter]</param>
|
/// <param name="devAddr">[TODO:parameter]</param>
|
||||||
/// <param name="data">[TODO:parameter]</param>
|
/// <param name="data">[TODO:parameter]</param>
|
||||||
/// <param name="timeout">[TODO:parameter]</param>
|
/// <param name="timeout">[TODO:parameter]</param>
|
||||||
|
@ -347,6 +351,7 @@ public class UDPClientPool
|
||||||
/// [TODO:description]
|
/// [TODO:description]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="endPoint">[TODO:parameter]</param>
|
/// <param name="endPoint">[TODO:parameter]</param>
|
||||||
|
/// <param name="taskID">[TODO:parameter]</param>
|
||||||
/// <param name="devAddr">[TODO:parameter]</param>
|
/// <param name="devAddr">[TODO:parameter]</param>
|
||||||
/// <param name="dataArray">[TODO:parameter]</param>
|
/// <param name="dataArray">[TODO:parameter]</param>
|
||||||
/// <param name="timeout">[TODO:parameter]</param>
|
/// <param name="timeout">[TODO:parameter]</param>
|
||||||
|
|
|
@ -231,6 +231,7 @@ public class UDPServer
|
||||||
/// 异步等待写响应
|
/// 异步等待写响应
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">IP地址</param>
|
/// <param name="address">IP地址</param>
|
||||||
|
/// <param name="taskID">[TODO:parameter]</param>
|
||||||
/// <param name="port">UDP 端口</param>
|
/// <param name="port">UDP 端口</param>
|
||||||
/// <param name="timeout">超时时间范围</param>
|
/// <param name="timeout">超时时间范围</param>
|
||||||
/// <returns>接收响应包</returns>
|
/// <returns>接收响应包</returns>
|
||||||
|
@ -256,6 +257,7 @@ public class UDPServer
|
||||||
/// 异步等待数据
|
/// 异步等待数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">IP地址</param>
|
/// <param name="address">IP地址</param>
|
||||||
|
/// <param name="taskID">[TODO:parameter]</param>
|
||||||
/// <param name="port">UDP 端口</param>
|
/// <param name="port">UDP 端口</param>
|
||||||
/// <param name="timeout">超时时间范围</param>
|
/// <param name="timeout">超时时间范围</param>
|
||||||
/// <returns>接收数据包</returns>
|
/// <returns>接收数据包</returns>
|
||||||
|
@ -420,6 +422,7 @@ public class UDPServer
|
||||||
/// 清空指定IP地址的数据
|
/// 清空指定IP地址的数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ipAddr">IP地址</param>
|
/// <param name="ipAddr">IP地址</param>
|
||||||
|
/// <param name="taskID">[TODO:parameter]</param>
|
||||||
/// <returns>无</returns>
|
/// <returns>无</returns>
|
||||||
public async Task ClearUDPData(string ipAddr, int taskID)
|
public async Task ClearUDPData(string ipAddr, int taskID)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue