A:增加窗口网速展示
This commit is contained in:
1 parent
d484c85abb
commit
c1d9e2c068
28 files changed
+2107
-479
No files matched your search
@@ -1,16 +1,13 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Common;
|
||||
using Common.Logger;
|
||||
using Common.Net.WebSocketConn;
|
||||
using Common.Utils;
|
||||
using Infrastructure.Models;
|
||||
using Infrastructure.utils;
|
||||
using Utils.ETW.Etw;
|
||||
|
||||
namespace Shell;
|
||||
namespace Application;
|
||||
|
||||
public class DispatchEngine
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Web;
|
||||
using Common.Logger;
|
||||
using Common.Utils;
|
||||
using Fleck;
|
||||
|
||||
@@ -105,6 +106,7 @@ public class HttpServer
|
||||
/// 便捷路由方法
|
||||
/// </summary>
|
||||
public void Get(string pattern, RequestHandler handler) => AddRoute(HttpMethod.GET, pattern, handler);
|
||||
|
||||
public void Post(string pattern, RequestHandler handler) => AddRoute(HttpMethod.POST, pattern, handler);
|
||||
public void Put(string pattern, RequestHandler handler) => AddRoute(HttpMethod.PUT, pattern, handler);
|
||||
public void Delete(string pattern, RequestHandler handler) => AddRoute(HttpMethod.DELETE, pattern, handler);
|
||||
@@ -116,7 +118,7 @@ public class HttpServer
|
||||
{
|
||||
_listener.Start();
|
||||
_isRunning = true;
|
||||
Console.WriteLine($"HTTP Server started at {_prefix}");
|
||||
Log.Info($"HTTP Server started at {_prefix}");
|
||||
|
||||
while (_isRunning && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
@@ -135,7 +137,7 @@ public class HttpServer
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error accepting request: {ex.Message}");
|
||||
Log.Info($"Error accepting request: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,7 +148,7 @@ public class HttpServer
|
||||
_isRunning = false;
|
||||
_listener.Stop();
|
||||
_listener.Close();
|
||||
Console.WriteLine("HTTP Server stopped");
|
||||
Log.Info("HTTP Server stopped");
|
||||
}
|
||||
|
||||
// 处理请求
|
||||
@@ -179,7 +181,7 @@ public class HttpServer
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Request handling error: {ex}");
|
||||
Log.Info($"Request handling error: {ex}");
|
||||
await SendInternalServerError(listenerContext.Response, ex);
|
||||
}
|
||||
finally
|
||||
@@ -286,7 +288,7 @@ public static class HttpListenerResponseExtensions
|
||||
{
|
||||
response.ContentType = "application/json; charset=utf-8";
|
||||
var json = JsonHelper.ToJson(data);
|
||||
|
||||
|
||||
var buffer = Encoding.UTF8.GetBytes(json);
|
||||
response.ContentLength64 = buffer.Length;
|
||||
await response.OutputStream.WriteAsync(buffer, 0, buffer.Length);
|
||||
@@ -315,7 +317,6 @@ public static class Middlewares
|
||||
// CORS 中间件
|
||||
public static async Task<bool> Cors(HttpContext context)
|
||||
{
|
||||
|
||||
// 允许所有来源(生产环境应该指定具体域名)
|
||||
string? origin = context.Request.Headers["Origin"];
|
||||
context.Response.Headers.Add("Access-Control-Allow-Origin", !string.IsNullOrEmpty(origin) ? origin : "*");
|
||||
@@ -324,7 +325,7 @@ public static class Middlewares
|
||||
context.Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH");
|
||||
|
||||
// 关键:允许的请求头(包括你的uuid头)
|
||||
context.Response.Headers.Add("Access-Control-Allow-Headers",
|
||||
context.Response.Headers.Add("Access-Control-Allow-Headers",
|
||||
"Content-Type, Authorization, X-Requested-With, uuid, Accept, Origin");
|
||||
|
||||
// 允许携带凭据(如果需要)
|
||||
@@ -349,7 +350,7 @@ public static class Middlewares
|
||||
public static async Task<bool> RequestLogging(HttpContext context)
|
||||
{
|
||||
var startTime = DateTime.UtcNow;
|
||||
Console.WriteLine($"[{startTime:yyyy-MM-dd HH:mm:ss}] {context.Request.HttpMethod} {context.Request.Url}");
|
||||
Log.Info($"[{startTime:yyyy-MM-dd HH:mm:ss}] {context.Request.HttpMethod} {context.Request.Url}");
|
||||
|
||||
// 继续处理
|
||||
return await Task.FromResult(true);
|
||||
|
||||
@@ -142,6 +142,7 @@ public class WebSocketManager
|
||||
{
|
||||
gzip.Write(inputBytes, 0, inputBytes.Length);
|
||||
}
|
||||
|
||||
return outputStream.ToArray();
|
||||
}
|
||||
|
||||
@@ -243,7 +244,7 @@ public class WebSocketManager
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("连接缺少userId参数");
|
||||
Log.Info("连接缺少userId参数");
|
||||
socket.Close();
|
||||
}
|
||||
}
|
||||
@@ -268,7 +269,7 @@ public class WebSocketManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"解析userId时出错: {ex.Message}");
|
||||
Log.Info($"解析userId时出错: {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -302,7 +303,7 @@ public class WebSocketManager
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine(DateTime.Now);
|
||||
Log.Info($"{DateTime.Now}");
|
||||
var commandMessage = JsonHelper.FromJson<CommandMessage>(message);
|
||||
if (commandMessage != null && !string.IsNullOrEmpty(commandMessage.Command))
|
||||
{
|
||||
@@ -534,11 +535,11 @@ public static class WebSocketManagerUsageExample
|
||||
|
||||
private static void OnConnectionClosed(ConnectionClosedEventArgs args)
|
||||
{
|
||||
Console.WriteLine($"连接已关闭:");
|
||||
Console.WriteLine($" Socket ID: {args.SocketId}");
|
||||
Console.WriteLine($" UUID: {args.Uuid ?? "未知"}");
|
||||
Console.WriteLine($" 关闭时间: {args.ClosedAt}");
|
||||
Console.WriteLine($" 关闭原因: {args.Reason ?? "未知"}");
|
||||
Log.Info($"连接已关闭:");
|
||||
Log.Info($" Socket ID: {args.SocketId}");
|
||||
Log.Info($" UUID: {args.Uuid ?? "未知"}");
|
||||
Log.Info($" 关闭时间: {args.ClosedAt}");
|
||||
Log.Info($" 关闭原因: {args.Reason ?? "未知"}");
|
||||
|
||||
// 在这里可以执行清理逻辑,比如:
|
||||
// - 清理用户相关的缓存数据
|
||||
@@ -556,6 +557,6 @@ public static class WebSocketManagerUsageExample
|
||||
private static void CleanupUserData(string uuid)
|
||||
{
|
||||
// 实现用户数据清理逻辑
|
||||
Console.WriteLine($"清理用户 {uuid} 的相关数据");
|
||||
Log.Info($"清理用户 {uuid} 的相关数据");
|
||||
}
|
||||
}
|
||||
@@ -1,79 +1,9 @@
|
||||
using NetVizor.Common.Utils;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Common.Utils;
|
||||
|
||||
namespace Common.Test
|
||||
{
|
||||
[TestFixture]
|
||||
public class NetworkInfoUtilTest
|
||||
{
|
||||
[Test]
|
||||
public void TestGetAllInterfaces()
|
||||
{
|
||||
TestContext.Progress.WriteLine("--- Network Interface List ---");
|
||||
|
||||
// 1. Get all network interface information
|
||||
var interfaces = NetworkInfoUtil.GetAllInterfaces();
|
||||
Assert.IsNotNull(interfaces);
|
||||
Assert.IsTrue(interfaces.Count > 0, "No network interfaces found.");
|
||||
|
||||
foreach (var iface in interfaces)
|
||||
{
|
||||
TestContext.Progress.WriteLine($"Index: {iface.Index}, Alias: {iface.Alias}, Status: {iface.Status}");
|
||||
TestContext.Progress.WriteLine($" Description: {iface.Description}");
|
||||
TestContext.Progress.WriteLine($" MAC: {iface.MacAddress}, Type: {iface.Type}");
|
||||
TestContext.Progress.WriteLine(
|
||||
$" Bytes Sent: {iface.BytesSent}, Bytes Received: {iface.BytesReceived}\n");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMonitorActiveInterfaceSpeed()
|
||||
{
|
||||
var interfaces = NetworkInfoUtil.GetAllInterfaces();
|
||||
|
||||
// 2. Select an active interface to monitor real-time speed
|
||||
var activeInterface = interfaces.FirstOrDefault(i =>
|
||||
i.Status == System.Net.NetworkInformation.OperationalStatus.Up
|
||||
&& i.Type != System.Net.NetworkInformation.NetworkInterfaceType.Loopback
|
||||
&& i.Type != System.Net.NetworkInformation.NetworkInterfaceType.Tunnel);
|
||||
|
||||
if (activeInterface == null)
|
||||
{
|
||||
Assert.Inconclusive("No active, non-loopback interface found to monitor. Test skipped.");
|
||||
return; // Skip test if no suitable interface is found
|
||||
}
|
||||
|
||||
TestContext.Progress.WriteLine($"--- Monitoring real-time speed for: {activeInterface.Alias} ---");
|
||||
var speedMonitor = new NetworkSpeedMonitor(activeInterface);
|
||||
|
||||
// 3. Periodically update and display the network speed
|
||||
TestContext.Progress.WriteLine("Monitoring for 5 seconds...");
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
Thread.Sleep(1000); // Update every second
|
||||
speedMonitor.Update();
|
||||
|
||||
string downSpeed = FormatSpeed(speedMonitor.DownloadSpeedBps);
|
||||
string upSpeed = FormatSpeed(speedMonitor.UploadSpeedBps);
|
||||
|
||||
TestContext.Progress.WriteLine(
|
||||
$"Time: {DateTime.Now:HH:mm:ss} | Download: {downSpeed} | Upload: {upSpeed}");
|
||||
}
|
||||
|
||||
Assert.IsTrue(speedMonitor.DownloadSpeedBps >= 0);
|
||||
Assert.IsTrue(speedMonitor.UploadSpeedBps >= 0);
|
||||
}
|
||||
|
||||
private static string FormatSpeed(double bytesPerSecond)
|
||||
{
|
||||
if (bytesPerSecond < 1024)
|
||||
return $"{bytesPerSecond:F2} B/s";
|
||||
if (bytesPerSecond < 1024 * 1024)
|
||||
return $"{bytesPerSecond / 1024:F2} KB/s";
|
||||
return $"{bytesPerSecond / (1024 * 1024):F2} MB/s";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using Serilog;
|
||||
using Common.Logger;
|
||||
|
||||
namespace Common.FunExpand;
|
||||
namespace Common.ExpandFun;
|
||||
|
||||
public static class StringExpand
|
||||
{
|
||||
@@ -61,4 +61,4 @@ public static class StringExpand
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Threading;
|
||||
using Common.Logger;
|
||||
|
||||
namespace Common.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// 基础网络监控帮助类 - 专注于网速计算
|
||||
/// </summary>
|
||||
public static class BasicNetworkMonitor
|
||||
{
|
||||
#region 数据模型
|
||||
|
||||
/// <summary>
|
||||
/// 基础网络接口信息
|
||||
/// </summary>
|
||||
public class BasicNetworkInterface
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public long BytesReceived { get; set; }
|
||||
public long BytesSent { get; set; }
|
||||
public bool IsConnected { get; set; }
|
||||
public NetworkInterfaceType Type { get; set; }
|
||||
public string TypeDescription { get; set; } = string.Empty;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 网络统计快照
|
||||
/// </summary>
|
||||
private class NetworkSnapshot
|
||||
{
|
||||
public long BytesReceived { get; set; }
|
||||
public long BytesSent { get; set; }
|
||||
public DateTime Timestamp { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 私有字段
|
||||
|
||||
// 使用线程安全的字典存储历史统计数据
|
||||
private static readonly ConcurrentDictionary<string, NetworkSnapshot> PreviousStats = new();
|
||||
private static readonly object LockObject = new();
|
||||
private static volatile bool _isInitialized = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 公共方法
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有网络接口
|
||||
/// </summary>
|
||||
/// <param name="includeLoopback">是否包含环回接口</param>
|
||||
/// <returns>网络接口列表</returns>
|
||||
public static List<BasicNetworkInterface> GetNetworkInterfaces(bool includeLoopback = false)
|
||||
{
|
||||
var interfaces = new List<BasicNetworkInterface>();
|
||||
|
||||
try
|
||||
{
|
||||
var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
|
||||
|
||||
foreach (var ni in networkInterfaces)
|
||||
{
|
||||
// 根据参数决定是否跳过环回接口
|
||||
if (!includeLoopback && ni.NetworkInterfaceType == NetworkInterfaceType.Loopback)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var basicInterface = CreateBasicInterface(ni);
|
||||
if (basicInterface != null)
|
||||
{
|
||||
interfaces.Add(basicInterface);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error4Ctx($"获取网络接口时发生错误: {ex.Message}");
|
||||
}
|
||||
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据接口名称计算网速
|
||||
/// </summary>
|
||||
/// <param name="interfaceName">接口名称</param>
|
||||
/// <returns>网速信息</returns>
|
||||
public static NetworkSpeed CalculateSpeedByName(string interfaceName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(interfaceName))
|
||||
{
|
||||
Log.Warning("接口名称不能为空");
|
||||
return new NetworkSpeed();
|
||||
}
|
||||
|
||||
var interfaces = GetNetworkInterfaces();
|
||||
var targetInterface = interfaces.FirstOrDefault(i =>
|
||||
string.Equals(i.Name, interfaceName, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(i.Id, interfaceName, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (targetInterface == null)
|
||||
{
|
||||
Log.Warning($"未找到接口: {interfaceName}");
|
||||
return new NetworkSpeed();
|
||||
}
|
||||
|
||||
return CalculateSpeed(targetInterface);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据接口ID计算网速
|
||||
/// </summary>
|
||||
/// <param name="interfaceId">接口ID</param>
|
||||
/// <returns>网速信息</returns>
|
||||
public static NetworkSpeed CalculateSpeedById(string interfaceId)
|
||||
{
|
||||
return CalculateSpeedByName(interfaceId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算所有网络接口的总网速
|
||||
/// </summary>
|
||||
/// <returns>总网速信息</returns>
|
||||
public static NetworkSpeed CalculateTotalSpeed()
|
||||
{
|
||||
var currentInterfaces = GetNetworkInterfaces();
|
||||
var now = DateTime.Now;
|
||||
var totalSpeed = new NetworkSpeed();
|
||||
|
||||
// 确保初始化
|
||||
EnsureInitialized(currentInterfaces, now);
|
||||
|
||||
foreach (var currentInterface in currentInterfaces)
|
||||
{
|
||||
var speed = CalculateSpeed(currentInterface, now);
|
||||
totalSpeed.DownloadSpeed += speed.DownloadSpeed;
|
||||
totalSpeed.UploadSpeed += speed.UploadSpeed;
|
||||
}
|
||||
|
||||
Log.Info($"总网速 - 下载: {totalSpeed.DownloadSpeedText}, 上传: {totalSpeed.UploadSpeedText}");
|
||||
return totalSpeed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置统计数据
|
||||
/// </summary>
|
||||
public static void ResetStatistics()
|
||||
{
|
||||
lock (LockObject)
|
||||
{
|
||||
Log.Info("重置网络统计数据");
|
||||
PreviousStats.Clear();
|
||||
_isInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取接口的历史统计数据
|
||||
/// </summary>
|
||||
/// <param name="interfaceId">接口ID</param>
|
||||
/// <returns>是否存在历史数据</returns>
|
||||
public static bool HasHistoricalData(string interfaceId)
|
||||
{
|
||||
return !string.IsNullOrEmpty(interfaceId) && PreviousStats.ContainsKey(interfaceId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取已连接的网络接口
|
||||
/// </summary>
|
||||
/// <returns>已连接的网络接口列表</returns>
|
||||
public static List<BasicNetworkInterface> GetConnectedNetworkInterfaces()
|
||||
{
|
||||
return GetNetworkInterfaces()
|
||||
.Where(i => i.IsConnected)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 私有方法
|
||||
|
||||
/// <summary>
|
||||
/// 创建基础网络接口信息
|
||||
/// </summary>
|
||||
/// <param name="ni">网络接口</param>
|
||||
/// <returns>基础网络接口信息</returns>
|
||||
private static BasicNetworkInterface CreateBasicInterface(NetworkInterface ni)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取IPv4统计信息
|
||||
var stats = ni.GetIPv4Statistics();
|
||||
|
||||
return new BasicNetworkInterface
|
||||
{
|
||||
Name = ni.Name,
|
||||
Description = ni.Description,
|
||||
Id = ni.Id,
|
||||
BytesReceived = stats.BytesReceived,
|
||||
BytesSent = stats.BytesSent,
|
||||
IsConnected = ni.OperationalStatus == OperationalStatus.Up,
|
||||
Type = ni.NetworkInterfaceType,
|
||||
TypeDescription = NetworkHelper.GetInterfaceTypeDescription(ni.NetworkInterfaceType)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning($"创建接口 {ni.Name} 基础信息时发生错误: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算指定接口的网速
|
||||
/// </summary>
|
||||
/// <param name="currentInterface">当前接口信息</param>
|
||||
/// <param name="now">当前时间(可选)</param>
|
||||
/// <returns>网速信息</returns>
|
||||
private static NetworkSpeed CalculateSpeed(BasicNetworkInterface currentInterface, DateTime? now = null)
|
||||
{
|
||||
if (currentInterface == null)
|
||||
{
|
||||
return new NetworkSpeed();
|
||||
}
|
||||
|
||||
var timestamp = now ?? DateTime.Now;
|
||||
var key = currentInterface.Id;
|
||||
var speed = new NetworkSpeed();
|
||||
|
||||
Log.Debug($"计算接口 {currentInterface.Name} 的网速...");
|
||||
|
||||
// 确保初始化
|
||||
if (!_isInitialized)
|
||||
{
|
||||
EnsureInitialized(new[] { currentInterface }, timestamp);
|
||||
return speed;
|
||||
}
|
||||
|
||||
// 获取或创建前一次的快照
|
||||
var currentSnapshot = new NetworkSnapshot
|
||||
{
|
||||
BytesReceived = currentInterface.BytesReceived,
|
||||
BytesSent = currentInterface.BytesSent,
|
||||
Timestamp = timestamp
|
||||
};
|
||||
|
||||
if (PreviousStats.TryGetValue(key, out var previousSnapshot))
|
||||
{
|
||||
var timeDiff = (timestamp - previousSnapshot.Timestamp).TotalSeconds;
|
||||
|
||||
if (NetworkHelper.IsValidTimeInterval(timeDiff))
|
||||
{
|
||||
// 计算速度
|
||||
speed.DownloadSpeed = NetworkHelper.CalculateSpeed(
|
||||
currentSnapshot.BytesReceived,
|
||||
previousSnapshot.BytesReceived,
|
||||
timeDiff);
|
||||
|
||||
speed.UploadSpeed = NetworkHelper.CalculateSpeed(
|
||||
currentSnapshot.BytesSent,
|
||||
previousSnapshot.BytesSent,
|
||||
timeDiff);
|
||||
|
||||
Log.Debug(
|
||||
$"接口 {currentInterface.Name} - 下载: {speed.DownloadSpeedText}, 上传: {speed.UploadSpeedText}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug($"时间间隔过短 ({timeDiff:F2}秒),跳过速度计算");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug($"接口 {currentInterface.Name} 首次记录数据");
|
||||
}
|
||||
|
||||
// 更新快照
|
||||
PreviousStats.AddOrUpdate(key, currentSnapshot, (k, v) => currentSnapshot);
|
||||
|
||||
return speed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保系统已初始化
|
||||
/// </summary>
|
||||
/// <param name="interfaces">接口列表</param>
|
||||
/// <param name="timestamp">时间戳</param>
|
||||
private static void EnsureInitialized(IEnumerable<BasicNetworkInterface> interfaces, DateTime timestamp)
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (LockObject)
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Info("初始化网络监控统计数据");
|
||||
|
||||
foreach (var iface in interfaces)
|
||||
{
|
||||
if (iface != null)
|
||||
{
|
||||
PreviousStats.TryAdd(iface.Id, new NetworkSnapshot
|
||||
{
|
||||
BytesReceived = iface.BytesReceived,
|
||||
BytesSent = iface.BytesSent,
|
||||
Timestamp = timestamp
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_isInitialized = true;
|
||||
Log.Info($"已初始化 {PreviousStats.Count} 个网络接口的统计数据");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理过期的统计数据
|
||||
/// </summary>
|
||||
/// <param name="maxAge">最大保留时间</param>
|
||||
public static void CleanupOldStatistics(TimeSpan maxAge)
|
||||
{
|
||||
var cutoffTime = DateTime.Now - maxAge;
|
||||
var expiredKeys = new List<string>();
|
||||
|
||||
foreach (var kvp in PreviousStats)
|
||||
{
|
||||
if (kvp.Value.Timestamp < cutoffTime)
|
||||
{
|
||||
expiredKeys.Add(kvp.Key);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var key in expiredKeys)
|
||||
{
|
||||
PreviousStats.TryRemove(key, out _);
|
||||
}
|
||||
|
||||
if (expiredKeys.Count > 0)
|
||||
{
|
||||
Log.Info($"清理了 {expiredKeys.Count} 个过期的网络统计记录");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,420 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Common.Logger;
|
||||
|
||||
namespace Common.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// 增强的网络监控帮助类 - 获取详细的网络信息
|
||||
/// </summary>
|
||||
public static class EnhancedNetworkMonitor
|
||||
{
|
||||
#region 数据模型
|
||||
|
||||
/// <summary>
|
||||
/// 详细的网络接口信息
|
||||
/// </summary>
|
||||
public class DetailedNetworkInterface
|
||||
{
|
||||
// 基本信息
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public bool IsConnected { get; set; }
|
||||
public NetworkInterfaceType Type { get; set; }
|
||||
public string TypeDescription { get; set; } = string.Empty;
|
||||
|
||||
// 物理信息
|
||||
public string PhysicalAddress { get; set; } = string.Empty;
|
||||
public long Speed { get; set; }
|
||||
public string SpeedText { get; set; } = string.Empty;
|
||||
|
||||
// IP配置
|
||||
public List<string> IPAddresses { get; set; } = new();
|
||||
public List<string> SubnetMasks { get; set; } = new();
|
||||
public List<string> Gateways { get; set; } = new();
|
||||
public List<string> DnsServers { get; set; } = new();
|
||||
public List<string> IPv6Addresses { get; set; } = new();
|
||||
|
||||
// 统计信息
|
||||
public long BytesReceived { get; set; }
|
||||
public long BytesSent { get; set; }
|
||||
public long PacketsReceived { get; set; }
|
||||
public long PacketsSent { get; set; }
|
||||
public long ErrorsReceived { get; set; }
|
||||
public long ErrorsSent { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name} - {Description}";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 公共方法
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有网络接口的详细信息
|
||||
/// </summary>
|
||||
/// <param name="includeLoopback">是否包含环回接口</param>
|
||||
/// <returns>网络接口列表</returns>
|
||||
public static List<DetailedNetworkInterface> GetDetailedNetworkInterfaces(bool includeLoopback = false)
|
||||
{
|
||||
var interfaces = new List<DetailedNetworkInterface>();
|
||||
|
||||
try
|
||||
{
|
||||
var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
|
||||
|
||||
foreach (var ni in networkInterfaces)
|
||||
{
|
||||
// 根据参数决定是否跳过环回接口
|
||||
if (!includeLoopback && ni.NetworkInterfaceType == NetworkInterfaceType.Loopback)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var detailedInterface = CreateDetailedInterface(ni);
|
||||
if (detailedInterface != null)
|
||||
{
|
||||
interfaces.Add(detailedInterface);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error4Ctx($"获取网络接口详细信息时发生错误: {ex.Message}");
|
||||
}
|
||||
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取已连接的网络接口
|
||||
/// </summary>
|
||||
/// <returns>已连接的网络接口列表</returns>
|
||||
public static List<DetailedNetworkInterface> GetConnectedNetworkInterfaces()
|
||||
{
|
||||
return GetDetailedNetworkInterfaces()
|
||||
.Where(i => i.IsConnected)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据名称获取网络接口
|
||||
/// </summary>
|
||||
/// <param name="name">接口名称</param>
|
||||
/// <returns>网络接口信息,未找到则返回null</returns>
|
||||
public static DetailedNetworkInterface GetNetworkInterfaceByName(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetDetailedNetworkInterfaces()
|
||||
.FirstOrDefault(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取外网IP地址
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>外网IP信息</returns>
|
||||
public static async Task<ExternalIPInfo> GetExternalIPAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await NetworkHelper.GetExternalIPAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前主要网络接口的详细信息
|
||||
/// </summary>
|
||||
/// <returns>主要网络接口信息,如果没有则返回null</returns>
|
||||
public static DetailedNetworkInterface GetPrimaryNetworkInterface()
|
||||
{
|
||||
var connectedInterfaces = GetConnectedNetworkInterfaces();
|
||||
|
||||
// 优先选择有网关的以太网接口
|
||||
var primaryInterface = connectedInterfaces
|
||||
.Where(i => i.Type == NetworkInterfaceType.Ethernet && i.Gateways.Any())
|
||||
.FirstOrDefault();
|
||||
|
||||
// 如果没有以太网,选择有网关的Wi-Fi接口
|
||||
if (primaryInterface == null)
|
||||
{
|
||||
primaryInterface = connectedInterfaces
|
||||
.Where(i => i.Type == NetworkInterfaceType.Wireless80211 && i.Gateways.Any())
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
// 如果还没有,选择任何有IP地址的接口
|
||||
if (primaryInterface == null)
|
||||
{
|
||||
primaryInterface = connectedInterfaces
|
||||
.Where(i => i.IPAddresses.Any())
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
return primaryInterface;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取完整的网络状态报告
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>网络状态报告</returns>
|
||||
public static async Task<string> GetNetworkStatusReportAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
try
|
||||
{
|
||||
var interfaces = GetConnectedNetworkInterfaces();
|
||||
var externalIP = await GetExternalIPAsync(cancellationToken);
|
||||
var uptime = NetworkHelper.GetSystemUptime();
|
||||
|
||||
// 报告头部
|
||||
sb.AppendLine("=== 网络状态详细报告 ===");
|
||||
sb.AppendLine($"报告生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
|
||||
sb.AppendLine($"系统运行时间: {NetworkHelper.FormatUptime(uptime)}");
|
||||
sb.AppendLine($"外网IPv4地址: {externalIP.IPv4}");
|
||||
sb.AppendLine($"外网IPv6地址: {externalIP.IPv6}");
|
||||
sb.AppendLine($"活跃网络接口数: {interfaces.Count}");
|
||||
sb.AppendLine();
|
||||
|
||||
// 接口详情
|
||||
foreach (var iface in interfaces)
|
||||
{
|
||||
AppendInterfaceInfo(sb, iface);
|
||||
}
|
||||
|
||||
// 统计汇总
|
||||
AppendNetworkSummary(sb, interfaces);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error4Ctx($"生成网络状态报告时发生错误: {ex.Message}");
|
||||
sb.AppendLine($"生成报告时发生错误: {ex.Message}");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 私有方法
|
||||
|
||||
/// <summary>
|
||||
/// 创建详细的网络接口信息
|
||||
/// </summary>
|
||||
/// <param name="ni">网络接口</param>
|
||||
/// <returns>详细网络接口信息</returns>
|
||||
private static DetailedNetworkInterface CreateDetailedInterface(NetworkInterface ni)
|
||||
{
|
||||
try
|
||||
{
|
||||
var detailedInterface = new DetailedNetworkInterface
|
||||
{
|
||||
// 基本信息
|
||||
Name = ni.Name,
|
||||
Description = ni.Description,
|
||||
Id = ni.Id,
|
||||
IsConnected = ni.OperationalStatus == OperationalStatus.Up,
|
||||
Type = ni.NetworkInterfaceType,
|
||||
TypeDescription = NetworkHelper.GetInterfaceTypeDescription(ni.NetworkInterfaceType),
|
||||
|
||||
// 物理信息
|
||||
PhysicalAddress = NetworkHelper.FormatMacAddress(ni.GetPhysicalAddress()),
|
||||
Speed = ni.Speed,
|
||||
SpeedText = NetworkHelper.FormatInterfaceSpeed(ni.Speed)
|
||||
};
|
||||
|
||||
// 获取统计信息
|
||||
PopulateStatistics(detailedInterface, ni);
|
||||
|
||||
// 获取IP配置信息
|
||||
PopulateIPConfiguration(detailedInterface, ni);
|
||||
|
||||
return detailedInterface;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning($"创建接口 {ni.Name} 的详细信息时发生错误: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 填充统计信息
|
||||
/// </summary>
|
||||
/// <param name="detailedInterface">详细接口信息</param>
|
||||
/// <param name="ni">网络接口</param>
|
||||
private static void PopulateStatistics(DetailedNetworkInterface detailedInterface, NetworkInterface ni)
|
||||
{
|
||||
try
|
||||
{
|
||||
var stats = ni.GetIPv4Statistics();
|
||||
detailedInterface.BytesReceived = stats.BytesReceived;
|
||||
detailedInterface.BytesSent = stats.BytesSent;
|
||||
detailedInterface.PacketsReceived = stats.UnicastPacketsReceived;
|
||||
detailedInterface.PacketsSent = stats.UnicastPacketsSent;
|
||||
detailedInterface.ErrorsReceived = stats.IncomingPacketsWithErrors;
|
||||
detailedInterface.ErrorsSent = stats.OutgoingPacketsWithErrors;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning($"获取接口 {ni.Name} 统计信息时发生错误: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 填充IP配置信息
|
||||
/// </summary>
|
||||
/// <param name="detailedInterface">详细接口信息</param>
|
||||
/// <param name="ni">网络接口</param>
|
||||
private static void PopulateIPConfiguration(DetailedNetworkInterface detailedInterface, NetworkInterface ni)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ipProps = ni.GetIPProperties();
|
||||
|
||||
// 处理单播地址
|
||||
foreach (var unicast in ipProps.UnicastAddresses)
|
||||
{
|
||||
switch (unicast.Address.AddressFamily)
|
||||
{
|
||||
case AddressFamily.InterNetwork:
|
||||
detailedInterface.IPAddresses.Add(unicast.Address.ToString());
|
||||
var mask = NetworkHelper.CalculateSubnetMask(unicast);
|
||||
if (!string.IsNullOrEmpty(mask))
|
||||
{
|
||||
detailedInterface.SubnetMasks.Add(mask);
|
||||
}
|
||||
|
||||
break;
|
||||
case AddressFamily.InterNetworkV6:
|
||||
detailedInterface.IPv6Addresses.Add(unicast.Address.ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理网关
|
||||
foreach (var gateway in ipProps.GatewayAddresses)
|
||||
{
|
||||
if (gateway.Address.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
detailedInterface.Gateways.Add(gateway.Address.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// 处理DNS服务器
|
||||
foreach (var dns in ipProps.DnsAddresses)
|
||||
{
|
||||
if (dns.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
detailedInterface.DnsServers.Add(dns.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning($"获取接口 {ni.Name} IP配置时发生错误: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加接口信息到报告
|
||||
/// </summary>
|
||||
/// <param name="sb">字符串构建器</param>
|
||||
/// <param name="iface">接口信息</param>
|
||||
private static void AppendInterfaceInfo(StringBuilder sb, DetailedNetworkInterface iface)
|
||||
{
|
||||
sb.AppendLine($"接口名称: {iface.Name}");
|
||||
sb.AppendLine($"接口描述: {iface.Description}");
|
||||
sb.AppendLine($"连接类型: {iface.TypeDescription}");
|
||||
sb.AppendLine($"接口速度: {iface.SpeedText}");
|
||||
sb.AppendLine($"物理地址: {iface.PhysicalAddress}");
|
||||
sb.AppendLine($"连接状态: {(iface.IsConnected ? "已连接" : "未连接")}");
|
||||
|
||||
if (iface.IPAddresses.Any())
|
||||
{
|
||||
sb.AppendLine($"IPv4地址: {string.Join(", ", iface.IPAddresses)}");
|
||||
}
|
||||
|
||||
if (iface.SubnetMasks.Any())
|
||||
{
|
||||
sb.AppendLine($"子网掩码: {string.Join(", ", iface.SubnetMasks)}");
|
||||
}
|
||||
|
||||
if (iface.Gateways.Any())
|
||||
{
|
||||
sb.AppendLine($"默认网关: {string.Join(", ", iface.Gateways)}");
|
||||
}
|
||||
|
||||
if (iface.DnsServers.Any())
|
||||
{
|
||||
sb.AppendLine($"DNS服务器: {string.Join(", ", iface.DnsServers)}");
|
||||
}
|
||||
|
||||
if (iface.IPv6Addresses.Any())
|
||||
{
|
||||
sb.AppendLine($"IPv6地址: {string.Join(", ", iface.IPv6Addresses)}");
|
||||
}
|
||||
|
||||
sb.AppendLine($"已接收: {NetworkHelper.FormatBytes(iface.BytesReceived)} ({iface.PacketsReceived:N0} 包)");
|
||||
sb.AppendLine($"已发送: {NetworkHelper.FormatBytes(iface.BytesSent)} ({iface.PacketsSent:N0} 包)");
|
||||
|
||||
if (iface.ErrorsReceived > 0 || iface.ErrorsSent > 0)
|
||||
{
|
||||
sb.AppendLine($"错误统计: 接收错误 {iface.ErrorsReceived:N0}, 发送错误 {iface.ErrorsSent:N0}");
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加网络汇总信息
|
||||
/// </summary>
|
||||
/// <param name="sb">字符串构建器</param>
|
||||
/// <param name="interfaces">接口列表</param>
|
||||
private static void AppendNetworkSummary(StringBuilder sb, List<DetailedNetworkInterface> interfaces)
|
||||
{
|
||||
if (!interfaces.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
sb.AppendLine("=== 网络统计汇总 ===");
|
||||
|
||||
var totalBytesReceived = interfaces.Sum(i => i.BytesReceived);
|
||||
var totalBytesSent = interfaces.Sum(i => i.BytesSent);
|
||||
var totalPacketsReceived = interfaces.Sum(i => i.PacketsReceived);
|
||||
var totalPacketsSent = interfaces.Sum(i => i.PacketsSent);
|
||||
var totalErrors = interfaces.Sum(i => i.ErrorsReceived + i.ErrorsSent);
|
||||
|
||||
sb.AppendLine($"总接收流量: {NetworkHelper.FormatBytes(totalBytesReceived)}");
|
||||
sb.AppendLine($"总发送流量: {NetworkHelper.FormatBytes(totalBytesSent)}");
|
||||
sb.AppendLine($"总数据包: 接收 {totalPacketsReceived:N0}, 发送 {totalPacketsSent:N0}");
|
||||
|
||||
if (totalErrors > 0)
|
||||
{
|
||||
sb.AppendLine($"总错误数: {totalErrors:N0}");
|
||||
}
|
||||
|
||||
var ethernetCount = interfaces.Count(i => i.Type == NetworkInterfaceType.Ethernet);
|
||||
var wifiCount = interfaces.Count(i => i.Type == NetworkInterfaceType.Wireless80211);
|
||||
|
||||
sb.AppendLine(
|
||||
$"接口类型分布: 以太网 {ethernetCount}, Wi-Fi {wifiCount}, 其他 {interfaces.Count - ethernetCount - wifiCount}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Common.Logger;
|
||||
|
||||
namespace Common.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// 网络工具公共类 - 提供格式化、验证等公共功能
|
||||
/// </summary>
|
||||
public static class NetworkHelper
|
||||
{
|
||||
#region 常量定义
|
||||
|
||||
private const long MAX_REASONABLE_SPEED = 1_000_000_000; // 1GB/s 最大合理速度
|
||||
private const double MIN_TIME_INTERVAL = 0.5; // 最小时间间隔(秒)
|
||||
private const int MAC_ADDRESS_LENGTH = 12;
|
||||
private const int IPV4_PREFIX_MAX = 32;
|
||||
private const int IPV6_PREFIX_MAX = 128;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 格式化方法
|
||||
|
||||
/// <summary>
|
||||
/// 格式化网速显示
|
||||
/// </summary>
|
||||
/// <param name="bytesPerSecond">每秒字节数</param>
|
||||
/// <returns>格式化后的速度字符串</returns>
|
||||
public static string FormatSpeed(double bytesPerSecond)
|
||||
{
|
||||
if (!IsValidSpeed(bytesPerSecond))
|
||||
{
|
||||
return "0.00 B/s";
|
||||
}
|
||||
|
||||
var units = new[] { "B/s", "KB/s", "MB/s", "GB/s", "TB/s" };
|
||||
var unitIndex = 0;
|
||||
var speed = bytesPerSecond;
|
||||
|
||||
while (speed >= 1024 && unitIndex < units.Length - 1)
|
||||
{
|
||||
speed /= 1024;
|
||||
unitIndex++;
|
||||
}
|
||||
|
||||
return $"{speed:0.00} {units[unitIndex]}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化接口速度显示
|
||||
/// </summary>
|
||||
/// <param name="bitsPerSecond">每秒比特数</param>
|
||||
/// <returns>格式化后的速度字符串</returns>
|
||||
public static string FormatInterfaceSpeed(long bitsPerSecond)
|
||||
{
|
||||
if (bitsPerSecond <= 0)
|
||||
{
|
||||
return "未知";
|
||||
}
|
||||
|
||||
var units = new[] { "bps", "Kbps", "Mbps", "Gbps", "Tbps" };
|
||||
var unitIndex = 0;
|
||||
var speed = (double)bitsPerSecond;
|
||||
|
||||
while (speed >= 1000 && unitIndex < units.Length - 1)
|
||||
{
|
||||
speed /= 1000;
|
||||
unitIndex++;
|
||||
}
|
||||
|
||||
return $"{speed:0.##} {units[unitIndex]}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化字节数显示
|
||||
/// </summary>
|
||||
/// <param name="bytes">字节数</param>
|
||||
/// <returns>格式化后的字节数字符串</returns>
|
||||
public static string FormatBytes(long bytes)
|
||||
{
|
||||
if (bytes < 0)
|
||||
{
|
||||
return "0 B";
|
||||
}
|
||||
|
||||
var units = new[] { "B", "KB", "MB", "GB", "TB", "PB" };
|
||||
var unitIndex = 0;
|
||||
var size = (double)bytes;
|
||||
|
||||
while (size >= 1024 && unitIndex < units.Length - 1)
|
||||
{
|
||||
size /= 1024;
|
||||
unitIndex++;
|
||||
}
|
||||
|
||||
return $"{size:0.##} {units[unitIndex]}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化MAC地址
|
||||
/// </summary>
|
||||
/// <param name="physicalAddress">物理地址对象</param>
|
||||
/// <param name="separator">分隔符,默认为"-"</param>
|
||||
/// <returns>格式化后的MAC地址</returns>
|
||||
public static string FormatMacAddress(PhysicalAddress physicalAddress, string separator = "-")
|
||||
{
|
||||
if (physicalAddress == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var address = physicalAddress.ToString();
|
||||
if (string.IsNullOrEmpty(address) || address.Length != MAC_ADDRESS_LENGTH)
|
||||
{
|
||||
return address;
|
||||
}
|
||||
|
||||
return string.Join(separator,
|
||||
Enumerable.Range(0, 6)
|
||||
.Select(i => address.Substring(i * 2, 2))
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化系统运行时间
|
||||
/// </summary>
|
||||
/// <param name="uptime">运行时间</param>
|
||||
/// <returns>格式化后的运行时间字符串</returns>
|
||||
public static string FormatUptime(TimeSpan uptime)
|
||||
{
|
||||
if (uptime.TotalSeconds < 0)
|
||||
{
|
||||
return "0秒";
|
||||
}
|
||||
|
||||
var parts = new List<string>();
|
||||
|
||||
if (uptime.Days > 0)
|
||||
parts.Add($"{uptime.Days}天");
|
||||
if (uptime.Hours > 0)
|
||||
parts.Add($"{uptime.Hours}小时");
|
||||
if (uptime.Minutes > 0)
|
||||
parts.Add($"{uptime.Minutes}分");
|
||||
if (uptime.Seconds > 0 || parts.Count == 0)
|
||||
parts.Add($"{uptime.Seconds}秒");
|
||||
|
||||
return string.Join("", parts);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 验证方法
|
||||
|
||||
/// <summary>
|
||||
/// 验证网速是否有效
|
||||
/// </summary>
|
||||
/// <param name="speed">网速值</param>
|
||||
/// <returns>是否有效</returns>
|
||||
public static bool IsValidSpeed(double speed)
|
||||
{
|
||||
return speed >= 0 &&
|
||||
!double.IsNaN(speed) &&
|
||||
!double.IsInfinity(speed) &&
|
||||
speed < MAX_REASONABLE_SPEED;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证时间间隔是否足够
|
||||
/// </summary>
|
||||
/// <param name="timeInterval">时间间隔(秒)</param>
|
||||
/// <returns>是否足够</returns>
|
||||
public static bool IsValidTimeInterval(double timeInterval)
|
||||
{
|
||||
return timeInterval >= MIN_TIME_INTERVAL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证字节数变化是否合理
|
||||
/// </summary>
|
||||
/// <param name="bytesChange">字节数变化</param>
|
||||
/// <returns>是否合理</returns>
|
||||
public static bool IsValidBytesChange(long bytesChange)
|
||||
{
|
||||
return bytesChange >= 0 && bytesChange < MAX_REASONABLE_SPEED;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 计算方法
|
||||
|
||||
/// <summary>
|
||||
/// 计算网速
|
||||
/// </summary>
|
||||
/// <param name="currentBytes">当前字节数</param>
|
||||
/// <param name="previousBytes">之前字节数</param>
|
||||
/// <param name="timeInterval">时间间隔(秒)</param>
|
||||
/// <returns>网速(字节/秒)</returns>
|
||||
public static double CalculateSpeed(long currentBytes, long previousBytes, double timeInterval)
|
||||
{
|
||||
if (!IsValidTimeInterval(timeInterval))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var bytesChange = currentBytes - previousBytes;
|
||||
if (!IsValidBytesChange(bytesChange))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return bytesChange / timeInterval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算子网掩码
|
||||
/// </summary>
|
||||
/// <param name="unicast">单播地址信息</param>
|
||||
/// <returns>子网掩码字符串</returns>
|
||||
public static string CalculateSubnetMask(UnicastIPAddressInformation unicast)
|
||||
{
|
||||
if (unicast == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 优先使用IPv4Mask
|
||||
if (unicast.IPv4Mask != null)
|
||||
{
|
||||
return unicast.IPv4Mask.ToString();
|
||||
}
|
||||
|
||||
// 通过前缀长度计算
|
||||
var prefixLength = unicast.PrefixLength;
|
||||
if (prefixLength > 0 && prefixLength <= IPV4_PREFIX_MAX)
|
||||
{
|
||||
var mask = (uint)(0xFFFFFFFF << (IPV4_PREFIX_MAX - prefixLength));
|
||||
return new IPAddress(new byte[]
|
||||
{
|
||||
(byte)(mask >> 24),
|
||||
(byte)(mask >> 16),
|
||||
(byte)(mask >> 8),
|
||||
(byte)mask
|
||||
}).ToString();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Debug($"计算子网掩码时发生错误: {ex.Message}");
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 获取方法
|
||||
|
||||
/// <summary>
|
||||
/// 获取网络接口类型描述
|
||||
/// </summary>
|
||||
/// <param name="type">接口类型</param>
|
||||
/// <returns>类型描述</returns>
|
||||
public static string GetInterfaceTypeDescription(NetworkInterfaceType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
NetworkInterfaceType.Ethernet => "以太网",
|
||||
NetworkInterfaceType.Wireless80211 => "Wi-Fi",
|
||||
NetworkInterfaceType.Loopback => "环回接口",
|
||||
NetworkInterfaceType.Ppp => "PPP",
|
||||
NetworkInterfaceType.Tunnel => "隧道",
|
||||
NetworkInterfaceType.Slip => "SLIP",
|
||||
NetworkInterfaceType.TokenRing => "令牌环",
|
||||
NetworkInterfaceType.Fddi => "FDDI",
|
||||
NetworkInterfaceType.BasicIsdn => "基本ISDN",
|
||||
NetworkInterfaceType.PrimaryIsdn => "主要ISDN",
|
||||
NetworkInterfaceType.GenericModem => "通用调制解调器",
|
||||
NetworkInterfaceType.FastEthernetT => "快速以太网T",
|
||||
NetworkInterfaceType.Isdn => "ISDN",
|
||||
NetworkInterfaceType.FastEthernetFx => "快速以太网FX",
|
||||
NetworkInterfaceType.Wman => "无线城域网",
|
||||
NetworkInterfaceType.Wwanpp => "无线广域网PP",
|
||||
NetworkInterfaceType.Wwanpp2 => "无线广域网PP2",
|
||||
_ => type.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统运行时间
|
||||
/// </summary>
|
||||
/// <returns>系统运行时间</returns>
|
||||
public static TimeSpan GetSystemUptime()
|
||||
{
|
||||
return TimeSpan.FromMilliseconds(Environment.TickCount64);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取外网IP地址
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>外网IP信息</returns>
|
||||
public static async Task<ExternalIPInfo> GetExternalIPAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var externalInfo = new ExternalIPInfo();
|
||||
|
||||
using var client = new HttpClient();
|
||||
client.Timeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
// 获取IPv4地址
|
||||
externalInfo.IPv4 = await GetIPv4AddressAsync(client, cancellationToken);
|
||||
|
||||
// 获取IPv6地址
|
||||
externalInfo.IPv6 = await GetIPv6AddressAsync(client, cancellationToken);
|
||||
|
||||
return externalInfo;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 私有方法
|
||||
|
||||
/// <summary>
|
||||
/// 获取IPv4外网地址
|
||||
/// </summary>
|
||||
private static async Task<string> GetIPv4AddressAsync(HttpClient client, CancellationToken cancellationToken)
|
||||
{
|
||||
var ipv4Services = new[]
|
||||
{
|
||||
"https://api.ipify.org",
|
||||
"https://icanhazip.com",
|
||||
"https://ipv4.icanhazip.com",
|
||||
"https://checkip.amazonaws.com"
|
||||
};
|
||||
|
||||
foreach (var service in ipv4Services)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await client.GetStringAsync(service, cancellationToken);
|
||||
var ip = response?.Trim();
|
||||
if (!string.IsNullOrEmpty(ip) && IPAddress.TryParse(ip, out _))
|
||||
{
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Debug($"获取IPv4地址失败 ({service}): {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return "无法获取";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取IPv6外网地址
|
||||
/// </summary>
|
||||
private static async Task<string> GetIPv6AddressAsync(HttpClient client, CancellationToken cancellationToken)
|
||||
{
|
||||
var ipv6Services = new[]
|
||||
{
|
||||
"https://api6.ipify.org",
|
||||
"https://ipv6.icanhazip.com"
|
||||
};
|
||||
|
||||
foreach (var service in ipv6Services)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await client.GetStringAsync(service, cancellationToken);
|
||||
var ip = response?.Trim();
|
||||
if (!string.IsNullOrEmpty(ip) && IPAddress.TryParse(ip, out _))
|
||||
{
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Debug($"获取IPv6地址失败 ({service}): {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return "不支持或无法获取";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 外网IP信息
|
||||
/// </summary>
|
||||
public class ExternalIPInfo
|
||||
{
|
||||
public string IPv4 { get; set; } = string.Empty;
|
||||
public string IPv6 { get; set; } = string.Empty;
|
||||
public string ISP { get; set; } = string.Empty;
|
||||
public string Location { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 网速信息
|
||||
/// </summary>
|
||||
public class NetworkSpeed
|
||||
{
|
||||
public double DownloadSpeed { get; set; }
|
||||
public double UploadSpeed { get; set; }
|
||||
public double TotalSpeed => DownloadSpeed + UploadSpeed;
|
||||
|
||||
public string DownloadSpeedText => NetworkHelper.FormatSpeed(DownloadSpeed);
|
||||
public string UploadSpeedText => NetworkHelper.FormatSpeed(UploadSpeed);
|
||||
public string TotalSpeedText => NetworkHelper.FormatSpeed(TotalSpeed);
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Runtime.InteropServices;
|
||||
using NetVizor.Common.Models;
|
||||
|
||||
namespace NetVizor.Common.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides utility methods to retrieve network interface information
|
||||
/// using the Windows IP Helper API (iphlpapi.dll).
|
||||
/// This class does not require administrator privileges.
|
||||
/// </summary>
|
||||
public static class NetworkInfoUtil
|
||||
{
|
||||
#region P/Invoke Definitions
|
||||
|
||||
private const int ERROR_SUCCESS = 0;
|
||||
private const int MAX_INTERFACE_NAME_LEN = 256;
|
||||
private const int MAXLEN_PHYSADDR = 8;
|
||||
private const int MAXLEN_IFDESCR = 256;
|
||||
|
||||
[DllImport("iphlpapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern uint GetIfTable2(out IntPtr pIfTable);
|
||||
|
||||
[DllImport("iphlpapi.dll", SetLastError = true)]
|
||||
private static extern void FreeMibTable(IntPtr pMibTable);
|
||||
|
||||
private enum TunnelType
|
||||
{
|
||||
TUNNEL_TYPE_NONE = 0,
|
||||
TUNNEL_TYPE_OTHER = 1,
|
||||
TUNNEL_TYPE_DIRECT = 2,
|
||||
TUNNEL_TYPE_6TO4 = 11,
|
||||
TUNNEL_TYPE_ISATAP = 13,
|
||||
TUNNEL_TYPE_TEREDO = 14,
|
||||
TUNNEL_TYPE_IPHTTPS = 15
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private struct MIB_IF_ROW2
|
||||
{
|
||||
public long InterfaceLuid;
|
||||
public uint InterfaceIndex;
|
||||
public Guid InterfaceGuid;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_INTERFACE_NAME_LEN + 1)]
|
||||
public string Alias;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAXLEN_IFDESCR + 1)]
|
||||
public string Description;
|
||||
|
||||
public uint PhysicalAddressLength;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = MAXLEN_PHYSADDR)]
|
||||
public byte[] PhysicalAddress;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = MAXLEN_PHYSADDR)]
|
||||
public byte[] PermanentPhysicalAddress;
|
||||
|
||||
public uint Mtu;
|
||||
public NetworkInterfaceType Type;
|
||||
public TunnelType TunnelType;
|
||||
public uint MediaConnectState;
|
||||
public uint MediaDuplexState;
|
||||
public uint InterfaceAndOperStatusFlags;
|
||||
public OperationalStatus OperStatus;
|
||||
public uint AdminStatus;
|
||||
public uint NetLuidIndex;
|
||||
public uint NdisMedium;
|
||||
public uint NdisPhysicalMedium;
|
||||
|
||||
public uint HardareInterface;
|
||||
|
||||
// --- Start of Corrected Fields ---
|
||||
public ulong ReceiveLinkSpeed;
|
||||
|
||||
public ulong TransmitLinkSpeed;
|
||||
|
||||
// --- End of Corrected Fields ---
|
||||
public ulong OutOctets;
|
||||
public ulong InOctets;
|
||||
public ulong OutUcastPkts;
|
||||
public ulong InUcastPkts;
|
||||
public ulong OutNUcastPkts;
|
||||
public ulong InNUcastPkts;
|
||||
public ulong OutDiscards;
|
||||
public ulong InDiscards;
|
||||
public ulong OutErrors;
|
||||
public ulong InErrors;
|
||||
public ulong OutUcastOctets;
|
||||
public ulong InUcastOctets;
|
||||
public ulong OutMulticastOctets;
|
||||
public ulong InMulticastOctets;
|
||||
public ulong OutBroadcastOctets;
|
||||
public ulong InBroadcastOctets;
|
||||
public uint OutQLen;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct MIB_IF_TABLE2
|
||||
{
|
||||
public uint NumEntries;
|
||||
// The Table field is a placeholder here. We'll calculate its position manually.
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all network interfaces on the system.
|
||||
/// </summary>
|
||||
/// <returns>A list of NetworkInterfaceInfo objects.</returns>
|
||||
/// <exception cref="Win32Exception">Thrown if the underlying Windows API call fails.</exception>
|
||||
public static List<NetworkInterfaceInfo> GetAllInterfaces()
|
||||
{
|
||||
IntPtr pIfTable = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
uint result = GetIfTable2(out pIfTable);
|
||||
if (result != ERROR_SUCCESS)
|
||||
{
|
||||
throw new Win32Exception((int)result);
|
||||
}
|
||||
|
||||
var ifTable = (MIB_IF_TABLE2)Marshal.PtrToStructure(pIfTable, typeof(MIB_IF_TABLE2));
|
||||
var interfaces = new List<NetworkInterfaceInfo>((int)ifTable.NumEntries);
|
||||
|
||||
IntPtr currentPtr = new IntPtr(pIfTable.ToInt64() + Marshal.SizeOf(typeof(uint)));
|
||||
|
||||
for (int i = 0; i < ifTable.NumEntries; i++)
|
||||
{
|
||||
var row = (MIB_IF_ROW2)Marshal.PtrToStructure(currentPtr, typeof(MIB_IF_ROW2));
|
||||
interfaces.Add(MapRowToInterfaceInfo(row));
|
||||
currentPtr = new IntPtr(currentPtr.ToInt64() + Marshal.SizeOf(typeof(MIB_IF_ROW2)));
|
||||
}
|
||||
|
||||
return interfaces;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (pIfTable != IntPtr.Zero)
|
||||
{
|
||||
FreeMibTable(pIfTable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets detailed information for a specific network interface by its index.
|
||||
/// </summary>
|
||||
/// <param name="interfaceIndex">The index of the interface to retrieve.</param>
|
||||
/// <returns>A NetworkInterfaceInfo object, or null if not found.</returns>
|
||||
public static NetworkInterfaceInfo? GetInterfaceByIndex(uint interfaceIndex)
|
||||
{
|
||||
var allInterfaces = GetAllInterfaces();
|
||||
foreach (var iface in allInterfaces)
|
||||
{
|
||||
if (iface.Index == interfaceIndex)
|
||||
{
|
||||
return iface;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static NetworkInterfaceInfo MapRowToInterfaceInfo(MIB_IF_ROW2 row)
|
||||
{
|
||||
// Guard against invalid address length reported by the API
|
||||
int macAddrLen = (int)Math.Min(row.PhysicalAddressLength, (uint)MAXLEN_PHYSADDR);
|
||||
var macBytes = new byte[macAddrLen];
|
||||
if (macAddrLen > 0)
|
||||
{
|
||||
Array.Copy(row.PhysicalAddress, macBytes, macAddrLen);
|
||||
}
|
||||
|
||||
return new NetworkInterfaceInfo
|
||||
{
|
||||
Index = row.InterfaceIndex,
|
||||
InterfaceId = row.InterfaceGuid.ToString(),
|
||||
Alias = row.Alias,
|
||||
Description = row.Description,
|
||||
Type = row.Type,
|
||||
Status = row.OperStatus,
|
||||
Speed = row.ReceiveLinkSpeed, // Correctly use the speed field from the struct
|
||||
MacAddress = macAddrLen > 0 ? BitConverter.ToString(macBytes).Replace('-', ':') : string.Empty,
|
||||
BytesSent = (long)row.OutOctets,
|
||||
BytesReceived = (long)row.InOctets
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Net.NetworkInformation;
|
||||
using Common.Logger;
|
||||
|
||||
namespace Common.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// 网络监控帮助类 - 使用 .NET 内置的 NetworkInterface 类
|
||||
/// </summary>
|
||||
public class NetworkMonitorHelper1
|
||||
{
|
||||
#region 公共类和方法
|
||||
|
||||
/// <summary>
|
||||
/// 网络接口信息
|
||||
/// </summary>
|
||||
public class NetworkInterface
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Id { get; set; }
|
||||
public uint Index { get; set; }
|
||||
public long BytesReceived { get; set; }
|
||||
public long BytesSent { get; set; }
|
||||
public bool IsConnected { get; set; }
|
||||
public NetworkInterfaceType Type { get; set; }
|
||||
public string TypeDescription { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 网速信息
|
||||
/// </summary>
|
||||
public class NetworkSpeed
|
||||
{
|
||||
public double DownloadSpeed { get; set; } // bytes per second
|
||||
public double UploadSpeed { get; set; } // bytes per second
|
||||
public double TotalSpeed => DownloadSpeed + UploadSpeed;
|
||||
|
||||
// 格式化显示
|
||||
public string DownloadSpeedText => FormatSpeed(DownloadSpeed);
|
||||
public string UploadSpeedText => FormatSpeed(UploadSpeed);
|
||||
public string TotalSpeedText => FormatSpeed(TotalSpeed);
|
||||
|
||||
private string FormatSpeed(double bytesPerSecond)
|
||||
{
|
||||
// 防止显示负数或异常大的数值
|
||||
if (bytesPerSecond < 0 || double.IsNaN(bytesPerSecond) || double.IsInfinity(bytesPerSecond))
|
||||
{
|
||||
return "0.00 B/s";
|
||||
}
|
||||
|
||||
string[] sizes = { "B/s", "KB/s", "MB/s", "GB/s" };
|
||||
int order = 0;
|
||||
while (bytesPerSecond >= 1024 && order < sizes.Length - 1)
|
||||
{
|
||||
order++;
|
||||
bytesPerSecond = bytesPerSecond / 1024;
|
||||
}
|
||||
|
||||
return $"{bytesPerSecond:0.00} {sizes[order]}";
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存上次的统计数据
|
||||
private static Dictionary<string, (long bytesReceived, long bytesSent, DateTime updateTime)> previousStats =
|
||||
new Dictionary<string, (long, long, DateTime)>();
|
||||
|
||||
private static bool isFirstRun = true;
|
||||
|
||||
/// <summary>
|
||||
/// 获取接口类型描述
|
||||
/// </summary>
|
||||
private static string GetInterfaceTypeDescription(NetworkInterfaceType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case NetworkInterfaceType.Ethernet:
|
||||
return "以太网";
|
||||
case NetworkInterfaceType.Wireless80211:
|
||||
return "Wi-Fi";
|
||||
case NetworkInterfaceType.Loopback:
|
||||
return "环回接口";
|
||||
case NetworkInterfaceType.Ppp:
|
||||
return "PPP";
|
||||
case NetworkInterfaceType.Tunnel:
|
||||
return "隧道";
|
||||
default:
|
||||
return type.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有网络接口
|
||||
/// </summary>
|
||||
public static List<NetworkInterface> GetNetworkInterfaces()
|
||||
{
|
||||
var interfaces = new List<NetworkInterface>();
|
||||
|
||||
try
|
||||
{
|
||||
var networkInterfaces = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces();
|
||||
|
||||
foreach (var ni in networkInterfaces)
|
||||
{
|
||||
// 跳过环回接口
|
||||
if (ni.NetworkInterfaceType == NetworkInterfaceType.Loopback)
|
||||
{
|
||||
Log.Info($"跳过环回接口: {ni.Name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取IPv4统计信息
|
||||
IPv4InterfaceStatistics stats = null;
|
||||
try
|
||||
{
|
||||
stats = ni.GetIPv4Statistics();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error4Ctx($"无法获取接口 {ni.Name} 的统计信息: {ex.Message}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var networkInterface = new NetworkInterface
|
||||
{
|
||||
Name = ni.Name,
|
||||
Description = ni.Description,
|
||||
Id = ni.Id,
|
||||
Index = 0, // NetworkInterface 类不提供 Index,使用 Id 作为唯一标识
|
||||
BytesReceived = stats.BytesReceived,
|
||||
BytesSent = stats.BytesSent,
|
||||
IsConnected = ni.OperationalStatus == OperationalStatus.Up,
|
||||
Type = ni.NetworkInterfaceType,
|
||||
TypeDescription = GetInterfaceTypeDescription(ni.NetworkInterfaceType)
|
||||
};
|
||||
|
||||
interfaces.Add(networkInterface);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error4Ctx($"获取网络接口时发生错误: {ex.Message}");
|
||||
Log.Error4Ctx($"错误堆栈: {ex.StackTrace}");
|
||||
}
|
||||
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算指定网络接口的网速
|
||||
/// </summary>
|
||||
public static NetworkSpeed CalculateSpeed(uint interfaceIndex)
|
||||
{
|
||||
// 由于使用了 NetworkInterface 类,这里改为使用接口名称查找
|
||||
var interfaces = GetNetworkInterfaces();
|
||||
return CalculateSpeedByName(interfaces, interfaceIndex.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据接口名称计算网速
|
||||
/// </summary>
|
||||
public static NetworkSpeed CalculateSpeedByName(string interfaceName)
|
||||
{
|
||||
var interfaces = GetNetworkInterfaces();
|
||||
return CalculateSpeedByName(interfaces, interfaceName);
|
||||
}
|
||||
|
||||
private static NetworkSpeed CalculateSpeedByName(List<NetworkInterface> currentInterfaces, string interfaceName)
|
||||
{
|
||||
Log.Info($"计算接口 {interfaceName} 的网速...");
|
||||
|
||||
var currentInterface =
|
||||
currentInterfaces.FirstOrDefault(i => i.Name == interfaceName || i.Id == interfaceName);
|
||||
|
||||
if (currentInterface == null)
|
||||
{
|
||||
Log.Warning($"未找到接口 {interfaceName}");
|
||||
return new NetworkSpeed();
|
||||
}
|
||||
|
||||
var now = DateTime.Now;
|
||||
var speed = new NetworkSpeed();
|
||||
string key = currentInterface.Id;
|
||||
|
||||
// 如果是第一次运行或没有该接口的历史数据,只保存数据
|
||||
if (isFirstRun || !previousStats.ContainsKey(key))
|
||||
{
|
||||
previousStats[key] = (currentInterface.BytesReceived, currentInterface.BytesSent, now);
|
||||
Log.Info($"接口 {interfaceName} 第一次记录数据");
|
||||
if (isFirstRun) isFirstRun = false;
|
||||
return speed;
|
||||
}
|
||||
|
||||
var (prevBytesReceived, prevBytesSent, lastUpdateTime) = previousStats[key];
|
||||
var timeDiff = (now - lastUpdateTime).TotalSeconds;
|
||||
|
||||
Log.Debug($"时间差: {timeDiff}秒");
|
||||
|
||||
if (timeDiff > 0.5) // 至少0.5秒的间隔
|
||||
{
|
||||
// 计算字节差
|
||||
long downloadBytes = currentInterface.BytesReceived - prevBytesReceived;
|
||||
long uploadBytes = currentInterface.BytesSent - prevBytesSent;
|
||||
|
||||
Log.Info($"下载字节差: {downloadBytes}, 上传字节差: {uploadBytes}");
|
||||
|
||||
// 防止计数器重置或异常值
|
||||
if (downloadBytes >= 0 && downloadBytes < 1000000000) // 小于1GB/s
|
||||
{
|
||||
speed.DownloadSpeed = downloadBytes / timeDiff;
|
||||
}
|
||||
|
||||
if (uploadBytes >= 0 && uploadBytes < 1000000000) // 小于1GB/s
|
||||
{
|
||||
speed.UploadSpeed = uploadBytes / timeDiff;
|
||||
}
|
||||
|
||||
Log.Info($"接口 {interfaceName} - 下载: {speed.DownloadSpeedText}, 上传: {speed.UploadSpeedText}");
|
||||
|
||||
// 更新缓存
|
||||
previousStats[key] = (currentInterface.BytesReceived, currentInterface.BytesSent, now);
|
||||
}
|
||||
|
||||
return speed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算所有网络接口的总网速
|
||||
/// </summary>
|
||||
public static NetworkSpeed CalculateTotalSpeed()
|
||||
{
|
||||
var currentInterfaces = GetNetworkInterfaces();
|
||||
var now = DateTime.Now;
|
||||
var totalSpeed = new NetworkSpeed();
|
||||
|
||||
// 如果是第一次运行,只保存数据
|
||||
if (isFirstRun)
|
||||
{
|
||||
foreach (var currentInterface in currentInterfaces)
|
||||
{
|
||||
previousStats[currentInterface.Id] =
|
||||
(currentInterface.BytesReceived, currentInterface.BytesSent, now);
|
||||
}
|
||||
|
||||
isFirstRun = false;
|
||||
Log.Info("第一次运行,初始化数据");
|
||||
return totalSpeed;
|
||||
}
|
||||
|
||||
foreach (var currentInterface in currentInterfaces)
|
||||
{
|
||||
string key = currentInterface.Id;
|
||||
|
||||
if (previousStats.ContainsKey(key))
|
||||
{
|
||||
var (prevBytesReceived, prevBytesSent, lastUpdateTime) = previousStats[key];
|
||||
var timeDiff = (now - lastUpdateTime).TotalSeconds;
|
||||
|
||||
if (timeDiff > 0.5) // 至少0.5秒的间隔
|
||||
{
|
||||
long downloadBytes = currentInterface.BytesReceived - prevBytesReceived;
|
||||
long uploadBytes = currentInterface.BytesSent - prevBytesSent;
|
||||
|
||||
// 防止计数器重置或异常值
|
||||
if (downloadBytes >= 0 && downloadBytes < 1000000000) // 小于1GB/s
|
||||
{
|
||||
totalSpeed.DownloadSpeed += downloadBytes / timeDiff;
|
||||
}
|
||||
|
||||
if (uploadBytes >= 0 && uploadBytes < 1000000000) // 小于1GB/s
|
||||
{
|
||||
totalSpeed.UploadSpeed += uploadBytes / timeDiff;
|
||||
}
|
||||
|
||||
// 更新缓存
|
||||
previousStats[key] = (currentInterface.BytesReceived, currentInterface.BytesSent, now);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 新接口,添加到缓存
|
||||
previousStats[key] = (currentInterface.BytesReceived, currentInterface.BytesSent, now);
|
||||
}
|
||||
}
|
||||
|
||||
Log.Info($"总网速 - 下载: {totalSpeed.DownloadSpeedText}, 上传: {totalSpeed.UploadSpeedText}");
|
||||
|
||||
return totalSpeed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置统计数据
|
||||
/// </summary>
|
||||
public static void ResetStatistics()
|
||||
{
|
||||
Log.Info("重置网络统计数据");
|
||||
previousStats.Clear();
|
||||
isFirstRun = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using NetVizor.Common.Models;
|
||||
|
||||
namespace NetVizor.Common.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Monitors the real-time network speed (upload/download) for a specific network interface.
|
||||
/// </summary>
|
||||
public class NetworkSpeedMonitor
|
||||
{
|
||||
private readonly uint _interfaceIndex;
|
||||
private long _lastBytesSent;
|
||||
private long _lastBytesReceived;
|
||||
private DateTime _lastUpdateTime;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the real-time download speed in bytes per second.
|
||||
/// </summary>
|
||||
public double DownloadSpeedBps { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the real-time upload speed in bytes per second.
|
||||
/// </summary>
|
||||
public double UploadSpeedBps { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the NetworkSpeedMonitor for a given interface.
|
||||
/// </summary>
|
||||
/// <param name="interfaceInfo">The network interface to monitor.</param>
|
||||
public NetworkSpeedMonitor(NetworkInterfaceInfo interfaceInfo)
|
||||
{
|
||||
_interfaceIndex = interfaceInfo.Index;
|
||||
_lastBytesSent = interfaceInfo.BytesSent;
|
||||
_lastBytesReceived = interfaceInfo.BytesReceived;
|
||||
_lastUpdateTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the speed calculation. This method should be called periodically.
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
var currentInterfaceInfo = NetworkInfoUtil.GetInterfaceByIndex(_interfaceIndex);
|
||||
if (currentInterfaceInfo == null)
|
||||
{
|
||||
DownloadSpeedBps = 0;
|
||||
UploadSpeedBps = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
var currentTime = DateTime.UtcNow;
|
||||
var timeSpan = (currentTime - _lastUpdateTime).TotalSeconds;
|
||||
|
||||
if (timeSpan <= 0)
|
||||
{
|
||||
return; // Avoid division by zero if called too quickly
|
||||
}
|
||||
|
||||
long bytesSent = currentInterfaceInfo.BytesSent;
|
||||
long bytesReceived = currentInterfaceInfo.BytesReceived;
|
||||
|
||||
UploadSpeedBps = (bytesSent - _lastBytesSent) / timeSpan;
|
||||
DownloadSpeedBps = (bytesReceived - _lastBytesReceived) / timeSpan;
|
||||
|
||||
_lastBytesSent = bytesSent;
|
||||
_lastBytesReceived = bytesReceived;
|
||||
_lastUpdateTime = currentTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FirewallManager" Version="1.0.2"/>
|
||||
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.21" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="10.0.0-preview.4.25258.110" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using NetFwTypeLib;
|
||||
|
||||
namespace NetVizor.Infrastructure.Models
|
||||
{
|
||||
// 用于定义防火墙规则的方向
|
||||
public enum FirewallDirection
|
||||
{
|
||||
In,
|
||||
Out
|
||||
}
|
||||
|
||||
// 用于定义防火墙规则的操作
|
||||
public enum FirewallAction
|
||||
{
|
||||
Block,
|
||||
Allow
|
||||
}
|
||||
|
||||
public class FirewallRule
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public string? Description { get; set; }
|
||||
public string? GroupName { get; set; }
|
||||
public string ApplicationName { get; set; } = ""; // "*" 表示所有程序
|
||||
public string Protocol { get; set; } = "TCP"; // TCP, UDP, ANY
|
||||
public string LocalPorts { get; set; } = "*"; // "80,443" 或 "*"
|
||||
public string RemotePorts { get; set; } = "*";
|
||||
public FirewallDirection Direction { get; set; } = FirewallDirection.In;
|
||||
public FirewallAction Action { get; set; } = FirewallAction.Allow;
|
||||
public bool Enabled { get; set; } = true;
|
||||
}
|
||||
}
|
||||
@@ -313,4 +313,5 @@ public static class SysInfoUtils
|
||||
|
||||
return $"{len:0.##} {sizes[order]}";
|
||||
}
|
||||
}
|
||||
}
|
||||
// Infrastructure.utils FormatBytes
|
||||
@@ -32,6 +32,7 @@
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ALazy_00601_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F211e6f3b24fa438a92f1815153647ce2c8f908_003F74_003F173162e6_003FLazy_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ALogger_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F59d0b425a1244469855c0039f03f864c27e00_003F43_003F8a29b095_003FLogger_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMonitor_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F211e6f3b24fa438a92f1815153647ce2c8f908_003Fb5_003F0c446809_003FMonitor_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANetworkInterfaceType_002Ecs_002Fl_003AC_0021_003FUsers_003Fyllyo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F92cb821cac5045cbbf918dc41b2ea2ca26920_003Fa4_003F57cbd2f9_003FNetworkInterfaceType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANetworkInterface_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F9c72a65889eb40ae9d8e0f130d1572e0268a0_003F34_003F49f5e6ff_003FNetworkInterface_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AProcessModule_002Ecs_002Fl_003AC_0021_003FUsers_003Fyllyo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa70eaad10e4f42d4a1f24ff3865c6c6252928_003Fec_003F0881d57a_003FProcessModule_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AProcess_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F4a26a752ec6249c4a090905d31c0cdfa528a0_003F8f_003F7037e590_003FProcess_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
<Application x:Class="Shell.App"
|
||||
<Application x:Class="NetVizor.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Shell"
|
||||
>
|
||||
<!-- StartupUri="MainWindow.xaml" -->
|
||||
xmlns:local="clr-namespace:NetVizor"
|
||||
xmlns:converters="clr-namespace:NetVizor.Views.Converters"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
|
||||
<ResourceDictionary>
|
||||
<converters:NullToCollapsedConverter x:Key="NullToCollapsedConverter" />
|
||||
<converters:NullToVisibleConverter x:Key="NullToVisibleConverter" />
|
||||
|
||||
<!-- 全局样式 -->
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="FontFamily" Value="Microsoft YaHei" />
|
||||
</Style>
|
||||
<Style TargetType="ComboBox">
|
||||
<Setter Property="FontFamily" Value="Microsoft YaHei" />
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
</Application>
|
||||
@@ -8,6 +8,7 @@ using System.Text.Json;
|
||||
using System.Web;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using Application;
|
||||
using Application.Utils;
|
||||
using Common;
|
||||
using Common.Logger;
|
||||
@@ -18,9 +19,10 @@ using Common.Utils;
|
||||
using Infrastructure.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog.Events;
|
||||
using Shell;
|
||||
using Utils.ETW.Etw;
|
||||
|
||||
namespace Shell;
|
||||
namespace NetVizor;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
@@ -36,8 +38,8 @@ public partial class App : System.Windows.Application
|
||||
Log.Initialize(configModelLogging);
|
||||
base.OnStartup(e);
|
||||
|
||||
// ✅ 启动你的服务(如 WebSocket、HTTP、端口监听等)
|
||||
StartMyServer();
|
||||
// 启动服务(如 WebSocket、HTTP、端口监听等)
|
||||
// StartMyServer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -3,11 +3,18 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Shell"
|
||||
xmlns:views="clr-namespace:Shell.Views"
|
||||
xmlns:local="clr-namespace:NetVizor"
|
||||
xmlns:views1="clr-namespace:Shell.Views"
|
||||
mc:Ignorable="d"
|
||||
Title="MainWindow" Height="700" Width="1300">
|
||||
Title="NetVizor Dashboard" Height="700" Width="1300">
|
||||
<Grid>
|
||||
<views:WebPanel/>
|
||||
<TabControl>
|
||||
<TabItem Header="Network Monitor">
|
||||
<views1:NetworkMonitorView />
|
||||
</TabItem>
|
||||
<TabItem Header="Web Panel">
|
||||
<views1:WebPanel />
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Grid>
|
||||
</Window>
|
||||
</Window>
|
||||
@@ -10,5 +10,6 @@ public partial class MainWindow : Window
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
Console.WriteLine("1211111111111111111111111111。");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<UserControl x:Class="Shell.Views.NetworkMonitorView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:NetVizor.Views"
|
||||
xmlns:converters="clr-namespace:NetVizor.Views.Converters"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
Background="#F0F2F5">
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="20" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 网络接口选择 -->
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Column="0" Content="选择网络接口:" VerticalAlignment="Center" />
|
||||
<ComboBox Grid.Column="1"
|
||||
Name="cmbInterfaces"
|
||||
Margin="5,0"
|
||||
VerticalAlignment="Center"
|
||||
SelectionChanged="cmbInterfaces_SelectionChanged">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Column="0" Margin="0,0,10,0">
|
||||
<TextBlock.Text>
|
||||
<Binding Path="Name" />
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Grid.Column="1" Foreground="Gray">
|
||||
<TextBlock.Text>
|
||||
<Binding Path="Description" />
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Grid.Column="2" Margin="10,0,0,0">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||
<Setter Property="Text" Value="[已连接]" />
|
||||
<Setter Property="Foreground" Value="Green" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding IsConnected}" Value="False">
|
||||
<Setter Property="Text" Value="[未连接]" />
|
||||
<Setter Property="Foreground" Value="Red" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<Button Grid.Column="2"
|
||||
Name="btnRefresh"
|
||||
Content="刷新"
|
||||
Padding="10,5"
|
||||
Click="btnRefresh_Click" />
|
||||
</Grid>
|
||||
|
||||
<!-- 网速显示区域 -->
|
||||
<Border Grid.Row="2"
|
||||
BorderBrush="#CCCCCC"
|
||||
BorderThickness="1"
|
||||
CornerRadius="5"
|
||||
Background="#F9F9F9">
|
||||
<Grid Margin="20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="20" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="20" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="实时网速监控"
|
||||
FontSize="18"
|
||||
FontWeight="Bold"
|
||||
HorizontalAlignment="Center" />
|
||||
|
||||
<!-- 下载速度 -->
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||
<TextBlock Text="▼" Foreground="Green" FontWeight="Bold" Margin="0,0,5,0" />
|
||||
<TextBlock Text="下载速度:" FontSize="14" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1"
|
||||
Name="txtDownloadSpeed"
|
||||
Text="0.00 B/s"
|
||||
FontSize="16"
|
||||
FontWeight="Bold"
|
||||
Foreground="Green"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
<!-- 上传速度 -->
|
||||
<Grid Grid.Row="3" Margin="0,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||
<TextBlock Text="▲" Foreground="Blue" FontWeight="Bold" Margin="0,0,5,0" />
|
||||
<TextBlock Text="上传速度:" FontSize="14" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1"
|
||||
Name="txtUploadSpeed"
|
||||
Text="0.00 B/s"
|
||||
FontSize="16"
|
||||
FontWeight="Bold"
|
||||
Foreground="Blue"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
<!-- 总速度 -->
|
||||
<Grid Grid.Row="4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||
<TextBlock Text="Σ" Foreground="DarkOrange" FontWeight="Bold" FontSize="16" Margin="0,0,5,0" />
|
||||
<TextBlock Text="总速度:" FontSize="14" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1"
|
||||
Name="txtTotalSpeed"
|
||||
Text="0.00 B/s"
|
||||
FontSize="16"
|
||||
FontWeight="Bold"
|
||||
Foreground="DarkOrange"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
<!-- 状态信息 -->
|
||||
<TextBlock Grid.Row="6"
|
||||
Name="txtStatus"
|
||||
Text="请选择一个网络接口"
|
||||
FontSize="12"
|
||||
Foreground="Gray"
|
||||
HorizontalAlignment="Center" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@@ -0,0 +1,212 @@
|
||||
using System.Windows.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Threading;
|
||||
using Common.Logger;
|
||||
using Common.Utils;
|
||||
|
||||
namespace Shell.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for NetworkMonitorView.xaml
|
||||
/// </summary>
|
||||
public partial class NetworkMonitorView : UserControl
|
||||
{
|
||||
private DispatcherTimer updateTimer;
|
||||
private List<NetworkMonitorHelper1.NetworkInterface> interfaces;
|
||||
private const string SELECT_ALL_TEXT = "选择全部";
|
||||
|
||||
public NetworkMonitorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
Log.Info("NetworkMonitorControl 初始化");
|
||||
|
||||
// 初始化定时器
|
||||
updateTimer = new DispatcherTimer();
|
||||
updateTimer.Interval = TimeSpan.FromSeconds(1); // 每秒更新一次
|
||||
updateTimer.Tick += UpdateTimer_Tick;
|
||||
|
||||
// 控件加载完成后初始化
|
||||
Loaded += NetworkMonitorControl_Loaded;
|
||||
Unloaded += NetworkMonitorControl_Unloaded;
|
||||
|
||||
// NewMethod();
|
||||
}
|
||||
|
||||
private static async Task NewMethod()
|
||||
{
|
||||
// 获取详细信息
|
||||
var interfaces = EnhancedNetworkMonitor.GetDetailedNetworkInterfaces();
|
||||
var json = JsonHelper.ToJson(interfaces);
|
||||
Console.WriteLine(json);
|
||||
// 获取完整报告
|
||||
var report = await EnhancedNetworkMonitor.GetNetworkStatusReportAsync();
|
||||
Console.WriteLine(report);
|
||||
|
||||
// 获取外网IP
|
||||
var externalIP = await EnhancedNetworkMonitor.GetExternalIPAsync();
|
||||
Console.WriteLine(externalIP);
|
||||
}
|
||||
|
||||
private void NetworkMonitorControl_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Log.Info("NetworkMonitorControl 加载完成");
|
||||
LoadNetworkInterfaces();
|
||||
updateTimer.Start();
|
||||
}
|
||||
|
||||
private void NetworkMonitorControl_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Log.Info("NetworkMonitorControl 卸载");
|
||||
updateTimer.Stop();
|
||||
}
|
||||
|
||||
private void LoadNetworkInterfaces()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取所有网络接口
|
||||
interfaces = NetworkMonitorHelper1.GetNetworkInterfaces();
|
||||
|
||||
// 创建下拉框项目列表
|
||||
var comboItems = new List<object>();
|
||||
|
||||
// 添加"选择全部"选项
|
||||
comboItems.Add(new
|
||||
{
|
||||
Name = SELECT_ALL_TEXT,
|
||||
Description = "显示所有网络接口的总网速",
|
||||
TypeDescription = "",
|
||||
IsConnected = true
|
||||
});
|
||||
|
||||
// 添加所有非环回接口
|
||||
foreach (var iface in interfaces)
|
||||
{
|
||||
// 添加所有接口到下拉框(环回接口已经在工具类中过滤)
|
||||
comboItems.Add(iface);
|
||||
}
|
||||
|
||||
// 更新下拉框
|
||||
cmbInterfaces.ItemsSource = comboItems;
|
||||
|
||||
// 默认选择第一项(选择全部)
|
||||
if (comboItems.Count > 0)
|
||||
{
|
||||
cmbInterfaces.SelectedIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
txtStatus.Text = "未找到可用的网络接口";
|
||||
Log.Warning("未找到任何网络接口");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error4Ctx($"加载网络接口时发生错误: {ex.Message}");
|
||||
txtStatus.Text = $"加载失败: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
if (cmbInterfaces.SelectedItem == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
dynamic selectedItem = cmbInterfaces.SelectedItem;
|
||||
NetworkMonitorHelper1.NetworkSpeed speed;
|
||||
|
||||
if (selectedItem.Name == SELECT_ALL_TEXT)
|
||||
{
|
||||
// 计算所有接口的总网速
|
||||
speed = NetworkMonitorHelper1.CalculateTotalSpeed();
|
||||
txtStatus.Text = "显示所有网络接口的总网速";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 计算指定接口的网速
|
||||
var networkInterface = selectedItem as NetworkMonitorHelper1.NetworkInterface;
|
||||
if (networkInterface != null)
|
||||
{
|
||||
speed = NetworkMonitorHelper1.CalculateSpeedByName(networkInterface.Name);
|
||||
|
||||
// 显示接口状态
|
||||
string statusText = $"正在监控: {networkInterface.Name} ({networkInterface.TypeDescription})";
|
||||
if (!networkInterface.IsConnected)
|
||||
{
|
||||
statusText += " - 未连接";
|
||||
}
|
||||
|
||||
txtStatus.Text = statusText;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新界面显示
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
txtDownloadSpeed.Text = speed.DownloadSpeedText;
|
||||
txtUploadSpeed.Text = speed.UploadSpeedText;
|
||||
txtTotalSpeed.Text = speed.TotalSpeedText;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error4Ctx($"更新网速时发生错误: {ex.Message}");
|
||||
txtStatus.Text = $"更新失败: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private void cmbInterfaces_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
Log.Info("用户切换了网络接口选择");
|
||||
|
||||
// 重置网速显示
|
||||
txtDownloadSpeed.Text = "0.00 B/s";
|
||||
txtUploadSpeed.Text = "0.00 B/s";
|
||||
txtTotalSpeed.Text = "0.00 B/s";
|
||||
|
||||
// 重置统计数据,以便立即开始新的统计
|
||||
NetworkMonitorHelper1.ResetStatistics();
|
||||
|
||||
if (cmbInterfaces.SelectedItem != null)
|
||||
{
|
||||
dynamic selectedItem = cmbInterfaces.SelectedItem;
|
||||
Log.Info($"选择了: {selectedItem.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
private void btnRefresh_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Log.Info("用户点击了刷新按钮");
|
||||
|
||||
// 停止定时器
|
||||
updateTimer.Stop();
|
||||
|
||||
// 重置统计数据
|
||||
NetworkMonitorHelper1.ResetStatistics();
|
||||
|
||||
// 重新加载接口
|
||||
LoadNetworkInterfaces();
|
||||
|
||||
// 重启定时器
|
||||
updateTimer.Start();
|
||||
|
||||
txtStatus.Text = "接口列表已刷新";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,21 +30,20 @@ public partial class WebPanel : UserControl
|
||||
|
||||
// ETW 信息获取
|
||||
// 检查管理员权限
|
||||
NewMethod();
|
||||
// NewMethod();
|
||||
}
|
||||
|
||||
private static async Task NewMethod()
|
||||
{
|
||||
if (!SysHelper.IsAdministrator())
|
||||
{
|
||||
Console.WriteLine("此程序需要管理员权限才能运行ETW监控!");
|
||||
Console.WriteLine("请以管理员身份重新运行程序。");
|
||||
Console.ReadKey();
|
||||
Log.Info("此程序需要管理员权限才能运行ETW监控!");
|
||||
Log.Info("请以管理员身份重新运行程序。");
|
||||
return;
|
||||
}
|
||||
|
||||
var example = new NetworkMonitorUsageExample();
|
||||
example.RunExample();
|
||||
// var example = new NetworkMonitorUsageExample();
|
||||
// example.RunExample();
|
||||
|
||||
// 创建监听实例
|
||||
// var etwNetworkManger = new EtwNetworkManger();
|
||||
@@ -60,8 +59,6 @@ public partial class WebPanel : UserControl
|
||||
// 获取网络状态
|
||||
webSocketManager.RegisterHandler("etwNetworkManger", async (cmd, socket) =>
|
||||
{
|
||||
Console.WriteLine("开始测试");
|
||||
|
||||
// 创建监听实例
|
||||
// var etwNetworkManger = new EtwNetworkManger();
|
||||
|
||||
@@ -166,8 +163,8 @@ public partial class WebPanel : UserControl
|
||||
|
||||
public class JsMessage
|
||||
{
|
||||
public string Channel { get; set; }
|
||||
public JsData Payload { get; set; }
|
||||
public string Channel { get; set; }
|
||||
public JsData Payload { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
@@ -177,8 +174,8 @@ public JsData Payload { get; set; }
|
||||
|
||||
public class JsData
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Content { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Content { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Common.Logger;
|
||||
using Microsoft.Diagnostics.Tracing;
|
||||
using Microsoft.Diagnostics.Tracing.Parsers;
|
||||
using Microsoft.Diagnostics.Tracing.Session;
|
||||
@@ -5,7 +6,6 @@ using Utils.ETW.Models;
|
||||
|
||||
namespace Utils.ETW.Core;
|
||||
|
||||
|
||||
// ETW网络事件处理器
|
||||
public class EtwNetworkCapture(string sessionName = "NetworkETWSession") : IDisposable
|
||||
{
|
||||
@@ -13,6 +13,7 @@ public class EtwNetworkCapture(string sessionName = "NetworkETWSession") : IDisp
|
||||
/// 此代码需要管理员权限
|
||||
/// </summary>
|
||||
private readonly TraceEventSession _session = new(sessionName);
|
||||
|
||||
private readonly ETWTraceEventSource _source = new(sessionName, TraceEventSourceType.Session);
|
||||
|
||||
/// <summary>
|
||||
@@ -36,11 +37,12 @@ public class EtwNetworkCapture(string sessionName = "NetworkETWSession") : IDisp
|
||||
public delegate void NetworkErrorEventHandler(NetworkErrorEventData eventData);
|
||||
|
||||
// 事件处理器
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 通用网络事件处理,所有的网络事件都会进入该函数
|
||||
/// </summary>
|
||||
public event NetworkEventHandler? OnNetworkEvent;
|
||||
|
||||
public event TcpConnectionEventHandler? OnTcpConnectionEvent;
|
||||
public event UdpPacketEventHandler? OnUdpPacketEvent;
|
||||
public event DnsEventHandler? OnDnsEvent;
|
||||
@@ -68,11 +70,11 @@ public class EtwNetworkCapture(string sessionName = "NetworkETWSession") : IDisp
|
||||
// 开始处理事件(这是阻塞调用)
|
||||
Task.Run(() => { _source.Process(); });
|
||||
|
||||
Console.WriteLine("ETW网络捕获已启动...");
|
||||
Log.Info("ETW网络捕获已启动...");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"启动ETW捕获失败: {ex.Message}");
|
||||
Log.Info($"启动ETW捕获失败: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -197,7 +199,7 @@ public class EtwNetworkCapture(string sessionName = "NetworkETWSession") : IDisp
|
||||
OnTcpConnectionEvent?.Invoke(tcpEvent);
|
||||
OnNetworkEvent?.Invoke(tcpEvent);
|
||||
};
|
||||
|
||||
|
||||
// TCP数据接收事件 ETW Event ID 11
|
||||
parser.TcpIpRecv += data =>
|
||||
{
|
||||
@@ -270,12 +272,12 @@ public class EtwNetworkCapture(string sessionName = "NetworkETWSession") : IDisp
|
||||
OnNetworkEvent?.Invoke(udpEvent);
|
||||
};
|
||||
// 建议添加进程事件监控以获取完整进程信息
|
||||
parser.ProcessStart += data => {
|
||||
parser.ProcessStart += data =>
|
||||
{
|
||||
// 缓存进程信息,包括完整路径、命令行等
|
||||
};
|
||||
// 通用事件处理器,用于其他网络事件
|
||||
_source.Dynamic.All += ProcessDynamicEvent;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -296,13 +298,13 @@ public class EtwNetworkCapture(string sessionName = "NetworkETWSession") : IDisp
|
||||
case "Microsoft-Windows-NetworkProfile":
|
||||
ProcessNetworkInterfaceEvent(data);
|
||||
break;
|
||||
// 负责 TCP/IP 协议栈事件,如连接、发送、接收、断开、重传等
|
||||
// 用于监控 TCP 会话生命周期,是连接追踪的核心
|
||||
// 负责 TCP/IP 协议栈事件,如连接、发送、接收、断开、重传等
|
||||
// 用于监控 TCP 会话生命周期,是连接追踪的核心
|
||||
case "Microsoft-Windows-TCPIP":
|
||||
// AFD(Ancillary Function Driver for WinSock)是 Winsock API 的核心内核模块
|
||||
// 常用于检测哪一个进程创建了 socket,socket 和 TCP 会话的关联
|
||||
// AFD(Ancillary Function Driver for WinSock)是 Winsock API 的核心内核模块
|
||||
// 常用于检测哪一个进程创建了 socket,socket 和 TCP 会话的关联
|
||||
case "Microsoft-Windows-Winsock-AFD":
|
||||
// 也叫 Kernel-Network
|
||||
// 也叫 Kernel-Network
|
||||
case "MSNT_SystemTrace":
|
||||
break;
|
||||
default:
|
||||
@@ -337,7 +339,7 @@ public class EtwNetworkCapture(string sessionName = "NetworkETWSession") : IDisp
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"处理DNS事件时出错: {ex.Message}");
|
||||
Log.Info($"处理DNS事件时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,7 +368,7 @@ public class EtwNetworkCapture(string sessionName = "NetworkETWSession") : IDisp
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"处理HTTP事件时出错: {ex.Message}");
|
||||
Log.Info($"处理HTTP事件时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,7 +398,7 @@ public class EtwNetworkCapture(string sessionName = "NetworkETWSession") : IDisp
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"处理网络接口事件时出错: {ex.Message}");
|
||||
Log.Info($"处理网络接口事件时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,7 +418,7 @@ public class EtwNetworkCapture(string sessionName = "NetworkETWSession") : IDisp
|
||||
ThreadId = data.ThreadID,
|
||||
ProcessName = data.ProcessName
|
||||
};
|
||||
Console.WriteLine($"data.PayloadName :=> {data.ProviderName}");
|
||||
Log.Info($"data.PayloadName :=> {data.ProviderName}");
|
||||
|
||||
// 提取通用网络信息
|
||||
foreach (var payloadName in data.PayloadNames)
|
||||
@@ -436,7 +438,7 @@ public class EtwNetworkCapture(string sessionName = "NetworkETWSession") : IDisp
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"处理通用网络事件时出错: {ex.Message}");
|
||||
Log.Info($"处理通用网络事件时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,11 +451,11 @@ public class EtwNetworkCapture(string sessionName = "NetworkETWSession") : IDisp
|
||||
_isCapturing = false;
|
||||
_session.Stop();
|
||||
_source.Dispose();
|
||||
Console.WriteLine("ETW网络捕获已停止");
|
||||
Log.Info("ETW网络捕获已停止");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"停止ETW捕获时出错: {ex.Message}");
|
||||
Log.Info($"停止ETW捕获时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,4 +465,4 @@ public class EtwNetworkCapture(string sessionName = "NetworkETWSession") : IDisp
|
||||
_session.Dispose();
|
||||
_source.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Common.Logger;
|
||||
using Utils.ETW.EtwTracker;
|
||||
using Utils.ETW.Models;
|
||||
|
||||
@@ -8,10 +9,7 @@ public class HttpTracker : INetTracker
|
||||
{
|
||||
public void SetupEtwHandlers(EtwNetworkCapture networkCapture)
|
||||
{
|
||||
networkCapture.OnHttpEvent += httpEvent =>
|
||||
{
|
||||
Console.WriteLine($"[HTTP] {httpEvent.HttpMethod} {httpEvent.Url}");
|
||||
};
|
||||
networkCapture.OnHttpEvent += httpEvent => { Log.Info($"[HTTP] {httpEvent.HttpMethod} {httpEvent.Url}"); };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +19,7 @@ public class NetworkProfileTracker : INetTracker
|
||||
{
|
||||
networkCapture.OnNetworkInterfaceEvent += interfaceEvent =>
|
||||
{
|
||||
Console.WriteLine($"[Interface] {interfaceEvent.InterfaceName}: {interfaceEvent.InterfaceState}");
|
||||
Log.Info($"[Interface] {interfaceEvent.InterfaceName}: {interfaceEvent.InterfaceState}");
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -64,8 +62,8 @@ public class EtwNetworkManger
|
||||
|
||||
// TCP
|
||||
// _tcpTracker.SetupEtwHandlers(networkCapture);
|
||||
|
||||
Console.WriteLine("进入了!!!!!!!!!!");
|
||||
|
||||
Log.Info("进入了!!!!!!!!!!");
|
||||
// DNS
|
||||
dnsTracker.SetupEtwHandlers(networkCapture);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Net;
|
||||
using Common.Logger;
|
||||
using Utils.ETW.EtwTracker;
|
||||
using Utils.ETW.Models;
|
||||
|
||||
@@ -13,8 +14,8 @@ public class UdpTracker : INetTracker
|
||||
{
|
||||
networkCapture.OnUdpPacketEvent += (udpEvent) =>
|
||||
{
|
||||
Console.WriteLine($"[UDP] {udpEvent.EventType}: {udpEvent.SourceIp}:{udpEvent.SourcePort} -> " +
|
||||
$"{udpEvent.DestinationIp}:{udpEvent.DestinationPort} ({udpEvent.DataLength} bytes)");
|
||||
Log.Info($"[UDP] {udpEvent.EventType}: {udpEvent.SourceIp}:{udpEvent.SourcePort} -> " +
|
||||
$"{udpEvent.DestinationIp}:{udpEvent.DestinationPort} ({udpEvent.DataLength} bytes)");
|
||||
|
||||
switch (udpEvent.EventType)
|
||||
{
|
||||
@@ -100,9 +101,10 @@ public class UdpTracker : INetTracker
|
||||
ProcessName = eventData.ProcessName
|
||||
};
|
||||
|
||||
NetworkInfoManger.Instance.SetUdpCache( networkModel);
|
||||
NetworkInfoManger.Instance.SetUdpCache(networkModel);
|
||||
|
||||
string str = $"UDP 会话开始: {networkModel.SourceIp}:{networkModel.SourcePort} -> {networkModel.DestinationIp}:{networkModel.DestinationPort}";
|
||||
string str =
|
||||
$"UDP 会话开始: {networkModel.SourceIp}:{networkModel.SourcePort} -> {networkModel.DestinationIp}:{networkModel.DestinationPort}";
|
||||
NetworkInfoManger.Instance.RecordEvent(str);
|
||||
}
|
||||
else
|
||||
@@ -111,19 +113,20 @@ public class UdpTracker : INetTracker
|
||||
existingSession.BytesSent += eventData.UdpLength;
|
||||
existingSession.LastSeenTime = now;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (eventData.DataLength < 0)
|
||||
{
|
||||
throw new Exception("数据长度不可能为 0 !!");
|
||||
}
|
||||
|
||||
// 统计端口流量
|
||||
NetworkInfoManger.Instance.PortTrafficSent.TryAdd(eventData.DestinationPort, 0);
|
||||
NetworkInfoManger.Instance.PortTrafficSent[eventData.DestinationPort] += (ulong)eventData.DataLength;
|
||||
|
||||
NetworkInfoManger.Instance.SourceTrafficSent.TryAdd(eventData.SourceIp, 0);
|
||||
NetworkInfoManger.Instance.SourceTrafficSent[eventData.SourceIp] += (ulong)eventData.DataLength;
|
||||
|
||||
|
||||
// UDP处理主要关注数据包本身,而不是连接状态
|
||||
UpdateTrafficStats(eventData);
|
||||
}
|
||||
@@ -178,9 +181,10 @@ public class UdpTracker : INetTracker
|
||||
ProcessName = eventData.ProcessName
|
||||
};
|
||||
|
||||
NetworkInfoManger.Instance.SetUdpCache( networkModel);
|
||||
NetworkInfoManger.Instance.SetUdpCache(networkModel);
|
||||
|
||||
string str = $"UDP 会话开始: {networkModel.SourceIp}:{networkModel.SourcePort} -> {networkModel.DestinationIp}:{networkModel.DestinationPort}";
|
||||
string str =
|
||||
$"UDP 会话开始: {networkModel.SourceIp}:{networkModel.SourcePort} -> {networkModel.DestinationIp}:{networkModel.DestinationPort}";
|
||||
NetworkInfoManger.Instance.RecordEvent(str);
|
||||
}
|
||||
else
|
||||
@@ -189,11 +193,12 @@ public class UdpTracker : INetTracker
|
||||
existingSession.BytesReceived += eventData.UdpLength;
|
||||
existingSession.LastSeenTime = now;
|
||||
}
|
||||
|
||||
|
||||
if (eventData.DataLength < 0)
|
||||
{
|
||||
throw new Exception("数据长度不可能为 0 !!");
|
||||
}
|
||||
|
||||
// 统计端口流量
|
||||
NetworkInfoManger.Instance.PortTrafficReceived.TryAdd(eventData.DestinationPort, 0);
|
||||
NetworkInfoManger.Instance.PortTrafficReceived[eventData.DestinationPort] += (ulong)eventData.DataLength;
|
||||
@@ -214,7 +219,7 @@ public class UdpTracker : INetTracker
|
||||
sourceTraffic.TryAdd(udpEvent.SourceIp, 0);
|
||||
sourceTraffic[udpEvent.SourceIp] += udpEvent.DataLength;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 因为 UDP 无断开标志,所以需要一个定期清理机制
|
||||
/// </summary>
|
||||
@@ -229,7 +234,8 @@ public class UdpTracker : INetTracker
|
||||
{
|
||||
NetworkInfoManger.Instance.RemoveUdpSession(session);
|
||||
|
||||
string str = $"UDP 会话结束: {session.SourceIp}:{session.SourcePort} -> {session.DestinationIp}:{session.DestinationPort}";
|
||||
string str =
|
||||
$"UDP 会话结束: {session.SourceIp}:{session.SourcePort} -> {session.DestinationIp}:{session.DestinationPort}";
|
||||
NetworkInfoManger.Instance.RecordEvent(str);
|
||||
}
|
||||
}
|
||||
@@ -246,7 +252,7 @@ public class UdpTracker : INetTracker
|
||||
|
||||
private void ProcessDhcpPacket(UdpPacketEventData udpEvent)
|
||||
{
|
||||
Console.WriteLine($"[DHCP] DHCP通信: {udpEvent.SourceIp} <-> {udpEvent.DestinationIp}");
|
||||
Log.Info($"[DHCP] DHCP通信: {udpEvent.SourceIp} <-> {udpEvent.DestinationIp}");
|
||||
}
|
||||
|
||||
private void ProcessGenericUdpPacket(UdpPacketEventData udpEvent)
|
||||
@@ -254,24 +260,24 @@ public class UdpTracker : INetTracker
|
||||
// 对于未知UDP流量,主要进行安全检查
|
||||
if (udpEvent.DataLength > 1024 * 64) // 64KB
|
||||
{
|
||||
Console.WriteLine($"[UDP警告] 大型UDP数据包: {udpEvent.ProcessName} " +
|
||||
$"发送 {udpEvent.DataLength} 字节到 {udpEvent.DestinationIp}:{udpEvent.DestinationPort}");
|
||||
Log.Info($"[UDP警告] 大型UDP数据包: {udpEvent.ProcessName} " +
|
||||
$"发送 {udpEvent.DataLength} 字节到 {udpEvent.DestinationIp}:{udpEvent.DestinationPort}");
|
||||
}
|
||||
}
|
||||
|
||||
public void PrintTrafficSummary()
|
||||
{
|
||||
Console.WriteLine("\n=== UDP流量统计 ===");
|
||||
Console.WriteLine("端口流量TOP 10:");
|
||||
Log.Info("\n=== UDP流量统计 ===");
|
||||
Log.Info("端口流量TOP 10:");
|
||||
foreach (var item in portTraffic.OrderByDescending(x => x.Value).Take(10))
|
||||
{
|
||||
Console.WriteLine($" 端口 {item.Key}: {item.Value / 1024}KB");
|
||||
Log.Info($" 端口 {item.Key}: {item.Value / 1024}KB");
|
||||
}
|
||||
|
||||
Console.WriteLine("\n源IP流量TOP 10:");
|
||||
Log.Info("\n源IP流量TOP 10:");
|
||||
foreach (var item in sourceTraffic.OrderByDescending(x => x.Value).Take(10))
|
||||
{
|
||||
Console.WriteLine($" {item.Key}: {item.Value / 1024}KB");
|
||||
Log.Info($" {item.Key}: {item.Value / 1024}KB");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using Common.Logger;
|
||||
using Infrastructure.utils;
|
||||
using Utils.ETW.Core;
|
||||
using Utils.ETW.Models;
|
||||
@@ -63,7 +64,7 @@ public class EnhancedEtwNetworkManager : IDisposable
|
||||
Task.Run(() => ProcessDnsLookupsAsync(_cancellationTokenSource.Token));
|
||||
Task.Run(() => CleanupExpiredDataAsync(_cancellationTokenSource.Token));
|
||||
|
||||
Console.WriteLine("增强型网络监控已启动");
|
||||
Log.Info("增强型网络监控已启动");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -77,7 +78,7 @@ public class EnhancedEtwNetworkManager : IDisposable
|
||||
_cancellationTokenSource.Cancel();
|
||||
_networkCapture.StopCapture();
|
||||
|
||||
Console.WriteLine("增强型网络监控已停止");
|
||||
Log.Info("增强型网络监控已停止");
|
||||
}
|
||||
|
||||
#region 事件处理方法
|
||||
@@ -146,7 +147,7 @@ public class EnhancedEtwNetworkManager : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"处理TCP事件时出错: {ex.Message}");
|
||||
Log.Info($"处理TCP事件时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +196,7 @@ public class EnhancedEtwNetworkManager : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"处理UDP事件时出错: {ex.Message}");
|
||||
Log.Info($"处理UDP事件时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +215,7 @@ public class EnhancedEtwNetworkManager : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"处理DNS事件时出错: {ex.Message}");
|
||||
Log.Info($"处理DNS事件时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,11 +224,11 @@ public class EnhancedEtwNetworkManager : IDisposable
|
||||
try
|
||||
{
|
||||
// 可以从HTTP事件中提取更多应用层信息
|
||||
Console.WriteLine($"[HTTP] {httpEvent.HttpMethod} {httpEvent.Url}");
|
||||
Log.Info($"[HTTP] {httpEvent.HttpMethod} {httpEvent.Url}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"处理HTTP事件时出错: {ex.Message}");
|
||||
Log.Info($"处理HTTP事件时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +256,7 @@ public class EnhancedEtwNetworkManager : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"更新进程信息时出错: {ex.Message}");
|
||||
Log.Info($"更新进程信息时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,7 +292,7 @@ public class EnhancedEtwNetworkManager : IDisposable
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 进程可能已经退出
|
||||
Console.WriteLine($"获取进程 {processInfo.ProcessId} 信息失败: {ex.Message}");
|
||||
Log.Info($"获取进程 {processInfo.ProcessId} 信息失败: {ex.Message}");
|
||||
processInfo.NeedsUpdate = false;
|
||||
}
|
||||
}
|
||||
@@ -300,7 +301,7 @@ public class EnhancedEtwNetworkManager : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"处理应用程序信息时出错: {ex.Message}");
|
||||
Log.Info($"处理应用程序信息时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -344,7 +345,7 @@ public class EnhancedEtwNetworkManager : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"处理DNS查询时出错: {ex.Message}");
|
||||
Log.Info($"处理DNS查询时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -367,7 +368,7 @@ public class EnhancedEtwNetworkManager : IDisposable
|
||||
{
|
||||
GlobalNetworkMonitor.Instance.RemoveConnection(conn.ConnectionKey);
|
||||
// 这里可以添加清理逻辑
|
||||
Console.WriteLine(
|
||||
Log.Info(
|
||||
$"清理过期连接: {app.ProgramInfo?.ProductName} - {conn.RemoteIp}:{conn.RemotePort}");
|
||||
}
|
||||
}
|
||||
@@ -376,7 +377,7 @@ public class EnhancedEtwNetworkManager : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"清理过期数据时出错: {ex.Message}");
|
||||
Log.Info($"清理过期数据时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using Common.Logger;
|
||||
using Common.Utils;
|
||||
using Utils.ETW.Core;
|
||||
|
||||
@@ -15,7 +16,7 @@ public class NetworkMonitorUsageExample
|
||||
|
||||
public void RunExample()
|
||||
{
|
||||
Console.WriteLine("启动网络监控系统...");
|
||||
Log.Info("启动网络监控系统...");
|
||||
|
||||
// 初始化网络管理器
|
||||
_networkManager = new EnhancedEtwNetworkManager();
|
||||
@@ -32,7 +33,7 @@ public class NetworkMonitorUsageExample
|
||||
// 启动WebSocket推送任务(示例)
|
||||
// var websocketTask = Task.Run(() => SimulateWebSocketPush(_cancellationTokenSource.Token));
|
||||
|
||||
// Console.WriteLine("网络监控已启动,按任意键停止...");
|
||||
// Log.Info("网络监控已启动,按任意键停止...");
|
||||
// Console.ReadKey();
|
||||
//
|
||||
// // 停止监控
|
||||
@@ -42,7 +43,7 @@ public class NetworkMonitorUsageExample
|
||||
finally
|
||||
{
|
||||
// _networkManager?.Dispose();
|
||||
// Console.WriteLine("网络监控已停止!!!!!!!!!!!!!11!!!!");
|
||||
// Log.Info("网络监控已停止!!!!!!!!!!!!!11!!!!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,26 +60,26 @@ public class NetworkMonitorUsageExample
|
||||
var snapshot = GlobalNetworkMonitor.Instance.GetSnapshot();
|
||||
|
||||
var serialize = JsonHelper.ToJson(snapshot);
|
||||
Console.WriteLine("=================================================");
|
||||
Console.WriteLine(serialize);
|
||||
Console.WriteLine("=================================================");
|
||||
Log.Info("=================================================");
|
||||
Log.Info(serialize);
|
||||
Log.Info("=================================================");
|
||||
// 输出概要信息
|
||||
Console.WriteLine($"\n========== 网络监控快照 {snapshot.SnapshotTime:yyyy-MM-dd HH:mm:ss} ==========");
|
||||
Console.WriteLine($"活跃应用程序: {snapshot.GlobalStats.TotalApplications}");
|
||||
Console.WriteLine(
|
||||
Log.Info($"\n========== 网络监控快照 {snapshot.SnapshotTime:yyyy-MM-dd HH:mm:ss} ==========");
|
||||
Log.Info($"活跃应用程序: {snapshot.GlobalStats.TotalApplications}");
|
||||
Log.Info(
|
||||
$"总连接数: {snapshot.GlobalStats.TotalConnections} (活跃: {snapshot.GlobalStats.ActiveConnections})");
|
||||
|
||||
// 输出每个应用的详细信息
|
||||
// foreach (var app in snapshot.Applications.OrderByDescending(a =>
|
||||
// a.TotalSendSpeed + a.TotalReceiveSpeed))
|
||||
// {
|
||||
// Console.WriteLine($"\n应用程序: {app.ApplicationName} (PID: {app.ProcessId})");
|
||||
// Console.WriteLine($" 路径: {app.ApplicationPath}");
|
||||
// Console.WriteLine($" 版本: {app.Version}");
|
||||
// Console.WriteLine($" 连接数: {app.ActiveConnections}/{app.TotalConnections}");
|
||||
// Console.WriteLine(
|
||||
// Log.Info($"\n应用程序: {app.ApplicationName} (PID: {app.ProcessId})");
|
||||
// Log.Info($" 路径: {app.ApplicationPath}");
|
||||
// Log.Info($" 版本: {app.Version}");
|
||||
// Log.Info($" 连接数: {app.ActiveConnections}/{app.TotalConnections}");
|
||||
// Log.Info(
|
||||
// $" 总流量: 发送 {FormatBytes(app.TotalBytesSent)}, 接收 {FormatBytes(app.TotalBytesReceived)}");
|
||||
// Console.WriteLine(
|
||||
// Log.Info(
|
||||
// $" 当前速度: ↑{FormatSpeed(app.TotalSendSpeed)} ↓{FormatSpeed(app.TotalReceiveSpeed)}");
|
||||
//
|
||||
// // 显示前5个活跃连接
|
||||
@@ -89,7 +90,7 @@ public class NetworkMonitorUsageExample
|
||||
//
|
||||
// foreach (var conn in topConnections)
|
||||
// {
|
||||
// Console.WriteLine(
|
||||
// Log.Info(
|
||||
// $" → {conn.Protocol} {conn.LocalPort} → {conn.RemoteIp}:{conn.RemotePort} " +
|
||||
// $"({conn.RemoteDomain ?? "未解析"}) " +
|
||||
// $"[{conn.State}] " +
|
||||
@@ -98,24 +99,24 @@ public class NetworkMonitorUsageExample
|
||||
// }
|
||||
//
|
||||
// // 输出端口流量TOP 5
|
||||
// Console.WriteLine("\n端口流量 TOP 5:");
|
||||
// Log.Info("\n端口流量 TOP 5:");
|
||||
// var topPorts = snapshot.GlobalStats.PortTraffic
|
||||
// .OrderByDescending(p => p.TotalBytes)
|
||||
// .Take(5);
|
||||
//
|
||||
// foreach (var port in topPorts)
|
||||
// {
|
||||
// Console.WriteLine($" 端口 {port.Port}: {FormatBytes(port.TotalBytes)} " +
|
||||
// Log.Info($" 端口 {port.Port}: {FormatBytes(port.TotalBytes)} " +
|
||||
// $"(发送: {FormatBytes(port.BytesSent)}, 接收: {FormatBytes(port.BytesReceived)})");
|
||||
// }
|
||||
//
|
||||
// // 输出IP流量TOP 5
|
||||
// Console.WriteLine("\nIP流量 TOP 5:");
|
||||
// Log.Info("\nIP流量 TOP 5:");
|
||||
// var topIps = snapshot.GlobalStats.IpTraffic.Take(5);
|
||||
//
|
||||
// foreach (var ip in topIps)
|
||||
// {
|
||||
// Console.WriteLine($" {ip.IpAddress}: {FormatBytes(ip.TotalBytes)} " +
|
||||
// Log.Info($" {ip.IpAddress}: {FormatBytes(ip.TotalBytes)} " +
|
||||
// $"(发送: {FormatBytes(ip.BytesSent)}, 接收: {FormatBytes(ip.BytesReceived)})");
|
||||
// }
|
||||
|
||||
@@ -123,7 +124,7 @@ public class NetworkMonitorUsageExample
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"读取网络数据时出错: {ex.Message}");
|
||||
Log.Info($"读取网络数据时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,13 +188,13 @@ public class NetworkMonitorUsageExample
|
||||
//
|
||||
// // 这里应该通过WebSocket发送数据
|
||||
// // await websocketClient.SendAsync(json);
|
||||
// Console.WriteLine($"\n[WebSocket模拟] 推送数据大小: {json.Length} 字节");
|
||||
// Console.WriteLine(json);
|
||||
// Log.Info($"\n[WebSocket模拟] 推送数据大小: {json.Length} 字节");
|
||||
// Log.Info(json);
|
||||
// await Task.Delay(1000, cancellationToken); // 每秒推送一次
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Console.WriteLine($"WebSocket推送时出错: {ex.Message}");
|
||||
// Log.Info($"WebSocket推送时出错: {ex.Message}");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -67,8 +67,8 @@ public class TcpTracker : INetTracker
|
||||
/// <param name="tcpEvent"></param>
|
||||
public void ProcessTcpEvent(TcpConnectionEventData tcpEvent)
|
||||
{
|
||||
Console.WriteLine($"[TCP] {tcpEvent.EventType}: {tcpEvent.SourceIp}:{tcpEvent.SourcePort} -> " +
|
||||
$"{tcpEvent.DestinationIp}:{tcpEvent.DestinationPort} ({tcpEvent.ProcessName})");
|
||||
Log.Info($"[TCP] {tcpEvent.EventType}: {tcpEvent.SourceIp}:{tcpEvent.SourcePort} -> " +
|
||||
$"{tcpEvent.DestinationIp}:{tcpEvent.DestinationPort} ({tcpEvent.ProcessName})");
|
||||
|
||||
switch (tcpEvent.EventType)
|
||||
{
|
||||
@@ -277,21 +277,21 @@ public class TcpTracker : INetTracker
|
||||
// 1. 安全检查
|
||||
// if (IsExternalConnection(session.DestinationIp))
|
||||
// {
|
||||
// Console.WriteLine(
|
||||
// Log.Info(
|
||||
// $"[SECURITY] 外部连接警告: {session.ProcessName} -> {session.DestinationIp}:{session.DestinationPort}");
|
||||
// }
|
||||
|
||||
// 2. 业务监控
|
||||
// if (IsBusinessCriticalPort(session.DestinationPort))
|
||||
// {
|
||||
// Console.WriteLine($"[BUSINESS] 关键业务连接: {session.ProcessName} -> {session.DestinationPort}");
|
||||
// Log.Info($"[BUSINESS] 关键业务连接: {session.ProcessName} -> {session.DestinationPort}");
|
||||
// }
|
||||
|
||||
// 3. 性能监控
|
||||
var tcpSize = NetworkInfoManger.Instance.GetTcpSize();
|
||||
if (tcpSize > 1000)
|
||||
{
|
||||
Console.WriteLine($"[PERFORMANCE] 连接数量警告: 当前活跃连接 {tcpSize}");
|
||||
Log.Info($"[PERFORMANCE] 连接数量警告: 当前活跃连接 {tcpSize}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,4 +341,4 @@ public class TcpTracker : INetTracker
|
||||
.Where(s => s.Duration > threshold)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user