新增 Mjpegplayer 用来播放 Web 流
This commit is contained in:
120
SHH.MjpegPlayer/Core/Extensions/NetHttpExtension.cs
Normal file
120
SHH.MjpegPlayer/Core/Extensions/NetHttpExtension.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using Ayay.SerilogLogs;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using System.Text;
|
||||
|
||||
namespace SHH.MjpegPlayer;
|
||||
|
||||
/// <summary>
|
||||
/// 扩展 HttpClient 的 PostJson 方法,用于发送 JSON 格式的数据
|
||||
/// </summary>
|
||||
public static class NetHttpExtension
|
||||
{
|
||||
// Optimized: 统一日志对象
|
||||
private static readonly ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
|
||||
|
||||
// Optimized: 使用静态单例 HttpClient 防止套接字耗尽。注意:生产环境建议配合 SocketsHttpHandler
|
||||
private static readonly HttpClient _httpClient = new HttpClient();
|
||||
|
||||
#region 同步方法 (Sync-over-Async, 谨慎使用)
|
||||
|
||||
/// <summary>
|
||||
/// 发送 JSON 格式的 POST 请求 (同步)
|
||||
/// </summary>
|
||||
public static string PostJson(this object jsonData, string url, int timeout = 2000)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Optimized: 显式调用异步版本并等待,注意在某些上下文可能死锁
|
||||
return PostJsonAsync(jsonData, url, timeout).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_sysLog.Error(ex, "Post 同步请求异常: {Url}", url);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送 JSON 格式的 POST 请求并反序列化 (同步)
|
||||
/// </summary>
|
||||
public static T? PostJson<T>(this object jsonData, string url, int timeout = 2000)
|
||||
{
|
||||
try
|
||||
{
|
||||
var msg = PostJson(jsonData, url, timeout);
|
||||
return string.IsNullOrWhiteSpace(msg) ? default : JsonConvert.DeserializeObject<T>(msg);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_sysLog.Error(ex, "Post 同步请求并解析 JSON 异常: {Url}", url);
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 异步方法 (推荐使用)
|
||||
|
||||
/// <summary>
|
||||
/// 发送 JSON 格式的 POST 请求 (异步)
|
||||
/// </summary>
|
||||
/// <param name="jsonData">要发送的对象</param>
|
||||
/// <param name="url">目标地址</param>
|
||||
/// <param name="timeout">超时(ms)</param>
|
||||
public static async Task<string> PostJsonAsync(this object jsonData, string url, int timeout = 2000)
|
||||
{
|
||||
string jsonString = string.Empty;
|
||||
try
|
||||
{
|
||||
// Optimized: 序列化处理
|
||||
jsonString = jsonData is string s ? s : JsonConvert.SerializeObject(jsonData);
|
||||
using var content = new StringContent(jsonString, Encoding.UTF8, "application/json");
|
||||
|
||||
// Optimized: 设置请求级别的超时处理(HttpClient.Timeout 是全局的,此处利用 CancellationTokenSource)
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeout));
|
||||
|
||||
var response = await _httpClient.PostAsync(url, content, cts.Token);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
|
||||
_sysLog.Warning("Post 请求状态异常: {Url}, StatusCode: {Code}", url, response.StatusCode);
|
||||
return string.Empty;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_sysLog.Warning("Post 请求超时: {Url}, Timeout: {Timeout}ms", url, timeout);
|
||||
return string.Empty;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Modified: 使用结构化日志记录错误
|
||||
_sysLog.Error(ex, "Post 异步请求发生故障: {Url}", url);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送 JSON 格式的 POST 请求并反序列化 (异步)
|
||||
/// </summary>
|
||||
public static async Task<T?> PostJsonAsync<T>(this object jsonData, string url, int timeout = 2000)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await PostJsonAsync(jsonData, url, timeout);
|
||||
if (string.IsNullOrWhiteSpace(result)) return default;
|
||||
|
||||
return JsonConvert.DeserializeObject<T>(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_sysLog.Error(ex, "Post 异步请求解析 JSON 失败: {Url}", url);
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
190
SHH.MjpegPlayer/Core/Extensions/NetPortExtension.cs
Normal file
190
SHH.MjpegPlayer/Core/Extensions/NetPortExtension.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using Ayay.SerilogLogs;
|
||||
using Serilog;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SHH.MjpegPlayer
|
||||
{
|
||||
/// <summary>
|
||||
/// 网口占用检测
|
||||
/// </summary>
|
||||
public static class NetPortExtension
|
||||
{
|
||||
private static ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
|
||||
|
||||
#region IsServerPort
|
||||
|
||||
/// <summary>
|
||||
/// 是否端口
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsServerPort(this int value)
|
||||
{
|
||||
if (value > 0 && value < 65535)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IsPortOccupied
|
||||
|
||||
/// <summary>
|
||||
/// 端口占用检测
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsPortOccupied(this int port)
|
||||
{
|
||||
var ipProperties = IPGlobalProperties.GetIPGlobalProperties();
|
||||
IPEndPoint[] activeListeners = ipProperties.GetActiveTcpListeners();
|
||||
foreach (var endPoint in activeListeners)
|
||||
{
|
||||
if (endPoint.Port == port)
|
||||
return true; // 端口被占用
|
||||
}
|
||||
|
||||
return false; // 端口可用
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetProcessIdByPort
|
||||
|
||||
/// <summary>
|
||||
/// 查询端口占用进程 Pid
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <returns></returns>
|
||||
public static int GetProcessIdByPort(this int port)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (Process proc = new Process())
|
||||
{
|
||||
proc.StartInfo.FileName = "cmd.exe";
|
||||
proc.StartInfo.Arguments = $"/c netstat -ano | findstr :{port}";
|
||||
proc.StartInfo.UseShellExecute = false;
|
||||
proc.StartInfo.RedirectStandardOutput = true;
|
||||
proc.StartInfo.CreateNoWindow = true;
|
||||
|
||||
proc.Start();
|
||||
string output = proc.StandardOutput.ReadToEnd();
|
||||
proc.WaitForExit();
|
||||
|
||||
// 解析输出(示例:TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING 1234)
|
||||
Match match = Regex.Match(output, @":\d+\s+.*?LISTENING\s+(\d+)");
|
||||
if (match.Success && int.TryParse(match.Groups[1].Value, out int pid))
|
||||
return pid;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_sysLog.Warning("查询端口占用进程出错", ex.Message, ex.StackTrace);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetProcessIdByPort
|
||||
|
||||
/// <summary>
|
||||
/// 查询端口占用进程 Pid
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetProcessNameIdByPort(this int port)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (Process proc = new Process())
|
||||
{
|
||||
proc.StartInfo.FileName = "cmd.exe";
|
||||
proc.StartInfo.Arguments = $"/c netstat -ano | findstr :{port}";
|
||||
proc.StartInfo.UseShellExecute = false;
|
||||
proc.StartInfo.RedirectStandardOutput = true;
|
||||
proc.StartInfo.CreateNoWindow = true;
|
||||
|
||||
proc.Start();
|
||||
string output = proc.StandardOutput.ReadToEnd();
|
||||
proc.WaitForExit();
|
||||
|
||||
// 解析输出(示例:TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING 1234)
|
||||
Match match = Regex.Match(output, @":\d+\s+.*?LISTENING\s+(\d+)");
|
||||
if (match.Success && int.TryParse(match.Groups[1].Value, out int pid))
|
||||
{
|
||||
using (Process process = Process.GetProcessById(pid))
|
||||
return process.ProcessName;
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_sysLog.Warning($"查询端口占用进程出错, 错误信息:{ex.Message} {ex.StackTrace}");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PortOccupiedProc
|
||||
|
||||
/// <summary>
|
||||
/// 端口占用检测并杀掉进程
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <returns>返回占用端口清理结果</returns>
|
||||
public static bool PortOccupiedProc(this int port)
|
||||
{
|
||||
if (port.IsPortOccupied())
|
||||
{
|
||||
_sysLog.Warning("服务器端口被占用, Port: {port}");
|
||||
|
||||
// 等待 5 秒
|
||||
for (var i = 0; i < 10; i++)
|
||||
Thread.Sleep(500);
|
||||
|
||||
// 查找占用端口的进程
|
||||
var pid = port.GetProcessIdByPort();
|
||||
if (pid != 0)
|
||||
{
|
||||
// 获取进程名
|
||||
string procName = pid.GetProcessName();
|
||||
|
||||
// 找到占用端口的进程
|
||||
_sysLog.Warning($"找到占用端口进程 Pid: {pid} 进程名:{procName}, 5 秒后即将尝试杀掉占用端口的进程.");
|
||||
|
||||
// 等待 5 秒
|
||||
for (var i = 0; i < 10; i++)
|
||||
Thread.Sleep(500);
|
||||
|
||||
// 杀掉指定进程
|
||||
if (!pid.KillProcessByPid(procName))
|
||||
{
|
||||
// 退出应用
|
||||
return false;
|
||||
}
|
||||
|
||||
// 等待 2 秒
|
||||
Thread.Sleep(2000);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
197
SHH.MjpegPlayer/Core/Extensions/ProcessExtension.cs
Normal file
197
SHH.MjpegPlayer/Core/Extensions/ProcessExtension.cs
Normal file
@@ -0,0 +1,197 @@
|
||||
using Ayay.SerilogLogs;
|
||||
using Serilog;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace SHH.MjpegPlayer
|
||||
{
|
||||
/// <summary>
|
||||
/// 进程扩展
|
||||
/// </summary>
|
||||
public static class ProcessExtension
|
||||
{
|
||||
private static ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
|
||||
|
||||
#region GetProcessName
|
||||
|
||||
/// <summary>
|
||||
/// 获取进程名称
|
||||
/// </summary>
|
||||
/// <param name="pid"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetProcessName(this int pid)
|
||||
{
|
||||
try
|
||||
{
|
||||
var process = Process.GetProcessById(pid);
|
||||
return process.ProcessName;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_sysLog.Error(ex, "查询进程名出错, Pid: {Pid}", pid);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region KillProcessByPid
|
||||
|
||||
/// <summary>
|
||||
/// 杀掉进程
|
||||
/// </summary>
|
||||
/// <param name="pid"></param>
|
||||
/// <param name="procName"></param>
|
||||
/// <returns></returns>
|
||||
public static bool KillProcessByPid(this int pid, string procName = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
var process = Process.GetProcessById(pid);
|
||||
|
||||
if (process != null)
|
||||
{
|
||||
procName = process.ProcessName;
|
||||
process.Kill();
|
||||
|
||||
_sysLog.Warning("拒绝停止高权限系统进程: {Pid} - {Name}", pid, process.ProcessName);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 找不到 ID 对应的进程,应该是进异常不会进这里
|
||||
_sysLog.Information("成功杀掉进程 - Pid: {Pid}", pid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
_sysLog.Warning("杀掉进程失败,Pid: {Pid} 不存在", pid);
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_sysLog.Error(ex, "杀掉进程异常, Pid: {Pid}", pid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region KillProcessByName
|
||||
|
||||
/// <summary>
|
||||
/// 杀掉进程
|
||||
/// </summary>
|
||||
/// <param name="pid"></param>
|
||||
/// <param name="procName"></param>
|
||||
/// <returns></returns>
|
||||
public static int KillProcessByName(this string procName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(procName)) return 0;
|
||||
|
||||
int killCount = 0;
|
||||
try
|
||||
{
|
||||
var processes = Process.GetProcessesByName(procName);
|
||||
foreach (var proc in processes)
|
||||
{
|
||||
using (proc) // Optimized: 确保 Process 资源被释放
|
||||
{
|
||||
try
|
||||
{
|
||||
if (proc.IsHighPrivilegeProcess()) continue;
|
||||
|
||||
int currentId = proc.Id;
|
||||
proc.Kill();
|
||||
killCount++;
|
||||
_sysLog.Information("成功通过名称杀掉进程 - Pid: {Pid}, Name: {Name}", currentId, procName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_sysLog.Error(ex, "通过名称杀掉单个进程失败: {Name}", procName);
|
||||
}
|
||||
}
|
||||
}
|
||||
return killCount;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_sysLog.Error(ex, "通过名称杀掉进程列表异常: {Name}", procName);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region StartProcess
|
||||
|
||||
/// <summary>
|
||||
/// 开启进程
|
||||
/// </summary>
|
||||
/// <param name="procPath"></param>
|
||||
public static bool StartProcess(this string procPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(procPath))
|
||||
{
|
||||
_sysLog.Error("启动进程失败,路径不存在: {Path}", procPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Optimized: 显式记录启动行为
|
||||
var process = Process.Start(procPath);
|
||||
if (process != null)
|
||||
{
|
||||
_sysLog.Information("进程启动成功: {Path}, Pid: {Pid}", procPath, process.Id);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_sysLog.Error(ex, "启动进程异常: {Path}", procPath);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IsHighPrivilegeProcess
|
||||
|
||||
/// <summary>
|
||||
/// 检测是否高权限等级
|
||||
/// </summary>
|
||||
/// <param name="proc"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsHighPrivilegeProcess(this Process proc)
|
||||
{
|
||||
// 典型的高权限进程列表(可根据实际需求扩展)
|
||||
string[] highPrivilegeProcesses = new[] {
|
||||
"System", "smss.exe", "csrss.exe", "wininit.exe", "services.exe",
|
||||
"lsass.exe", "winlogon.exe", "spoolsv.exe", "svchost.exe",
|
||||
"csrss", "msedge"
|
||||
};
|
||||
|
||||
// 检查进程名称是否在高权限列表中
|
||||
foreach (string name in highPrivilegeProcesses)
|
||||
{
|
||||
if (proc.ProcessName.Equals(name, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查进程是否属于系统会话(Session 0)
|
||||
try
|
||||
{
|
||||
return proc.SessionId == 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 如果无法获取 SessionId,保守返回 true
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user