U:调整结构性目录
This commit is contained in:
1 parent
693b390df0
commit
c255419412
172 files changed
+839
-18898
No files matched your search
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fleck" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fleck" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Application;
|
||||
|
||||
|
||||
// 7. 示例消息处理器
|
||||
public class ChatMessageHandler : IMessageHandler
|
||||
{
|
||||
public string MessageType => "chat";
|
||||
|
||||
public async Task<object?> HandleAsync(string connectionId, string data)
|
||||
{
|
||||
try
|
||||
{
|
||||
var chatData = JsonSerializer.Deserialize<ChatData>(data);
|
||||
if (chatData == null) return new { status = "error", message = "无效的聊天数据" };
|
||||
|
||||
// 处理聊天逻辑
|
||||
return new
|
||||
{
|
||||
status = "success",
|
||||
message = "聊天消息已接收",
|
||||
echo = new
|
||||
{
|
||||
from = connectionId,
|
||||
content = chatData.Content,
|
||||
timestamp = DateTime.UtcNow
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new { status = "error", message = ex.Message };
|
||||
}
|
||||
}
|
||||
|
||||
public class ChatData
|
||||
{
|
||||
public string Content { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
namespace Application;
|
||||
|
||||
public class Class1
|
||||
{
|
||||
void main()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
namespace Application;
|
||||
|
||||
|
||||
// 4. 实现连接管理器
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net.WebSockets;
|
||||
|
||||
public class ConnectionManager : IConnectionManager
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, WebSocket> _connections = new();
|
||||
|
||||
public void AddConnection(string connectionId, WebSocket webSocket)
|
||||
{
|
||||
_connections.TryAdd(connectionId, webSocket);
|
||||
}
|
||||
|
||||
public void RemoveConnection(string connectionId)
|
||||
{
|
||||
if (_connections.TryRemove(connectionId, out var webSocket))
|
||||
{
|
||||
if (webSocket.State == WebSocketState.Open)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Connection removed", CancellationToken.None);
|
||||
}
|
||||
catch { /* 忽略关闭时的异常 */ }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public WebSocket? GetConnection(string connectionId)
|
||||
{
|
||||
_connections.TryGetValue(connectionId, out var webSocket);
|
||||
return webSocket?.State == WebSocketState.Open ? webSocket : null;
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetAllConnectionIds()
|
||||
{
|
||||
return _connections.Where(c => c.Value.State == WebSocketState.Open).Select(c => c.Key);
|
||||
}
|
||||
|
||||
public int Count => _connections.Count(c => c.Value.State == WebSocketState.Open);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System.Net.WebSockets;
|
||||
|
||||
namespace Application;
|
||||
|
||||
// 1. 服务接口定义 - 便于单元测试和依赖注入
|
||||
public interface IWebSocketServerService
|
||||
{
|
||||
event Action<string, string>? MessageReceived;
|
||||
event Action<string>? ClientConnected;
|
||||
event Action<string>? ClientDisconnected;
|
||||
event Action<string>? ServerMessage;
|
||||
|
||||
Task<bool> StartAsync(string url);
|
||||
Task StopAsync();
|
||||
Task SendMessageAsync(string connectionId, object message);
|
||||
Task BroadcastMessageAsync(object message);
|
||||
bool IsRunning { get; }
|
||||
int ConnectionCount { get; }
|
||||
IEnumerable<string> GetConnectedClients();
|
||||
}
|
||||
|
||||
// 2. 消息处理策略接口
|
||||
public interface IMessageHandler
|
||||
{
|
||||
string MessageType { get; }
|
||||
Task<object?> HandleAsync(string connectionId, string data);
|
||||
}
|
||||
|
||||
// 3. 连接管理接口
|
||||
public interface IConnectionManager
|
||||
{
|
||||
void AddConnection(string connectionId, WebSocket webSocket);
|
||||
void RemoveConnection(string connectionId);
|
||||
WebSocket? GetConnection(string connectionId);
|
||||
IEnumerable<string> GetAllConnectionIds();
|
||||
int Count { get; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Application;
|
||||
|
||||
|
||||
// 5. 消息模型
|
||||
public class WebSocketMessage
|
||||
{
|
||||
public string Type { get; set; } = string.Empty;
|
||||
public string Data { get; set; } = string.Empty;
|
||||
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
||||
public string? ConnectionId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
namespace Application;
|
||||
|
||||
|
||||
// 6. 主要的WebSocket服务实现
|
||||
using System.Net;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
public class WebSocketServerService : IWebSocketServerService, IDisposable
|
||||
{
|
||||
private readonly IConnectionManager _connectionManager;
|
||||
private readonly Dictionary<string, IMessageHandler> _messageHandlers;
|
||||
private HttpListener? _httpListener;
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
private readonly SemaphoreSlim _startStopSemaphore = new(1, 1);
|
||||
|
||||
public event Action<string, string>? MessageReceived;
|
||||
public event Action<string>? ClientConnected;
|
||||
public event Action<string>? ClientDisconnected;
|
||||
public event Action<string>? ServerMessage;
|
||||
|
||||
public bool IsRunning { get; private set; }
|
||||
public int ConnectionCount => _connectionManager.Count;
|
||||
|
||||
public WebSocketServerService(IConnectionManager connectionManager)
|
||||
{
|
||||
_connectionManager = connectionManager;
|
||||
_messageHandlers = new Dictionary<string, IMessageHandler>();
|
||||
}
|
||||
|
||||
// 注册消息处理器
|
||||
public void RegisterMessageHandler(IMessageHandler handler)
|
||||
{
|
||||
_messageHandlers[handler.MessageType.ToLower()] = handler;
|
||||
}
|
||||
|
||||
public async Task<bool> StartAsync(string url)
|
||||
{
|
||||
await _startStopSemaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (IsRunning) return true;
|
||||
|
||||
_httpListener = new HttpListener();
|
||||
_httpListener.Prefixes.Add(url);
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
try
|
||||
{
|
||||
_httpListener.Start();
|
||||
IsRunning = true;
|
||||
ServerMessage?.Invoke($"WebSocket服务器已启动: {url}");
|
||||
|
||||
// 开始监听连接
|
||||
_ = Task.Run(() => ListenForConnectionsAsync(_cancellationTokenSource.Token));
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ServerMessage?.Invoke($"启动服务器失败: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_startStopSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StopAsync()
|
||||
{
|
||||
await _startStopSemaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (!IsRunning) return;
|
||||
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_httpListener?.Stop();
|
||||
_httpListener?.Close();
|
||||
|
||||
// 关闭所有连接
|
||||
foreach (var connectionId in _connectionManager.GetAllConnectionIds().ToList())
|
||||
{
|
||||
_connectionManager.RemoveConnection(connectionId);
|
||||
}
|
||||
|
||||
IsRunning = false;
|
||||
ServerMessage?.Invoke("WebSocket服务器已停止");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_startStopSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ListenForConnectionsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested && _httpListener!.IsListening)
|
||||
{
|
||||
try
|
||||
{
|
||||
var context = await _httpListener.GetContextAsync();
|
||||
|
||||
if (context.Request.IsWebSocketRequest)
|
||||
{
|
||||
// 不等待,让连接处理在后台进行
|
||||
_ = Task.Run(() => ProcessWebSocketRequestAsync(context, cancellationToken), cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 对非WebSocket请求返回404
|
||||
context.Response.StatusCode = 404;
|
||||
context.Response.Close();
|
||||
}
|
||||
}
|
||||
catch (HttpListenerException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ServerMessage?.Invoke($"监听连接时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessWebSocketRequestAsync(HttpListenerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
WebSocketContext? webSocketContext = null;
|
||||
string connectionId = Guid.NewGuid().ToString();
|
||||
|
||||
try
|
||||
{
|
||||
webSocketContext = await context.AcceptWebSocketAsync(null);
|
||||
var webSocket = webSocketContext.WebSocket;
|
||||
|
||||
_connectionManager.AddConnection(connectionId, webSocket);
|
||||
ClientConnected?.Invoke(connectionId);
|
||||
ServerMessage?.Invoke($"客户端连接: {connectionId}");
|
||||
|
||||
await HandleWebSocketConnectionAsync(webSocket, connectionId, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ServerMessage?.Invoke($"处理WebSocket请求时出错 [{connectionId}]: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_connectionManager.RemoveConnection(connectionId);
|
||||
ClientDisconnected?.Invoke(connectionId);
|
||||
ServerMessage?.Invoke($"客户端断开: {connectionId}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleWebSocketConnectionAsync(WebSocket webSocket, string connectionId, CancellationToken cancellationToken)
|
||||
{
|
||||
var buffer = new byte[1024 * 4];
|
||||
|
||||
try
|
||||
{
|
||||
while (webSocket.State == WebSocketState.Open && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);
|
||||
|
||||
if (result.MessageType == WebSocketMessageType.Text)
|
||||
{
|
||||
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
|
||||
MessageReceived?.Invoke(connectionId, message);
|
||||
|
||||
// 处理消息但不等待响应,避免阻塞接收循环
|
||||
_ = Task.Run(() => ProcessMessageAsync(message, connectionId), cancellationToken);
|
||||
}
|
||||
else if (result.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", cancellationToken);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// 正常取消操作
|
||||
}
|
||||
catch (WebSocketException ex) when (ex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely)
|
||||
{
|
||||
// 客户端异常断开连接
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ServerMessage?.Invoke($"处理WebSocket连接时出错 [{connectionId}]: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessMessageAsync(string message, string connectionId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var messageObj = JsonSerializer.Deserialize<WebSocketMessage>(message);
|
||||
if (messageObj == null) return;
|
||||
|
||||
messageObj.ConnectionId = connectionId;
|
||||
messageObj.Timestamp = DateTime.UtcNow;
|
||||
|
||||
// 查找对应的消息处理器
|
||||
if (_messageHandlers.TryGetValue(messageObj.Type.ToLower(), out var handler))
|
||||
{
|
||||
var response = await handler.HandleAsync(connectionId, messageObj.Data);
|
||||
if (response != null)
|
||||
{
|
||||
await SendMessageAsync(connectionId, response);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorResponse = new { status = "error", message = $"未知消息类型: {messageObj.Type}" };
|
||||
await SendMessageAsync(connectionId, errorResponse);
|
||||
}
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
var errorResponse = new { status = "error", message = "无效的JSON格式" };
|
||||
await SendMessageAsync(connectionId, errorResponse);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ServerMessage?.Invoke($"处理消息时出错 [{connectionId}]: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SendMessageAsync(string connectionId, object message)
|
||||
{
|
||||
var webSocket = _connectionManager.GetConnection(connectionId);
|
||||
if (webSocket != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = JsonSerializer.Serialize(message);
|
||||
var buffer = Encoding.UTF8.GetBytes(json);
|
||||
await webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ServerMessage?.Invoke($"发送消息失败 [{connectionId}]: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task BroadcastMessageAsync(object message)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(message);
|
||||
var tasks = new List<Task>();
|
||||
|
||||
foreach (var connectionId in _connectionManager.GetAllConnectionIds())
|
||||
{
|
||||
tasks.Add(SendMessageAsync(connectionId, message));
|
||||
}
|
||||
|
||||
if (tasks.Count > 0)
|
||||
{
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetConnectedClients()
|
||||
{
|
||||
return _connectionManager.GetAllConnectionIds();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_ = Task.Run(async () => await StopAsync());
|
||||
_startStopSemaphore?.Dispose();
|
||||
_cancellationTokenSource?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fleck" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,37 @@
|
||||
using Common.Logger;
|
||||
|
||||
namespace Common.Net.WebSocketConn;
|
||||
|
||||
// 主程序示例
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var wsManager = WebSocketManager.Instance;
|
||||
|
||||
try
|
||||
{
|
||||
// 启动WebSocket服务器
|
||||
wsManager.Start("ws://127.0.0.1:8080");
|
||||
|
||||
// 注册自定义处理器
|
||||
wsManager.RegisterHandler("customCommand", async (cmd, socket) =>
|
||||
{
|
||||
Log.Information($"处理自定义命令: {cmd.Command}");
|
||||
await wsManager.SendToClient(socket.ConnectionInfo.Id, new ResponseMessage
|
||||
{
|
||||
Type = "customResponse",
|
||||
Success = true,
|
||||
Message = "自定义命令处理完成"
|
||||
});
|
||||
});
|
||||
|
||||
Log.Information("服务器运行中,按任意键退出...");
|
||||
Console.ReadKey();
|
||||
}
|
||||
finally
|
||||
{
|
||||
wsManager.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Common.Net.WebSocketConn;
|
||||
|
||||
// 消息基类
|
||||
public abstract class BaseMessage
|
||||
{
|
||||
[JsonPropertyName("messageId")] public string? MessageId { get; set; } = Guid.NewGuid().ToString();
|
||||
[JsonPropertyName("timestamp")] public DateTime Timestamp { get; set; } = DateTime.Now;
|
||||
}
|
||||
|
||||
// 网络状态消息
|
||||
public class NetworkStatusMessage : NotificationMessage
|
||||
{
|
||||
public string InterfaceName { get; set; }
|
||||
public string Status { get; set; }
|
||||
public long BytesSent { get; set; }
|
||||
public long BytesReceived { get; set; }
|
||||
public string IpAddress { get; set; }
|
||||
}
|
||||
|
||||
// 防火墙规则消息
|
||||
public class FirewallRuleMessage : NotificationMessage
|
||||
{
|
||||
public string RuleName { get; set; }
|
||||
public string Action { get; set; }
|
||||
public string Protocol { get; set; }
|
||||
public string Port { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
|
||||
// 命令消息
|
||||
public class CommandMessage : BaseMessage
|
||||
{
|
||||
[JsonPropertyName("command")] public string Command { get; set; }
|
||||
|
||||
[JsonPropertyName("data")] public object Data { get; set; }
|
||||
}
|
||||
|
||||
// 响应消息
|
||||
public class ResponseMessage : NotificationMessage
|
||||
{
|
||||
[JsonPropertyName("success")] public bool Success { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通知消息 【服务端发往客户端的消息】
|
||||
/// </summary>
|
||||
public class NotificationMessage : BaseMessage
|
||||
{
|
||||
[JsonPropertyName("type")] public string Type { get; set; } // 通知类型,如 "networkStatusUpdate", "firewallRuleChanged"
|
||||
[JsonPropertyName("data")] public object Data { get; set; }
|
||||
[JsonPropertyName("message")] public string Message { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text.Json;
|
||||
using Common.Logger;
|
||||
using Fleck;
|
||||
|
||||
namespace Common.Net.WebSocketConn;
|
||||
|
||||
// WebSocket管理器 - 单例模式
|
||||
public class WebSocketManager
|
||||
{
|
||||
private static readonly Lazy<WebSocketManager> _instance = new(() => new WebSocketManager());
|
||||
public static WebSocketManager Instance => _instance.Value;
|
||||
|
||||
private WebSocketServer _server;
|
||||
private readonly ConcurrentDictionary<Guid, IWebSocketConnection> _connections;
|
||||
private readonly object _lockObject = new();
|
||||
private bool _isStarted;
|
||||
|
||||
// 消息处理器字典
|
||||
private readonly ConcurrentDictionary<string, Func<CommandMessage, IWebSocketConnection, Task>> _messageHandlers;
|
||||
|
||||
private WebSocketManager()
|
||||
{
|
||||
_connections = new ConcurrentDictionary<Guid, IWebSocketConnection>();
|
||||
_messageHandlers = new ConcurrentDictionary<string, Func<CommandMessage, IWebSocketConnection, Task>>();
|
||||
RegisterDefaultHandlers();
|
||||
}
|
||||
|
||||
// 启动WebSocket服务器
|
||||
public void Start(string url = "ws://127.0.0.1:8080")
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (_isStarted) return;
|
||||
|
||||
_server = new WebSocketServer(url);
|
||||
_server.Start(socket =>
|
||||
{
|
||||
socket.OnOpen = () => OnConnectionOpen(socket);
|
||||
socket.OnClose = () => OnConnectionClose(socket);
|
||||
socket.OnMessage = message => OnMessageReceived(socket, message);
|
||||
});
|
||||
|
||||
_isStarted = true;
|
||||
Log.Information($"WebSocket服务器已启动: {url}");
|
||||
}
|
||||
}
|
||||
|
||||
// 停止服务器
|
||||
public void Stop()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (!_isStarted) return;
|
||||
AppConfig.Instance.WebSocketPath = "";
|
||||
_server?.Dispose();
|
||||
CloseAllSocketsAsync();
|
||||
_connections.Clear();
|
||||
_isStarted = false;
|
||||
Log.Information("WebSocket服务器已停止");
|
||||
}
|
||||
}
|
||||
public async Task CloseAllSocketsAsync()
|
||||
{
|
||||
foreach (var webSocketConnection in _connections)
|
||||
{
|
||||
webSocketConnection.Value.Close();
|
||||
}
|
||||
}
|
||||
// 连接建立
|
||||
private void OnConnectionOpen(IWebSocketConnection socket)
|
||||
{
|
||||
_connections.TryAdd(socket.ConnectionInfo.Id, socket);
|
||||
Log.Information($"客户端连接: {socket.ConnectionInfo.Id}");
|
||||
|
||||
// 发送欢迎消息
|
||||
_ = SendToClient(socket.ConnectionInfo.Id, new ResponseMessage
|
||||
{
|
||||
Type = "welcome",
|
||||
Success = true,
|
||||
Message = "连接成功",
|
||||
Data = new { ClientId = socket.ConnectionInfo.Id }
|
||||
});
|
||||
}
|
||||
|
||||
// 连接关闭
|
||||
private void OnConnectionClose(IWebSocketConnection socket)
|
||||
{
|
||||
_connections.TryRemove(socket.ConnectionInfo.Id, out _);
|
||||
Log.Information($"客户端断开: {socket.ConnectionInfo.Id}");
|
||||
}
|
||||
|
||||
// 接收消息
|
||||
private async void OnMessageReceived(IWebSocketConnection socket, string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine(DateTime.Now);
|
||||
var commandMessage = JsonSerializer.Deserialize<CommandMessage>(message);
|
||||
if (commandMessage != null && !string.IsNullOrEmpty(commandMessage.Command))
|
||||
{
|
||||
await ProcessCommand(commandMessage, socket);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Information($"消息处理错误: {ex.Message}");
|
||||
await SendErrorResponse(socket, "消息格式错误");
|
||||
}
|
||||
}
|
||||
|
||||
// 处理命令
|
||||
private async Task ProcessCommand(CommandMessage command, IWebSocketConnection socket)
|
||||
{
|
||||
if (_messageHandlers.TryGetValue(command.Command, out var handler))
|
||||
{
|
||||
await handler(command, socket);
|
||||
}
|
||||
else
|
||||
{
|
||||
await SendErrorResponse(socket, $"未知命令: {command.Command}");
|
||||
}
|
||||
}
|
||||
|
||||
// 注册消息处理器
|
||||
public void RegisterHandler(string command, Func<CommandMessage, IWebSocketConnection, Task> handler)
|
||||
{
|
||||
_messageHandlers.AddOrUpdate(command, handler, (key, oldValue) => handler);
|
||||
}
|
||||
|
||||
// 注册默认处理器
|
||||
private void RegisterDefaultHandlers()
|
||||
{
|
||||
// 获取网络状态
|
||||
RegisterHandler("getNetworkStatus", async (cmd, socket) =>
|
||||
{
|
||||
// cmd.Command;
|
||||
// 这里应该调用你的网络状态获取逻辑
|
||||
var networkStatus = GetNetworkStatus();
|
||||
await SendToClient(socket.ConnectionInfo.Id, new ResponseMessage
|
||||
{
|
||||
Type = "networkStatus",
|
||||
Success = true,
|
||||
Data = networkStatus
|
||||
});
|
||||
});
|
||||
|
||||
// 获取防火墙规则
|
||||
RegisterHandler("getFirewallRules", async (cmd, socket) =>
|
||||
{
|
||||
var firewallRules = GetFirewallRules();
|
||||
await SendToClient(socket.ConnectionInfo.Id, new ResponseMessage
|
||||
{
|
||||
Type = "firewallRules",
|
||||
Success = true,
|
||||
Data = firewallRules
|
||||
});
|
||||
});
|
||||
|
||||
// 添加防火墙规则
|
||||
RegisterHandler("addFirewallRule", async (cmd, socket) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var ruleData = JsonSerializer.Deserialize<FirewallRuleMessage>(cmd.Data.ToString());
|
||||
var success = AddFirewallRule(ruleData);
|
||||
|
||||
await SendToClient(socket.ConnectionInfo.Id, new ResponseMessage
|
||||
{
|
||||
Type = "addFirewallRuleResponse",
|
||||
Success = success,
|
||||
Message = success ? "规则添加成功" : "规则添加失败"
|
||||
});
|
||||
|
||||
// 广播更新给所有客户端
|
||||
if (success)
|
||||
{
|
||||
await BroadcastFirewallUpdate();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await SendErrorResponse(socket, $"添加规则失败: {ex.Message}");
|
||||
}
|
||||
});
|
||||
|
||||
// 心跳检测
|
||||
RegisterHandler("ping", async (cmd, socket) =>
|
||||
{
|
||||
await SendToClient(socket.ConnectionInfo.Id, new ResponseMessage
|
||||
{
|
||||
Type = "pong",
|
||||
Success = true,
|
||||
Message = "服务器正常"
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 发送消息给指定客户端
|
||||
public async Task<bool> SendToClient(Guid clientId, ResponseMessage message)
|
||||
{
|
||||
if (_connections.TryGetValue(clientId, out var socket))
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = JsonSerializer.Serialize(message);
|
||||
await socket.Send(json);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Information($"发送消息失败: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 广播消息给所有客户端
|
||||
public async Task BroadcastMessage(ResponseMessage message)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(message);
|
||||
var tasks = new List<Task>();
|
||||
|
||||
foreach (var connection in _connections.Values)
|
||||
{
|
||||
tasks.Add(connection.Send(json));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Information($"广播消息失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// 发送错误响应
|
||||
private async Task SendErrorResponse(IWebSocketConnection socket, string errorMessage)
|
||||
{
|
||||
await SendToClient(socket.ConnectionInfo.Id, new ResponseMessage
|
||||
{
|
||||
Type = "error",
|
||||
Success = false,
|
||||
Message = errorMessage
|
||||
});
|
||||
}
|
||||
|
||||
// 获取连接数量
|
||||
public int GetConnectionCount() => _connections.Count;
|
||||
|
||||
// 获取所有连接ID
|
||||
public Guid[] GetConnectionIds() => _connections.Keys.ToArray();
|
||||
|
||||
#region 业务逻辑示例方法(需要根据实际需求实现)
|
||||
|
||||
private object GetNetworkStatus()
|
||||
{
|
||||
// 实现网络状态获取逻辑
|
||||
return new[]
|
||||
{
|
||||
new NetworkStatusMessage
|
||||
{
|
||||
Type = "networkStatus",
|
||||
InterfaceName = "以太网",
|
||||
Status = "已连接",
|
||||
BytesSent = 1024000,
|
||||
BytesReceived = 2048000,
|
||||
IpAddress = "192.168.1.100"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private object GetFirewallRules()
|
||||
{
|
||||
// 实现防火墙规则获取逻辑
|
||||
return new[]
|
||||
{
|
||||
new FirewallRuleMessage
|
||||
{
|
||||
Type = "firewallRule",
|
||||
RuleName = "HTTP流量",
|
||||
Action = "允许",
|
||||
Protocol = "TCP",
|
||||
Port = "80",
|
||||
Enabled = true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private bool AddFirewallRule(FirewallRuleMessage rule)
|
||||
{
|
||||
// 实现添加防火墙规则逻辑
|
||||
Log.Information($"添加防火墙规则: {rule.RuleName}");
|
||||
return true; // 示例返回
|
||||
}
|
||||
|
||||
private async Task BroadcastFirewallUpdate()
|
||||
{
|
||||
var rules = GetFirewallRules();
|
||||
await BroadcastMessage(new ResponseMessage
|
||||
{
|
||||
Type = "firewallRulesUpdate",
|
||||
Success = true,
|
||||
Data = rules
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
|
||||
<PackageReference Include="NUnit" Version="3.14.0"/>
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.9.0"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="NUnit.Framework"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,222 @@
|
||||
using Common.Logger;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Common.Test.Logger;
|
||||
|
||||
public class LogTest
|
||||
{
|
||||
[Test]
|
||||
public void Test1()
|
||||
{
|
||||
TaskTest();
|
||||
Assert.Pass();
|
||||
}
|
||||
|
||||
async void TaskTest()
|
||||
{
|
||||
// ===== 使用方式1: 使用默认配置 =====
|
||||
using (var logger = new UniversalLogger())
|
||||
{
|
||||
logger.LogInformation("应用程序启动");
|
||||
logger.LogWarning("这是一个警告消息");
|
||||
|
||||
try
|
||||
{
|
||||
throw new Exception("测试异常");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "捕获到异常: {Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 使用方式2: 自定义配置 =====
|
||||
var config = new LoggerConfig
|
||||
{
|
||||
EnableConsole = true,
|
||||
EnableFile = true,
|
||||
LogPath = "MyAppLogs",
|
||||
MaxFileSizeMB = 2,
|
||||
RetentionDays = 15,
|
||||
CompressDaysThreshold = 2,
|
||||
MinimumLevel = LogEventLevel.Debug,
|
||||
UseJsonFormat = false
|
||||
};
|
||||
|
||||
using (var logger = new UniversalLogger(config))
|
||||
{
|
||||
logger.LogDebug("调试信息");
|
||||
logger.LogInformation("信息消息");
|
||||
logger.LogWarning("警告消息");
|
||||
logger.LogError("错误消息");
|
||||
|
||||
// 带上下文信息的日志
|
||||
var context = new Dictionary<string, object>
|
||||
{
|
||||
["UserId"] = 12345,
|
||||
["Action"] = "Login",
|
||||
["IP"] = "192.168.1.100"
|
||||
};
|
||||
logger.LogWithContext(LogEventLevel.Information, "用户登录", context);
|
||||
}
|
||||
|
||||
// ===== 使用方式3: 从配置文件读取 =====
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddJsonFile("appsettings.json", optional: false)
|
||||
.Build();
|
||||
|
||||
using (var logger = UniversalLogger.FromConfiguration(configuration, "Logging"))
|
||||
{
|
||||
logger.LogInformation("从配置文件创建的日志记录器");
|
||||
}
|
||||
|
||||
// ===== 使用方式4: 静态日志记录器 =====
|
||||
// 初始化静态日志记录器
|
||||
Log.Initialize(config);
|
||||
|
||||
Log.Information("使用静态日志记录器");
|
||||
Log.Warning("警告消息");
|
||||
Log.Error("错误消息");
|
||||
|
||||
try
|
||||
{
|
||||
throw new InvalidOperationException("测试异常");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "静态日志记录器捕获异常");
|
||||
}
|
||||
|
||||
// 手动执行维护任务
|
||||
await Log.RunMaintenanceAsync();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 扩展示例: 自定义日志级别和过滤器 =====
|
||||
public static class LoggerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 记录性能日志
|
||||
/// </summary>
|
||||
public static void LogPerformance(this UniversalLogger logger, string operation, TimeSpan duration,
|
||||
Dictionary<string, object> additionalData = null)
|
||||
{
|
||||
var context = new Dictionary<string, object>
|
||||
{
|
||||
["Operation"] = operation,
|
||||
["Duration"] = duration.TotalMilliseconds,
|
||||
["Category"] = "Performance"
|
||||
};
|
||||
|
||||
if (additionalData != null)
|
||||
{
|
||||
foreach (var kvp in additionalData)
|
||||
{
|
||||
context[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogWithContext(LogEventLevel.Information, "性能监控: {Operation} 耗时 {Duration}ms", context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录业务日志
|
||||
/// </summary>
|
||||
public static void LogBusiness(this UniversalLogger logger, string action, string entityType, object entityId,
|
||||
Dictionary<string, object> details = null)
|
||||
{
|
||||
var context = new Dictionary<string, object>
|
||||
{
|
||||
["Action"] = action,
|
||||
["EntityType"] = entityType,
|
||||
["EntityId"] = entityId,
|
||||
["Category"] = "Business"
|
||||
};
|
||||
|
||||
if (details != null)
|
||||
{
|
||||
foreach (var kvp in details)
|
||||
{
|
||||
context[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogWithContext(LogEventLevel.Information, "业务操作: {Action} {EntityType} {EntityId}", context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录安全日志
|
||||
/// </summary>
|
||||
public static void LogSecurity(this UniversalLogger logger, string eventType, string userId, string ipAddress,
|
||||
bool success, string details = null)
|
||||
{
|
||||
var context = new Dictionary<string, object>
|
||||
{
|
||||
["EventType"] = eventType,
|
||||
["UserId"] = userId,
|
||||
["IpAddress"] = ipAddress,
|
||||
["Success"] = success,
|
||||
["Category"] = "Security"
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(details))
|
||||
{
|
||||
context["Details"] = details;
|
||||
}
|
||||
|
||||
var level = success ? LogEventLevel.Information : LogEventLevel.Warning;
|
||||
logger.LogWithContext(level, "安全事件: {EventType} 用户 {UserId} 来自 {IpAddress} {Result}",
|
||||
context,
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 使用扩展方法的示例 =====
|
||||
public class ExtensionUsageExample
|
||||
{
|
||||
private readonly UniversalLogger _logger;
|
||||
|
||||
public ExtensionUsageExample()
|
||||
{
|
||||
_logger = new UniversalLogger();
|
||||
}
|
||||
|
||||
public async Task ProcessUserOrder(int userId, int orderId)
|
||||
{
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
try
|
||||
{
|
||||
// 记录业务日志
|
||||
_logger.LogBusiness("ProcessOrder", "Order", orderId, new Dictionary<string, object>
|
||||
{
|
||||
["UserId"] = userId,
|
||||
["Timestamp"] = DateTime.UtcNow
|
||||
});
|
||||
|
||||
// 模拟业务处理
|
||||
await Task.Delay(1000);
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
// 记录性能日志
|
||||
_logger.LogPerformance("ProcessUserOrder", stopwatch.Elapsed, new Dictionary<string, object>
|
||||
{
|
||||
["UserId"] = userId,
|
||||
["OrderId"] = orderId
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "处理订单时发生错误: OrderId={OrderId}, UserId={UserId}", orderId, userId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleUserLogin(string userId, string ipAddress, bool success)
|
||||
{
|
||||
_logger.LogSecurity("Login", userId, ipAddress, success,
|
||||
success ? "登录成功" : "登录失败");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Common.Test;
|
||||
|
||||
public class Tests
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test1()
|
||||
{
|
||||
Assert.Pass();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Common.Wpf;
|
||||
|
||||
public class Class1
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Common.Wpf.ExceptionHandler;
|
||||
|
||||
public class Class1
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Common.Configuration;
|
||||
|
||||
namespace Common;
|
||||
|
||||
public sealed class AppConfig
|
||||
{
|
||||
// 使用 Lazy<T> 实现线程安全的懒加载单例
|
||||
private static readonly Lazy<AppConfig> _instance = new Lazy<AppConfig>(() => new AppConfig());
|
||||
|
||||
// 提供全局访问点
|
||||
public static AppConfig Instance => _instance.Value;
|
||||
|
||||
// 私有构造函数,防止外部实例化
|
||||
private AppConfig()
|
||||
{
|
||||
var model = ConfigReader.LoadConfig();
|
||||
ConfigModel = model;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全局配置
|
||||
/// </summary>
|
||||
public AppConfigModel ConfigModel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// WebSocket 端口
|
||||
/// </summary>
|
||||
public int WebSocketPort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// WebSocket 连接地址
|
||||
/// </summary>
|
||||
public string? WebSocketPath { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0-preview.4.25258.110" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0-preview.4.25258.110" />
|
||||
<PackageReference Include="Serilog" Version="4.3.1-dev-02373" />
|
||||
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" />
|
||||
<PackageReference Include="Serilog.Enrichers.Process" Version="3.0.0" />
|
||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.1-dev-02317" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.1-dev-00953" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,34 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Common.Configuration;
|
||||
|
||||
public static class ConfigLoader
|
||||
{
|
||||
private static readonly object _lock = new();
|
||||
private static AppConfig _config;
|
||||
|
||||
public static AppConfig Config
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_config == null)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_config == null)
|
||||
{
|
||||
var builder = new ConfigurationBuilder()
|
||||
.SetBasePath(AppContext.BaseDirectory)
|
||||
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
|
||||
|
||||
IConfiguration configuration = builder.Build();
|
||||
|
||||
// _config = new AppConfig();
|
||||
configuration.GetSection("AppSettings").Bind(_config);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _config;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Common.Logger;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Common.Configuration;
|
||||
|
||||
public static class ConfigReader
|
||||
{
|
||||
public static AppConfigModel LoadConfig()
|
||||
{
|
||||
var env = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production";
|
||||
var builder = new ConfigurationBuilder()
|
||||
.SetBasePath(AppContext.BaseDirectory)
|
||||
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
||||
.AddJsonFile($"appsettings.{env}.json", optional: true, reloadOnChange: true);
|
||||
|
||||
IConfiguration config = builder.Build();
|
||||
|
||||
var model = new AppConfigModel();
|
||||
config.GetSection("AppSettings").Bind(model);
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
||||
// 可单独定义模型类用于绑定
|
||||
public class AppConfigModel
|
||||
{
|
||||
public string AppName { get; set; }
|
||||
public string Version { get; set; }
|
||||
public int MaxConnection { get; set; }
|
||||
public LoggerConfig Logging { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Common.ExpandException;
|
||||
|
||||
public class ExceptionEnum
|
||||
{
|
||||
// region 系统常见错误本地化
|
||||
|
||||
/// <summary>
|
||||
/// 进程信息获取失败
|
||||
/// </summary>
|
||||
public static readonly ExceptionExpand ProcessGetException = new(100001L, "进程消息获取失败,指定线程可能不存在或已关闭!", "以管理员权限重试!");
|
||||
|
||||
|
||||
// endregion
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
namespace Common.ExpandException;
|
||||
|
||||
public class ExceptionExpand : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认异常编码
|
||||
/// </summary>
|
||||
private const long DefaultCode = -1;
|
||||
|
||||
public string ErrorMessage { get; private set; } = "";
|
||||
public long ErrorCode { get; private set; } = DefaultCode;
|
||||
public string Suggestion { get; private set; } = "";
|
||||
|
||||
private ExceptionExpand()
|
||||
{
|
||||
}
|
||||
|
||||
public ExceptionExpand(long errorCode, string massage, string solution) : base(
|
||||
massage)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
Suggestion = solution;
|
||||
}
|
||||
|
||||
public ExceptionExpand(long errorCode, string massage, string solution, Exception? innerException) : base(
|
||||
massage, innerException)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
Suggestion = solution;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Error Code: {ErrorCode}\nMessage: {Message}\nSuggestion: {Suggestion}\n{base.ToString()}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using Serilog;
|
||||
|
||||
namespace Common.FunExpand;
|
||||
|
||||
public static class StringExpand
|
||||
{
|
||||
/// <summary>
|
||||
/// 检测是否是 url
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsValidUrl(this string str)
|
||||
{
|
||||
return Uri.TryCreate(str, UriKind.Absolute, out _);
|
||||
}
|
||||
|
||||
public static int ToInt(this string? str, int defaultValue = default)
|
||||
{
|
||||
return TryToInt(str) ?? defaultValue;
|
||||
}
|
||||
|
||||
public static int? TryToInt(this string? str)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(str))
|
||||
{
|
||||
Log.Error($"数据为空, 返回默认值 {null} .");
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return int.Parse(str);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"数据 {str} 转换失败, 返回默认值 {null} .");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static double ToDouble(this string? str, double defaultValue = default)
|
||||
{
|
||||
return TryToDouble(str) ?? defaultValue;
|
||||
}
|
||||
|
||||
public static double? TryToDouble(this string? str)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(str))
|
||||
{
|
||||
Log.Error($"数据为空, 返回默认值 {null} .");
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return double.Parse(str);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"数据 {str} 转换失败, 返回默认值 {null} .");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Common.Logger;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 日志配置类
|
||||
/// </summary>
|
||||
public class LoggerConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否启用控制台输出
|
||||
/// </summary>
|
||||
public bool EnableConsole { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用文件输出
|
||||
/// </summary>
|
||||
public bool EnableFile { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 日志文件路径
|
||||
/// </summary>
|
||||
public string LogPath { get; set; } = "logs";
|
||||
|
||||
/// <summary>
|
||||
/// 单个日志文件最大大小(MB)
|
||||
/// </summary>
|
||||
public int MaxFileSizeMB { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 日志保留天数
|
||||
/// </summary>
|
||||
public int RetentionDays { get; set; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// 压缩天数阈值(几天前的日志开始压缩)
|
||||
/// </summary>
|
||||
public int CompressDaysThreshold { get; set; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// 最小日志级别
|
||||
/// </summary>
|
||||
public LogEventLevel MinimumLevel { get; set; } = LogEventLevel.Information;
|
||||
|
||||
/// <summary>
|
||||
/// 日志格式模板
|
||||
/// </summary>
|
||||
public string OutputTemplate { get; set; } = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}";
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用JSON格式
|
||||
/// </summary>
|
||||
public bool UseJsonFormat { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 压缩检查间隔(小时)
|
||||
/// </summary>
|
||||
public int CompressCheckIntervalHours { get; set; } = 24;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
# UniversalLogger - 通用日志记录库
|
||||
|
||||
一个基于Serilog构建的功能完整的C#日志记录库,支持多种输出方式、自动维护、配置灵活等特性。
|
||||
|
||||
## 主要功能
|
||||
|
||||
✅ **多种输出方式**: 支持控制台、文件输出,可单独或同时使用
|
||||
✅ **按日期分割**: 自动按日期创建日志文件
|
||||
✅ **文件大小控制**: 超过指定大小自动创建新文件
|
||||
✅ **自动压缩**: 指定天数后自动压缩旧日志文件
|
||||
✅ **自动清理**: 超过保留期限自动删除旧日志
|
||||
✅ **配置灵活**: 支持代码配置、JSON文件配置
|
||||
✅ **功能完整**: 支持各级别日志、异常堆栈、上下文信息
|
||||
✅ **性能优化**: 基于Serilog高性能日志框架
|
||||
✅ **易于使用**: 提供静态类和实例两种使用方式
|
||||
|
||||
## 安装依赖
|
||||
|
||||
在你的项目中添加以下NuGet包:
|
||||
|
||||
```xml
|
||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="Serilog.Formatting.Json" Version="1.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 使用默认配置
|
||||
|
||||
```csharp
|
||||
using UniversalLogger;
|
||||
|
||||
// 使用默认配置创建日志记录器
|
||||
using var logger = new UniversalLogger(new LoggerConfig());
|
||||
|
||||
logger.LogInformation("Hello, World!");
|
||||
logger.LogWarning("这是一个警告");
|
||||
logger.LogError("这是一个错误");
|
||||
```
|
||||
|
||||
### 2. 使用静态日志记录器
|
||||
|
||||
```csharp
|
||||
// 初始化静态日志记录器
|
||||
Log.Initialize();
|
||||
|
||||
// 直接使用
|
||||
Log.Information("应用程序启动");
|
||||
Log.Warning("警告信息");
|
||||
Log.Error(exception, "发生错误: {Message}", exception.Message);
|
||||
```
|
||||
|
||||
### 3. 从配置文件加载
|
||||
|
||||
创建 `appsettings.json` 文件:
|
||||
|
||||
```json
|
||||
{
|
||||
"Logging": {
|
||||
"EnableConsole": true,
|
||||
"EnableFile": true,
|
||||
"LogPath": "logs",
|
||||
"MaxFileSizeMB": 1,
|
||||
"RetentionDays": 30,
|
||||
"CompressDaysThreshold": 3,
|
||||
"MinimumLevel": "Information",
|
||||
"OutputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}",
|
||||
"UseJsonFormat": false,
|
||||
"CompressCheckIntervalHours": 24
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
使用配置文件:
|
||||
|
||||
```csharp
|
||||
// 从配置文件初始化
|
||||
Log.InitializeFromJsonFile("appsettings.json");
|
||||
|
||||
Log.Information("使用配置文件的日志记录器");
|
||||
```
|
||||
|
||||
## 配置选项
|
||||
|
||||
| 属性 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `EnableConsole` | bool | true | 是否启用控制台输出 |
|
||||
| `EnableFile` | bool | true | 是否启用文件输出 |
|
||||
| `LogPath` | string | "logs" | 日志文件存储路径 |
|
||||
| `MaxFileSizeMB` | int | 1 | 单个日志文件最大大小(MB) |
|
||||
| `RetentionDays` | int | 30 | 日志保留天
|
||||
@@ -0,0 +1,463 @@
|
||||
using System.IO.Compression;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Serilog.Formatting.Json;
|
||||
|
||||
namespace Common.Logger;
|
||||
|
||||
/// <summary>
|
||||
/// 通用日志记录器
|
||||
/// </summary>
|
||||
public class UniversalLogger : IDisposable
|
||||
{
|
||||
private readonly Serilog.Core.Logger _logger;
|
||||
private readonly LoggerConfig _config;
|
||||
private readonly System.Timers.Timer _maintenanceTimer;
|
||||
private bool _disposed = false;
|
||||
|
||||
public UniversalLogger(LoggerConfig config = null)
|
||||
{
|
||||
_config = config ?? new LoggerConfig();
|
||||
_logger = CreateLogger();
|
||||
|
||||
// 启动维护定时器
|
||||
_maintenanceTimer =
|
||||
new System.Timers.Timer(TimeSpan.FromHours(_config.CompressCheckIntervalHours).TotalMilliseconds);
|
||||
_maintenanceTimer.Elapsed += async (s, e) => await PerformMaintenanceAsync();
|
||||
_maintenanceTimer.Start();
|
||||
|
||||
// 启动时执行一次维护
|
||||
Task.Run(async () => await PerformMaintenanceAsync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从配置文件创建日志记录器
|
||||
/// </summary>
|
||||
public static UniversalLogger FromConfiguration(IConfiguration configuration, string sectionName = "Logging")
|
||||
{
|
||||
var config = new LoggerConfig();
|
||||
configuration.GetSection(sectionName).Bind(config);
|
||||
return new UniversalLogger(config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从JSON配置文件创建日志记录器
|
||||
/// </summary>
|
||||
public static UniversalLogger FromJsonFile(string configPath)
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddJsonFile(configPath, optional: false)
|
||||
.Build();
|
||||
return FromConfiguration(configuration);
|
||||
}
|
||||
|
||||
private Serilog.Core.Logger CreateLogger()
|
||||
{
|
||||
var loggerConfig = new LoggerConfiguration()
|
||||
.MinimumLevel.Is(_config.MinimumLevel)
|
||||
.Enrich.WithThreadId()
|
||||
.Enrich.WithProcessId()
|
||||
// .Enrich.WithMachineName() // 推荐添加机器名 enrich 扩展
|
||||
;
|
||||
|
||||
// 配置控制台输出
|
||||
if (_config.EnableConsole)
|
||||
{
|
||||
loggerConfig = loggerConfig.WriteTo.Console(
|
||||
outputTemplate: _config.OutputTemplate,
|
||||
restrictedToMinimumLevel: _config.MinimumLevel);
|
||||
}
|
||||
|
||||
// 配置文件输出
|
||||
if (_config.EnableFile)
|
||||
{
|
||||
Directory.CreateDirectory(_config.LogPath);
|
||||
|
||||
string logFileName = Path.Combine(_config.LogPath, "log-.log");
|
||||
|
||||
if (_config.UseJsonFormat)
|
||||
{
|
||||
loggerConfig = loggerConfig.WriteTo.File(
|
||||
formatter: new JsonFormatter(),
|
||||
path: logFileName,
|
||||
rollingInterval: RollingInterval.Day, // 按天分类
|
||||
rollOnFileSizeLimit: true, // 当日志文件大小超过限制时滚动
|
||||
fileSizeLimitBytes: _config.MaxFileSizeMB * 1024 * 1024, // 日志文件大小限制
|
||||
retainedFileCountLimit: null, // 保留最近7天的日志文件
|
||||
restrictedToMinimumLevel: _config.MinimumLevel);
|
||||
}
|
||||
else
|
||||
{
|
||||
loggerConfig = loggerConfig.WriteTo.File(
|
||||
path: logFileName,
|
||||
outputTemplate: _config.OutputTemplate,
|
||||
rollingInterval: RollingInterval.Day,
|
||||
rollOnFileSizeLimit: true,
|
||||
fileSizeLimitBytes: _config.MaxFileSizeMB * 1024 * 1024,
|
||||
retainedFileCountLimit: null,
|
||||
restrictedToMinimumLevel: _config.MinimumLevel,
|
||||
shared: true // 允许多个应用程序实例共享同一日志文件
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return loggerConfig.CreateLogger();
|
||||
}
|
||||
|
||||
#region 日志记录方法
|
||||
|
||||
public void LogVerbose(string message, params object[] args)
|
||||
{
|
||||
_logger.Verbose(message, args);
|
||||
}
|
||||
|
||||
public void LogDebug(string message, params object[] args)
|
||||
{
|
||||
_logger.Debug(message, args);
|
||||
}
|
||||
|
||||
public void LogInformation(string message, params object[] args)
|
||||
{
|
||||
_logger.Information(message, args);
|
||||
}
|
||||
|
||||
public void LogWarning(string message, params object[] args)
|
||||
{
|
||||
_logger.Warning(message, args);
|
||||
}
|
||||
|
||||
public void LogError(string message, params object[] args)
|
||||
{
|
||||
_logger.Error(message, args);
|
||||
}
|
||||
|
||||
public void LogError(Exception? exception, string message, params object[] args)
|
||||
{
|
||||
_logger.Error(exception, message, args);
|
||||
}
|
||||
|
||||
public void LogFatal(string message, params object[] args)
|
||||
{
|
||||
_logger.Fatal(message, args);
|
||||
}
|
||||
|
||||
public void LogFatal(Exception? exception, string message, params object[] args)
|
||||
{
|
||||
_logger.Fatal(exception, message, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取上下文
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public ILogger GetLogger<T>() => _logger.ForContext<T>();
|
||||
|
||||
public ILogger ForContext(string propertyName, object? value)
|
||||
=> _logger.ForContext(propertyName, value);
|
||||
|
||||
/// <summary>
|
||||
/// 记录带上下文信息的日志
|
||||
/// </summary>
|
||||
public void LogWithContext(LogEventLevel level, string message, Dictionary<string, object> context,
|
||||
Exception? exception = null)
|
||||
{
|
||||
var logEvent = new LogEventLevel[] { level };
|
||||
ILogger logger = _logger;
|
||||
|
||||
foreach (var kvp in context ?? new Dictionary<string, object>())
|
||||
{
|
||||
var i = logger.ForContext(kvp.Key, kvp.Value);
|
||||
logger = logger.ForContext(kvp.Key, kvp.Value);
|
||||
}
|
||||
|
||||
if (exception != null)
|
||||
{
|
||||
logger.Write(level, exception, message);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Write(level, message);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 维护功能
|
||||
|
||||
/// <summary>
|
||||
/// 执行日志维护任务
|
||||
/// </summary>
|
||||
private async Task PerformMaintenanceAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_config.EnableFile)
|
||||
{
|
||||
await CompressOldLogsAsync();
|
||||
await DeleteOldLogsAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception? ex)
|
||||
{
|
||||
_logger.Error(ex, "执行日志维护任务时发生错误");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 压缩旧日志文件
|
||||
/// </summary>
|
||||
private async Task CompressOldLogsAsync()
|
||||
{
|
||||
var logDirectory = new DirectoryInfo(_config.LogPath);
|
||||
if (!logDirectory.Exists) return;
|
||||
|
||||
var cutoffDate = DateTime.Now.Date.AddDays(-_config.CompressDaysThreshold);
|
||||
var logFiles = logDirectory.GetFiles("log*.log")
|
||||
.Where(f => f.CreationTime.Date < cutoffDate && !f.Name.EndsWith(".gz"))
|
||||
.ToList();
|
||||
|
||||
foreach (var file in logFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
var compressedFileName = file.FullName + ".gz";
|
||||
if (File.Exists(compressedFileName)) continue;
|
||||
|
||||
await CompressFileAsync(file.FullName, compressedFileName);
|
||||
File.Delete(file.FullName);
|
||||
|
||||
_logger.Information("已压缩日志文件: {FileName}", file.Name);
|
||||
}
|
||||
catch (Exception? ex)
|
||||
{
|
||||
_logger.Error(ex, "压缩日志文件失败: {FileName}", file.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除过期的日志文件
|
||||
/// </summary>
|
||||
private async Task DeleteOldLogsAsync()
|
||||
{
|
||||
var logDirectory = new DirectoryInfo(_config.LogPath);
|
||||
if (!logDirectory.Exists) return;
|
||||
|
||||
var cutoffDate = DateTime.Now.Date.AddDays(-_config.RetentionDays);
|
||||
var oldFiles = logDirectory.GetFiles("log*.*")
|
||||
.Where(f => f.CreationTime.Date < cutoffDate)
|
||||
.ToList();
|
||||
|
||||
foreach (var file in oldFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(file.FullName);
|
||||
_logger.Information("已删除过期日志文件: {FileName}", file.Name);
|
||||
}
|
||||
catch (Exception? ex)
|
||||
{
|
||||
_logger.Error(ex, "删除过期日志文件失败: {FileName}", file.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 压缩文件
|
||||
/// </summary>
|
||||
private async Task CompressFileAsync(string sourceFile, string compressedFile)
|
||||
{
|
||||
using (var originalFileStream = File.OpenRead(sourceFile))
|
||||
using (var compressedFileStream = File.Create(compressedFile))
|
||||
using (var compressionStream = new GZipStream(compressedFileStream, CompressionMode.Compress))
|
||||
{
|
||||
await originalFileStream.CopyToAsync(compressionStream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手动执行维护任务
|
||||
/// </summary>
|
||||
public async Task RunMaintenanceAsync()
|
||||
{
|
||||
await PerformMaintenanceAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_maintenanceTimer?.Stop();
|
||||
_maintenanceTimer?.Dispose();
|
||||
_logger?.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~UniversalLogger()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 静态日志记录器(单例模式)
|
||||
/// </summary>
|
||||
public static class Log
|
||||
{
|
||||
private static UniversalLogger _instance;
|
||||
private static readonly object _lock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化日志记录器
|
||||
/// </summary>
|
||||
public static void Initialize(LoggerConfig config = null)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_instance?.Dispose();
|
||||
_instance = new UniversalLogger(config);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从配置文件初始化
|
||||
/// </summary>
|
||||
public static void InitializeFromConfig(IConfiguration configuration, string sectionName = "Logging")
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_instance?.Dispose();
|
||||
_instance = UniversalLogger.FromConfiguration(configuration, sectionName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从JSON文件初始化
|
||||
/// </summary>
|
||||
public static void InitializeFromJsonFile(string configPath)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_instance?.Dispose();
|
||||
_instance = UniversalLogger.FromJsonFile(configPath);
|
||||
}
|
||||
}
|
||||
|
||||
private static UniversalLogger Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new UniversalLogger();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Verbose(string message, params object[] args) => Instance.LogVerbose(message, args);
|
||||
|
||||
public static void Verbose4Ctx(string message, [CallerMemberName] string callerMemberName = "",
|
||||
[CallerFilePath] string filePath = "", [CallerLineNumber] int callerLineNumber = 0)
|
||||
{
|
||||
var str = GetLog(callerMemberName, Path.GetFileName(filePath), callerLineNumber, message);
|
||||
Verbose(str);
|
||||
}
|
||||
|
||||
public static void Debug(string message, params object[] args) => Instance.LogDebug(message, args);
|
||||
|
||||
public static void Debug4Ctx(string message, [CallerMemberName] string callerMemberName = "",
|
||||
[CallerFilePath] string filePath = "", [CallerLineNumber] int callerLineNumber = 0)
|
||||
{
|
||||
var str = GetLog(callerMemberName, Path.GetFileName(filePath), callerLineNumber, message);
|
||||
Debug(str);
|
||||
}
|
||||
|
||||
public static void Information(string message, params object[] args) => Instance.LogInformation(message, args);
|
||||
|
||||
public static void Information4Ctx(string message, [CallerMemberName] string callerMemberName = "",
|
||||
[CallerFilePath] string filePath = "", [CallerLineNumber] int callerLineNumber = 0)
|
||||
{
|
||||
var str = GetLog(callerMemberName, Path.GetFileName(filePath), callerLineNumber, message);
|
||||
Information(str);
|
||||
}
|
||||
|
||||
public static void Warning(string message, params object[] args) => Instance.LogWarning(message, args);
|
||||
|
||||
public static void Warning4Ctx(string message, [CallerMemberName] string callerMemberName = "",
|
||||
[CallerFilePath] string filePath = "", [CallerLineNumber] int callerLineNumber = 0)
|
||||
{
|
||||
var str = GetLog(callerMemberName, Path.GetFileName(filePath), callerLineNumber, message);
|
||||
Warning(str);
|
||||
}
|
||||
|
||||
public static void Error(string message, params object[] args) => Instance.LogError(message, args);
|
||||
|
||||
public static void Error4Ctx(string message, [CallerMemberName] string callerMemberName = "",
|
||||
[CallerFilePath] string filePath = "", [CallerLineNumber] int callerLineNumber = 0)
|
||||
{
|
||||
var str = GetLog(callerMemberName, Path.GetFileName(filePath), callerLineNumber, message);
|
||||
Error(str);
|
||||
}
|
||||
|
||||
|
||||
public static void Error(Exception? exception, string message, [CallerMemberName] string callerMemberName = "",
|
||||
[CallerFilePath] string filePath = "", [CallerLineNumber] int callerLineNumber = 0)
|
||||
{
|
||||
var str = GetLog(callerMemberName, Path.GetFileName(filePath), callerLineNumber, message);
|
||||
Instance.LogError(exception, str);
|
||||
}
|
||||
|
||||
public static void Fatal(string message, params object[] args) => Instance.LogFatal(message, args);
|
||||
|
||||
public static void Fatal4Ctx(string message, [CallerMemberName] string callerMemberName = "",
|
||||
[CallerFilePath] string filePath = "", [CallerLineNumber] int callerLineNumber = 0)
|
||||
{
|
||||
var str = GetLog(callerMemberName, Path.GetFileName(filePath), callerLineNumber, message);
|
||||
Fatal(str);
|
||||
}
|
||||
|
||||
public static void Fatal(Exception? exception, string message, [CallerMemberName] string callerMemberName = "",
|
||||
[CallerFilePath] string filePath = "", [CallerLineNumber] int callerLineNumber = 0)
|
||||
{
|
||||
var str = GetLog(callerMemberName, Path.GetFileName(filePath), callerLineNumber, message);
|
||||
Instance.LogFatal(exception, str);
|
||||
}
|
||||
|
||||
private static string GetLog(string callerMemberName, string getFileName, int callerLineNumber, string message)
|
||||
{
|
||||
return $"[{callerMemberName}] - {getFileName}:{callerLineNumber} :{message}";
|
||||
}
|
||||
|
||||
public static void WithContext(LogEventLevel level, string message, Dictionary<string, object> context,
|
||||
Exception? exception = null)
|
||||
=> Instance.LogWithContext(level, message, context, exception);
|
||||
|
||||
public static async Task RunMaintenanceAsync() => await Instance.RunMaintenanceAsync();
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Common.Monitors;
|
||||
|
||||
public class ApplicationHealthMonitor
|
||||
{
|
||||
private readonly Timer _healthCheckTimer;
|
||||
|
||||
public ApplicationHealthMonitor()
|
||||
{
|
||||
_healthCheckTimer = new Timer(CheckApplicationHealth, null,
|
||||
TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(5));
|
||||
}
|
||||
|
||||
private void CheckApplicationHealth(object state)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查内存使用
|
||||
var memoryUsage = GC.GetTotalMemory(false);
|
||||
if (memoryUsage > 500 * 1024 * 1024) // 500MB
|
||||
{
|
||||
// Logger.Warn($"内存使用过高: {memoryUsage / 1024 / 1024}MB");
|
||||
GC.Collect(); // 强制垃圾回收
|
||||
}
|
||||
|
||||
// 检查线程数量
|
||||
var threadCount = Process.GetCurrentProcess().Threads.Count;
|
||||
if (threadCount > 100)
|
||||
{
|
||||
// Logger.Warn($"线程数量过多: {threadCount}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// LogException(ex, "健康检查异常");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
| 类别 | 示例 |
|
||||
| -------- | --------------------------------------------- |
|
||||
| ✅ 配置封装 | `AppConfig`, `ConfigReader` |
|
||||
| ✅ 通用工具类 | 字符串处理、加密、哈希、路径处理 |
|
||||
| ✅ 基础模型类 | `Result<T>`, `PagedList<T>`, `ErrorInfo` |
|
||||
| ✅ 通用扩展方法 | `string.ToSafeInt()`, `DateTime.ToUnixTime()` |
|
||||
| ✅ 接口定义 | 如 `INetworkMonitor`, `ILogger`(便于依赖注入) |
|
||||
| ✅ 网络协议模型 | 公共 API 的数据结构定义(DTO/VO) |
|
||||
| ✅ 常量/枚举 | 错误码、状态码、常量定义 |
|
||||
@@ -0,0 +1,487 @@
|
||||
// namespace Common.TaskQueue;
|
||||
//
|
||||
// using System;
|
||||
// using System.Collections.Concurrent;
|
||||
// using System.Collections.Generic;
|
||||
// using System.Linq;
|
||||
// using System.Threading;
|
||||
// using System.Threading.Channels;
|
||||
// using System.Threading.Tasks;
|
||||
//
|
||||
// // 1. 任务接口定义
|
||||
// public interface ITaskItem
|
||||
// {
|
||||
// string TaskId { get; }
|
||||
// TaskPriority Priority { get; }
|
||||
// DateTime CreatedAt { get; }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// 异步执行方法
|
||||
// /// </summary>
|
||||
// /// <param name="cancellationToken"></param>
|
||||
// /// <returns></returns>
|
||||
// Task ExecuteAsync(CancellationToken cancellationToken = default);
|
||||
// }
|
||||
//
|
||||
// public enum TaskPriority
|
||||
// {
|
||||
// Low = 0,
|
||||
// Normal = 1,
|
||||
// High = 2,
|
||||
// Critical = 3
|
||||
// }
|
||||
//
|
||||
// // 2. 基础任务实现
|
||||
// public abstract class BaseTask : ITaskItem
|
||||
// {
|
||||
// public string TaskId { get; } = Guid.NewGuid().ToString();
|
||||
// public TaskPriority Priority { get; protected set; } = TaskPriority.Normal;
|
||||
// public DateTime CreatedAt { get; } = DateTime.UtcNow;
|
||||
//
|
||||
// public abstract Task ExecuteAsync(CancellationToken cancellationToken = default);
|
||||
// }
|
||||
//
|
||||
// // 3. ETW网络连接任务示例
|
||||
// public class NetworkConnectionTask : BaseTask
|
||||
// {
|
||||
// private readonly NetworkConnectionData _data;
|
||||
// private readonly INetworkDataStore _dataStore;
|
||||
//
|
||||
// public NetworkConnectionTask(NetworkConnectionData data, INetworkDataStore dataStore)
|
||||
// {
|
||||
// _data = data;
|
||||
// _dataStore = dataStore;
|
||||
// Priority = TaskPriority.High; // 网络事件优先级较高
|
||||
// }
|
||||
//
|
||||
// public override async Task ExecuteAsync(CancellationToken cancellationToken = default)
|
||||
// {
|
||||
// // 处理网络连接数据
|
||||
// await _dataStore.UpdateNetworkConnectionAsync(_data);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 4. 数据库写入任务示例
|
||||
// public class DatabaseWriteTask : BaseTask
|
||||
// {
|
||||
// private readonly object _data;
|
||||
// private readonly Func<object, CancellationToken, Task> _writeAction;
|
||||
//
|
||||
// public DatabaseWriteTask(object data, Func<object, CancellationToken, Task> writeAction)
|
||||
// {
|
||||
// _data = data;
|
||||
// _writeAction = writeAction;
|
||||
// Priority = TaskPriority.Normal;
|
||||
// }
|
||||
//
|
||||
// public override async Task ExecuteAsync(CancellationToken cancellationToken = default)
|
||||
// {
|
||||
// await _writeAction(_data, cancellationToken);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 5. 任务队列配置
|
||||
// public class TaskQueueOptions
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// 默认并发数 = CPU核心数
|
||||
// /// </summary>
|
||||
// public int MaxConcurrency { get; set; } = Environment.ProcessorCount;
|
||||
//
|
||||
// /// <summary>
|
||||
// /// 队列最大容量
|
||||
// /// </summary>
|
||||
// public int MaxQueueSize { get; set; } = 10000;
|
||||
//
|
||||
// /// <summary>
|
||||
// /// 单个任务超时时间
|
||||
// /// </summary>
|
||||
// public TimeSpan TaskTimeout { get; set; } = TimeSpan.FromMinutes(5);
|
||||
//
|
||||
// /// <summary>
|
||||
// /// 是否启用优先级处理
|
||||
// /// </summary>
|
||||
// public bool EnablePriorityProcessing { get; set; } = true;
|
||||
// }
|
||||
//
|
||||
// // 6. 核心任务队列实现
|
||||
// public class TaskQueue : ITaskQueue, IDisposable
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// 高性能队列通道
|
||||
// /// </summary>
|
||||
// private readonly Channel<ITaskItem> _taskChannel;
|
||||
//
|
||||
// private readonly ChannelWriter<ITaskItem> _writer;
|
||||
// private readonly ChannelReader<ITaskItem> _reader;
|
||||
// private readonly TaskQueueOptions _options;
|
||||
//
|
||||
// /// <summary>
|
||||
// /// 并发控制信号量
|
||||
// /// </summary>
|
||||
// private readonly SemaphoreSlim _concurrencySemaphore;
|
||||
//
|
||||
// /// <summary>
|
||||
// /// 正在运行的任务字典
|
||||
// /// </summary>
|
||||
// private readonly ConcurrentDictionary<string, TaskInfo> _runningTasks;
|
||||
//
|
||||
// /// <summary>
|
||||
// /// 取消令牌源
|
||||
// /// </summary>
|
||||
// private readonly CancellationTokenSource _cancellationTokenSource;
|
||||
//
|
||||
// /// <summary>
|
||||
// /// 工作线程数组
|
||||
// /// </summary>
|
||||
// private readonly Task[] _workerTasks;
|
||||
//
|
||||
// private bool _disposed = false;
|
||||
//
|
||||
// public TaskQueue(TaskQueueOptions options = null)
|
||||
// {
|
||||
// _options = options ?? new TaskQueueOptions();
|
||||
//
|
||||
// var channelOptions = new BoundedChannelOptions(_options.MaxQueueSize)
|
||||
// {
|
||||
// FullMode = BoundedChannelFullMode.Wait, // 队列满时等待,而不是丢弃
|
||||
// SingleReader = false, // 多个读取者(工作线程)
|
||||
// SingleWriter = false // 多个写入者(生产者)
|
||||
// };
|
||||
//
|
||||
// _taskChannel = Channel.CreateBounded<ITaskItem>(channelOptions);
|
||||
// _writer = _taskChannel.Writer;
|
||||
// _reader = _taskChannel.Reader;
|
||||
//
|
||||
// _concurrencySemaphore = new SemaphoreSlim(_options.MaxConcurrency, _options.MaxConcurrency);
|
||||
// _runningTasks = new ConcurrentDictionary<string, TaskInfo>();
|
||||
// _cancellationTokenSource = new CancellationTokenSource();
|
||||
//
|
||||
// // 启动工作线程
|
||||
// _workerTasks = new Task[_options.MaxConcurrency];
|
||||
// for (int i = 0; i < _options.MaxConcurrency; i++)
|
||||
// {
|
||||
// _workerTasks[i] = Task.Run(() => ProcessTasksAsync(_cancellationTokenSource.Token));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// 任务入队方法
|
||||
// /// </summary>
|
||||
// /// <param name="task"></param>
|
||||
// /// <param name="cancellationToken"></param>
|
||||
// /// <returns></returns>
|
||||
// public async Task<bool> EnqueueAsync(ITaskItem task, CancellationToken cancellationToken = default)
|
||||
// {
|
||||
// if (_disposed)
|
||||
// return false;
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// // 异步写入,支持取消
|
||||
// await _writer.WriteAsync(task, cancellationToken);
|
||||
// return true;
|
||||
// }
|
||||
// catch (Exception)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public TaskStatistics GetStatistics()
|
||||
// {
|
||||
// return new TaskStatistics
|
||||
// {
|
||||
// QueuedTasks = _taskChannel.Reader.Count,
|
||||
// RunningTasks = _runningTasks.Count,
|
||||
// AvailableWorkers = _concurrencySemaphore.CurrentCount
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// public async Task StopAsync()
|
||||
// {
|
||||
// _writer.Complete();
|
||||
// _cancellationTokenSource.Cancel();
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// await Task.WhenAll(_workerTasks);
|
||||
// }
|
||||
// catch (OperationCanceledException)
|
||||
// {
|
||||
// // 预期的取消异常
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// protected async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
// {
|
||||
// var tasks = new List<Task>();
|
||||
//
|
||||
// // 创建多个工作任务
|
||||
// for (int i = 0; i < _options.MaxConcurrency; i++)
|
||||
// {
|
||||
// tasks.Add(ProcessTasksAsync(stoppingToken));
|
||||
// }
|
||||
//
|
||||
// await Task.WhenAll(tasks);
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// 异步任务处理
|
||||
// /// </summary>
|
||||
// /// <param name="cancellationToken"></param>
|
||||
// private async Task ProcessTasksAsync(CancellationToken cancellationToken)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// // 异步枚举,从Channel读取任务
|
||||
// await foreach (var task in _reader.ReadAllAsync(cancellationToken))
|
||||
// {
|
||||
// // 1. 获取信号量,控制并发数
|
||||
// await _concurrencySemaphore.WaitAsync(cancellationToken);
|
||||
//
|
||||
// // 2. 记录任务信息
|
||||
// var taskInfo = new TaskInfo
|
||||
// {
|
||||
// Task = task,
|
||||
// StartTime = DateTime.UtcNow
|
||||
// };
|
||||
//
|
||||
// _runningTasks.TryAdd(task.TaskId, taskInfo);
|
||||
//
|
||||
// // 异步执行任务,不阻塞队列处理
|
||||
// _ = Task.Run(async () =>
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// // 创建带超时的取消令牌
|
||||
// using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
// cts.CancelAfter(_options.TaskTimeout);
|
||||
//
|
||||
// await task.ExecuteAsync(cts.Token);
|
||||
// }
|
||||
// catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
// {
|
||||
// // 任务被取消
|
||||
// }
|
||||
// catch (Exception)
|
||||
// {
|
||||
// // 任务执行失败,静默处理
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// _runningTasks.TryRemove(task.TaskId, out _);
|
||||
// _concurrencySemaphore.Release();
|
||||
// }
|
||||
// }, cancellationToken);
|
||||
// }
|
||||
// }
|
||||
// catch (OperationCanceledException)
|
||||
// {
|
||||
// // 工作线程被取消
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public void Dispose()
|
||||
// {
|
||||
// if (_disposed)
|
||||
// return;
|
||||
//
|
||||
// _disposed = true;
|
||||
// _writer.Complete();
|
||||
// _cancellationTokenSource.Cancel();
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// Task.WaitAll(_workerTasks, TimeSpan.FromSeconds(5));
|
||||
// }
|
||||
// catch (Exception)
|
||||
// {
|
||||
// // 忽略关闭时的异常
|
||||
// }
|
||||
//
|
||||
// _concurrencySemaphore?.Dispose();
|
||||
// _cancellationTokenSource?.Dispose();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 7. 接口定义
|
||||
// public interface ITaskQueue
|
||||
// {
|
||||
// Task<bool> EnqueueAsync(ITaskItem task, CancellationToken cancellationToken = default);
|
||||
// TaskStatistics GetStatistics();
|
||||
// Task StopAsync();
|
||||
// }
|
||||
//
|
||||
// // 8. 辅助类
|
||||
// public class TaskInfo
|
||||
// {
|
||||
// public ITaskItem Task { get; set; }
|
||||
// public DateTime StartTime { get; set; }
|
||||
// }
|
||||
//
|
||||
// public class TaskStatistics
|
||||
// {
|
||||
// public int QueuedTasks { get; set; }
|
||||
// public int RunningTasks { get; set; }
|
||||
// public int AvailableWorkers { get; set; }
|
||||
// }
|
||||
//
|
||||
// // 9. 网络数据存储接口(单例数据存储)
|
||||
// public interface INetworkDataStore
|
||||
// {
|
||||
// Task UpdateNetworkConnectionAsync(NetworkConnectionData data);
|
||||
// Task<IEnumerable<NetworkConnectionData>> GetRecentConnectionsAsync(TimeSpan timeSpan);
|
||||
// Task<NetworkStatistics> GetStatisticsAsync();
|
||||
// }
|
||||
//
|
||||
// // 10. 网络数据存储单例实现
|
||||
// public class NetworkDataStore : INetworkDataStore
|
||||
// {
|
||||
// private readonly ConcurrentQueue<NetworkConnectionData> _recentConnections; // 最近连接队列
|
||||
// private readonly ConcurrentDictionary<string, ConnectionStatistics> _connectionStats; // 连接统计
|
||||
// private readonly ReaderWriterLockSlim _lock; // 读写锁
|
||||
// private readonly Timer _cleanupTimer; // 清理定时器
|
||||
// public NetworkDataStore()
|
||||
// {
|
||||
// _recentConnections = new ConcurrentQueue<NetworkConnectionData>();
|
||||
// _connectionStats = new ConcurrentDictionary<string, ConnectionStatistics>();
|
||||
// _lock = new ReaderWriterLockSlim();
|
||||
//
|
||||
// // 定期清理过期数据
|
||||
// _cleanupTimer = new Timer(CleanupExpiredData, null,
|
||||
// TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
|
||||
// }
|
||||
//
|
||||
// public async Task UpdateNetworkConnectionAsync(NetworkConnectionData data)
|
||||
// {
|
||||
// await Task.Run(() =>
|
||||
// {
|
||||
// _recentConnections.Enqueue(data);
|
||||
//
|
||||
// // 更新统计信息
|
||||
// var key = $"{data.ProcessName}:{data.LocalPort}";
|
||||
// _connectionStats.AddOrUpdate(key,
|
||||
// new ConnectionStatistics { Count = 1, LastSeen = DateTime.UtcNow },
|
||||
// (k, v) => new ConnectionStatistics { Count = v.Count + 1, LastSeen = DateTime.UtcNow });
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// public async Task<IEnumerable<NetworkConnectionData>> GetRecentConnectionsAsync(TimeSpan timeSpan)
|
||||
// {
|
||||
// return await Task.FromResult(
|
||||
// _recentConnections
|
||||
// .Where(c => DateTime.UtcNow - c.Timestamp <= timeSpan)
|
||||
// .ToList());
|
||||
// }
|
||||
//
|
||||
// public async Task<NetworkStatistics> GetStatisticsAsync()
|
||||
// {
|
||||
// return await Task.FromResult(new NetworkStatistics
|
||||
// {
|
||||
// TotalConnections = _recentConnections.Count,
|
||||
// ActiveProcesses = _connectionStats.Count,
|
||||
// LastUpdate = DateTime.UtcNow
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// private void CleanupExpiredData(object state)
|
||||
// {
|
||||
// var cutoff = DateTime.UtcNow.AddHours(-1);
|
||||
//
|
||||
// // 清理过期连接数据
|
||||
// while (_recentConnections.TryPeek(out var connection) &&
|
||||
// connection.Timestamp < cutoff)
|
||||
// {
|
||||
// _recentConnections.TryDequeue(out _);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 11. 数据模型
|
||||
// public class NetworkConnectionData
|
||||
// {
|
||||
// public string ProcessName { get; set; }
|
||||
// public int ProcessId { get; set; }
|
||||
// public string LocalAddress { get; set; }
|
||||
// public int LocalPort { get; set; }
|
||||
// public string RemoteAddress { get; set; }
|
||||
// public int RemotePort { get; set; }
|
||||
// public DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
||||
// public string Protocol { get; set; }
|
||||
// }
|
||||
//
|
||||
// public class ConnectionStatistics
|
||||
// {
|
||||
// public int Count { get; set; }
|
||||
// public DateTime LastSeen { get; set; }
|
||||
// }
|
||||
//
|
||||
// public class NetworkStatistics
|
||||
// {
|
||||
// public int TotalConnections { get; set; }
|
||||
// public int ActiveProcesses { get; set; }
|
||||
// public DateTime LastUpdate { get; set; }
|
||||
// }
|
||||
//
|
||||
// // 12. 简单使用示例
|
||||
// public class TaskQueueManager
|
||||
// {
|
||||
// private readonly TaskQueue _taskQueue;
|
||||
// private readonly NetworkDataStore _dataStore;
|
||||
//
|
||||
// public TaskQueueManager()
|
||||
// {
|
||||
// var options = new TaskQueueOptions
|
||||
// {
|
||||
// MaxConcurrency = Environment.ProcessorCount,
|
||||
// MaxQueueSize = 5000
|
||||
// };
|
||||
//
|
||||
// _taskQueue = new TaskQueue(options);
|
||||
// _dataStore = new NetworkDataStore();
|
||||
// }
|
||||
//
|
||||
// // ETW事件处理
|
||||
// public async Task OnNetworkConnectionEvent(NetworkConnectionData connectionData)
|
||||
// {
|
||||
// var task = new NetworkConnectionTask(connectionData, _dataStore);
|
||||
// await _taskQueue.EnqueueAsync(task);
|
||||
// }
|
||||
//
|
||||
// // 数据库写入
|
||||
// public async Task SaveToDatabase(object data)
|
||||
// {
|
||||
// var dbTask = new DatabaseWriteTask(data, async (d, ct) =>
|
||||
// {
|
||||
// // 实际的数据库写入逻辑
|
||||
// await WriteToDatabase(d);
|
||||
// });
|
||||
//
|
||||
// await _taskQueue.EnqueueAsync(dbTask);
|
||||
// }
|
||||
//
|
||||
// // 获取统计信息
|
||||
// public TaskStatistics GetQueueStatistics()
|
||||
// {
|
||||
// return _taskQueue.GetStatistics();
|
||||
// }
|
||||
//
|
||||
// // 获取网络数据
|
||||
// public async Task<IEnumerable<NetworkConnectionData>> GetRecentConnections(TimeSpan timeSpan)
|
||||
// {
|
||||
// return await _dataStore.GetRecentConnectionsAsync(timeSpan);
|
||||
// }
|
||||
//
|
||||
// private async Task WriteToDatabase(object data)
|
||||
// {
|
||||
// // 数据库写入实现
|
||||
// await Task.Delay(100); // 模拟数据库操作
|
||||
// }
|
||||
//
|
||||
// public async Task StopAsync()
|
||||
// {
|
||||
// await _taskQueue.StopAsync();
|
||||
// _taskQueue.Dispose();
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,724 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
// 1. 任务接口定义
|
||||
public interface ITaskItem
|
||||
{
|
||||
string TaskId { get; }
|
||||
TaskPriority Priority { get; }
|
||||
DateTime CreatedAt { get; }
|
||||
int SequenceNumber { get; set; } // 用于严格排序
|
||||
string GroupId { get; } // 用于分组排序
|
||||
Task ExecuteAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public enum TaskPriority
|
||||
{
|
||||
Low = 0,
|
||||
Normal = 1,
|
||||
High = 2,
|
||||
Critical = 3,
|
||||
Urgent = 4 // 最高优先级,用于插队
|
||||
}
|
||||
|
||||
// 2. 基础任务实现
|
||||
public abstract class BaseTask : ITaskItem
|
||||
{
|
||||
public string TaskId { get; } = Guid.NewGuid().ToString();
|
||||
public TaskPriority Priority { get; set; } = TaskPriority.Normal;
|
||||
public DateTime CreatedAt { get; } = DateTime.UtcNow;
|
||||
public int SequenceNumber { get; set; } = 0;
|
||||
public string GroupId { get; set; } = null;
|
||||
|
||||
public abstract Task ExecuteAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
// 3. 支持分组的任务基类
|
||||
public abstract class GroupedTask : BaseTask
|
||||
{
|
||||
protected GroupedTask(string? groupId)
|
||||
{
|
||||
GroupId = groupId ?? throw new ArgumentNullException(nameof(groupId));
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 网络连接任务(支持分组)
|
||||
public class NetworkConnectionTask : GroupedTask
|
||||
{
|
||||
private readonly NetworkConnectionData _data;
|
||||
private readonly INetworkDataStore _dataStore;
|
||||
|
||||
public NetworkConnectionTask(NetworkConnectionData data, INetworkDataStore dataStore, string? groupId = null)
|
||||
: base(groupId ?? $"network_{data.ProcessName}")
|
||||
{
|
||||
_data = data;
|
||||
_dataStore = dataStore;
|
||||
Priority = TaskPriority.High;
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _dataStore.UpdateNetworkConnectionAsync(_data);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 数据库写入任务
|
||||
public class DatabaseWriteTask : BaseTask
|
||||
{
|
||||
private readonly object _data;
|
||||
private readonly Func<object, CancellationToken, Task> _writeAction;
|
||||
|
||||
public DatabaseWriteTask(object data, Func<object, CancellationToken, Task> writeAction,
|
||||
TaskPriority priority = TaskPriority.Normal)
|
||||
{
|
||||
_data = data;
|
||||
_writeAction = writeAction;
|
||||
Priority = priority;
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _writeAction(_data, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 任务队列配置
|
||||
public class TaskQueueOptions
|
||||
{
|
||||
public int MaxConcurrency { get; set; } = Environment.ProcessorCount;
|
||||
public int MaxQueueSize { get; set; } = 10000;
|
||||
public TimeSpan TaskTimeout { get; set; } = TimeSpan.FromMinutes(5);
|
||||
public bool EnablePriorityProcessing { get; set; } = true;
|
||||
public bool EnableGroupedExecution { get; set; } = true; // 是否启用分组执行
|
||||
public TimeSpan StatisticsUpdateInterval { get; set; } = TimeSpan.FromSeconds(1);
|
||||
}
|
||||
|
||||
// 7. 任务统计信息
|
||||
public class TaskStatistics
|
||||
{
|
||||
public int QueuedTasks { get; set; }
|
||||
public int RunningTasks { get; set; }
|
||||
public int AvailableWorkers { get; set; }
|
||||
public long TotalEnqueued { get; set; } // 总入队任务数
|
||||
public long TotalCompleted { get; set; } // 总完成任务数
|
||||
public long TotalFailed { get; set; } // 总失败任务数
|
||||
public double AverageExecutionTime { get; set; } // 平均执行时间(毫秒)
|
||||
public Dictionary<TaskPriority, int> QueuedByPriority { get; set; } = new();
|
||||
public Dictionary<string, int> QueuedByGroup { get; set; } = new();
|
||||
public DateTime LastUpdated { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
// 8. 任务信息
|
||||
public class TaskInfo
|
||||
{
|
||||
public ITaskItem Task { get; set; }
|
||||
public DateTime StartTime { get; set; }
|
||||
public DateTime? EndTime { get; set; }
|
||||
public bool IsCompleted { get; set; }
|
||||
public bool IsFailed { get; set; }
|
||||
public Exception Exception { get; set; }
|
||||
|
||||
public TimeSpan ExecutionTime => EndTime.HasValue ? EndTime.Value - StartTime : DateTime.UtcNow - StartTime;
|
||||
}
|
||||
|
||||
// 9. 优先级任务比较器
|
||||
public class TaskPriorityComparer : IComparer<ITaskItem>
|
||||
{
|
||||
public int Compare(ITaskItem x, ITaskItem y)
|
||||
{
|
||||
if (x == null && y == null) return 0;
|
||||
if (x == null) return 1;
|
||||
if (y == null) return -1;
|
||||
|
||||
// 1. 先按优先级排序(优先级高的在前)
|
||||
var priorityComparison = y.Priority.CompareTo(x.Priority);
|
||||
if (priorityComparison != 0) return priorityComparison;
|
||||
|
||||
// 2. 同优先级按序列号排序
|
||||
if (x.SequenceNumber != 0 || y.SequenceNumber != 0)
|
||||
{
|
||||
return x.SequenceNumber.CompareTo(y.SequenceNumber);
|
||||
}
|
||||
|
||||
// 3. 最后按创建时间排序(FIFO)
|
||||
return x.CreatedAt.CompareTo(y.CreatedAt);
|
||||
}
|
||||
}
|
||||
|
||||
// 10. 增强的任务队列实现
|
||||
public class EnhancedTaskQueue : ITaskQueue, IDisposable
|
||||
{
|
||||
private readonly PriorityQueue<ITaskItem, ITaskItem> _priorityQueue;
|
||||
private readonly TaskQueueOptions _options;
|
||||
private readonly SemaphoreSlim _concurrencySemaphore;
|
||||
private readonly ConcurrentDictionary<string, TaskInfo> _runningTasks;
|
||||
private readonly ConcurrentDictionary<string, SemaphoreSlim> _groupSemaphores;
|
||||
private readonly CancellationTokenSource _cancellationTokenSource;
|
||||
private readonly Task[] _workerTasks;
|
||||
private readonly ReaderWriterLockSlim _queueLock;
|
||||
private readonly Timer _statisticsTimer;
|
||||
|
||||
// 统计数据
|
||||
private long _totalEnqueued = 0;
|
||||
private long _totalCompleted = 0;
|
||||
private long _totalFailed = 0;
|
||||
private readonly ConcurrentQueue<double> _executionTimes = new();
|
||||
|
||||
/// <summary>
|
||||
/// 当前序列号
|
||||
/// </summary>
|
||||
private int _currentSequenceNumber = 0;
|
||||
|
||||
private bool _disposed = false;
|
||||
private readonly ManualResetEventSlim _newTaskEvent;
|
||||
|
||||
public EnhancedTaskQueue(TaskQueueOptions? options = null)
|
||||
{
|
||||
_options = options ?? new TaskQueueOptions();
|
||||
|
||||
_priorityQueue = new PriorityQueue<ITaskItem, ITaskItem>(new TaskPriorityComparer());
|
||||
_concurrencySemaphore = new SemaphoreSlim(_options.MaxConcurrency, _options.MaxConcurrency);
|
||||
_runningTasks = new ConcurrentDictionary<string, TaskInfo>();
|
||||
_groupSemaphores = new ConcurrentDictionary<string, SemaphoreSlim>();
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_queueLock = new ReaderWriterLockSlim();
|
||||
_newTaskEvent = new ManualResetEventSlim(false);
|
||||
|
||||
// 启动统计更新定时器
|
||||
_statisticsTimer = new Timer(UpdateStatistics, null,
|
||||
_options.StatisticsUpdateInterval, _options.StatisticsUpdateInterval);
|
||||
|
||||
// 启动工作线程
|
||||
_workerTasks = new Task[_options.MaxConcurrency];
|
||||
for (int i = 0; i < _options.MaxConcurrency; i++)
|
||||
{
|
||||
_workerTasks[i] = Task.Run(() => ProcessTasksAsync(_cancellationTokenSource.Token));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> EnqueueAsync(ITaskItem task, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_disposed) return false;
|
||||
|
||||
try
|
||||
{
|
||||
_queueLock.EnterWriteLock();
|
||||
|
||||
// 检查队列大小
|
||||
if (_priorityQueue.Count >= _options.MaxQueueSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果没有设置序列号,自动分配
|
||||
if (task.SequenceNumber == 0)
|
||||
{
|
||||
task.SequenceNumber = Interlocked.Increment(ref _currentSequenceNumber);
|
||||
}
|
||||
|
||||
_priorityQueue.Enqueue(task, task);
|
||||
Interlocked.Increment(ref _totalEnqueued);
|
||||
|
||||
_newTaskEvent.Set(); // 通知工作线程有新任务
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_queueLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> EnqueueUrgentAsync(ITaskItem task, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 设置为最高优先级并立即处理
|
||||
if (task is BaseTask baseTask)
|
||||
{
|
||||
baseTask.Priority = TaskPriority.Urgent;
|
||||
}
|
||||
|
||||
return await EnqueueAsync(task, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<bool> EnqueueSequentialAsync(IEnumerable<ITaskItem> tasks, string? groupId = null)
|
||||
{
|
||||
if (_disposed) return false;
|
||||
|
||||
var taskList = tasks.ToList();
|
||||
if (!taskList.Any()) return true;
|
||||
|
||||
var actualGroupId = groupId ?? Guid.NewGuid().ToString();
|
||||
|
||||
try
|
||||
{
|
||||
_queueLock.EnterWriteLock();
|
||||
|
||||
// 检查队列容量
|
||||
if (_priorityQueue.Count + taskList.Count > _options.MaxQueueSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var sequenceStart = Interlocked.Add(ref _currentSequenceNumber, taskList.Count) - taskList.Count + 1;
|
||||
|
||||
for (int i = 0; i < taskList.Count; i++)
|
||||
{
|
||||
var task = taskList[i];
|
||||
task.SequenceNumber = sequenceStart + i;
|
||||
|
||||
// 设置分组ID
|
||||
if (task is BaseTask baseTask && string.IsNullOrEmpty(baseTask.GroupId))
|
||||
{
|
||||
baseTask.GroupId = actualGroupId;
|
||||
}
|
||||
|
||||
_priorityQueue.Enqueue(task, task);
|
||||
Interlocked.Increment(ref _totalEnqueued);
|
||||
}
|
||||
|
||||
_newTaskEvent.Set();
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_queueLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public TaskStatistics GetStatistics()
|
||||
{
|
||||
var stats = new TaskStatistics
|
||||
{
|
||||
RunningTasks = _runningTasks.Count,
|
||||
AvailableWorkers = _concurrencySemaphore.CurrentCount,
|
||||
TotalEnqueued = Interlocked.Read(ref _totalEnqueued),
|
||||
TotalCompleted = Interlocked.Read(ref _totalCompleted),
|
||||
TotalFailed = Interlocked.Read(ref _totalFailed),
|
||||
LastUpdated = DateTime.UtcNow
|
||||
};
|
||||
|
||||
// 计算平均执行时间
|
||||
if (_executionTimes.Count > 0)
|
||||
{
|
||||
stats.AverageExecutionTime = _executionTimes.Average();
|
||||
}
|
||||
|
||||
// 获取队列统计
|
||||
_queueLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
stats.QueuedTasks = _priorityQueue.Count;
|
||||
|
||||
// 按优先级和分组统计(这里简化处理,实际中可能需要遍历队列)
|
||||
var tempQueue = new List<ITaskItem>();
|
||||
while (_priorityQueue.Count > 0)
|
||||
{
|
||||
var task = _priorityQueue.Dequeue();
|
||||
tempQueue.Add(task);
|
||||
|
||||
// 统计优先级
|
||||
if (!stats.QueuedByPriority.ContainsKey(task.Priority))
|
||||
stats.QueuedByPriority[task.Priority] = 0;
|
||||
stats.QueuedByPriority[task.Priority]++;
|
||||
|
||||
// 统计分组
|
||||
if (!string.IsNullOrEmpty(task.GroupId))
|
||||
{
|
||||
stats.QueuedByGroup.TryAdd(task.GroupId, 0);
|
||||
stats.QueuedByGroup[task.GroupId]++;
|
||||
}
|
||||
}
|
||||
|
||||
// 重新入队
|
||||
foreach (var task in tempQueue)
|
||||
{
|
||||
_priorityQueue.Enqueue(task, task);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_queueLock.ExitReadLock();
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public List<ITaskItem> GetQueuedTasks()
|
||||
{
|
||||
_queueLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
var result = new List<ITaskItem>();
|
||||
var tempQueue = new List<ITaskItem>();
|
||||
|
||||
while (_priorityQueue.Count > 0)
|
||||
{
|
||||
var task = _priorityQueue.Dequeue();
|
||||
tempQueue.Add(task);
|
||||
result.Add(task);
|
||||
}
|
||||
|
||||
// 重新入队
|
||||
foreach (var task in tempQueue)
|
||||
{
|
||||
_priorityQueue.Enqueue(task, task);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_queueLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public List<TaskInfo> GetRunningTasks()
|
||||
{
|
||||
return _runningTasks.Values.ToList();
|
||||
}
|
||||
|
||||
private async Task ProcessTasksAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
ITaskItem? task = null;
|
||||
|
||||
// 从优先级队列获取任务
|
||||
_queueLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (_priorityQueue.Count > 0)
|
||||
{
|
||||
task = _priorityQueue.Dequeue();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_queueLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
if (task == null)
|
||||
{
|
||||
// 没有任务,等待新任务信号
|
||||
try
|
||||
{
|
||||
_newTaskEvent.Wait(1000, cancellationToken); // 最多等待1秒
|
||||
_newTaskEvent.Reset();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// 控制并发数
|
||||
await _concurrencySemaphore.WaitAsync(cancellationToken);
|
||||
|
||||
// 处理分组任务的串行执行
|
||||
SemaphoreSlim? groupSemaphore = null;
|
||||
if (_options.EnableGroupedExecution && !string.IsNullOrEmpty(task.GroupId))
|
||||
{
|
||||
groupSemaphore = _groupSemaphores.GetOrAdd(task.GroupId, _ => new SemaphoreSlim(1, 1));
|
||||
await groupSemaphore.WaitAsync(cancellationToken);
|
||||
}
|
||||
|
||||
var taskInfo = new TaskInfo
|
||||
{
|
||||
Task = task,
|
||||
StartTime = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_runningTasks.TryAdd(task.TaskId, taskInfo);
|
||||
|
||||
// 异步执行任务
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
cts.CancelAfter(_options.TaskTimeout);
|
||||
|
||||
await task.ExecuteAsync(cts.Token);
|
||||
|
||||
taskInfo.IsCompleted = true;
|
||||
taskInfo.EndTime = DateTime.UtcNow;
|
||||
Interlocked.Increment(ref _totalCompleted);
|
||||
|
||||
// 记录执行时间
|
||||
var executionTime = taskInfo.ExecutionTime.TotalMilliseconds;
|
||||
_executionTimes.Enqueue(executionTime);
|
||||
|
||||
// 限制执行时间队列大小
|
||||
while (_executionTimes.Count > 1000)
|
||||
{
|
||||
_executionTimes.TryDequeue(out _);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
taskInfo.IsFailed = true;
|
||||
taskInfo.Exception = ex;
|
||||
taskInfo.EndTime = DateTime.UtcNow;
|
||||
Interlocked.Increment(ref _totalFailed);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_runningTasks.TryRemove(task.TaskId, out _);
|
||||
_concurrencySemaphore.Release();
|
||||
groupSemaphore?.Release();
|
||||
}
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStatistics(object state)
|
||||
{
|
||||
// 定期更新统计信息的逻辑
|
||||
// 这里可以添加更复杂的统计计算
|
||||
}
|
||||
|
||||
public async Task StopAsync()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
_cancellationTokenSource.Cancel();
|
||||
_newTaskEvent.Set(); // 唤醒等待的工作线程
|
||||
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(_workerTasks);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 预期的取消异常
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
_disposed = true;
|
||||
_cancellationTokenSource.Cancel();
|
||||
_newTaskEvent.Set();
|
||||
|
||||
try
|
||||
{
|
||||
Task.WaitAll(_workerTasks, TimeSpan.FromSeconds(5));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 忽略关闭时的异常
|
||||
}
|
||||
|
||||
_concurrencySemaphore?.Dispose();
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_queueLock?.Dispose();
|
||||
_newTaskEvent?.Dispose();
|
||||
_statisticsTimer?.Dispose();
|
||||
|
||||
// 清理分组信号量
|
||||
foreach (var semaphore in _groupSemaphores.Values)
|
||||
{
|
||||
semaphore.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 11. 增强的接口定义
|
||||
public interface ITaskQueue
|
||||
{
|
||||
Task<bool> EnqueueAsync(ITaskItem task, CancellationToken cancellationToken = default);
|
||||
Task<bool> EnqueueUrgentAsync(ITaskItem task, CancellationToken cancellationToken = default);
|
||||
Task<bool> EnqueueSequentialAsync(IEnumerable<ITaskItem> tasks, string groupId = null);
|
||||
TaskStatistics GetStatistics();
|
||||
List<ITaskItem> GetQueuedTasks();
|
||||
List<TaskInfo> GetRunningTasks();
|
||||
Task StopAsync();
|
||||
}
|
||||
|
||||
// 12. 网络数据存储接口和实现(保持原有实现)
|
||||
public interface INetworkDataStore
|
||||
{
|
||||
Task UpdateNetworkConnectionAsync(NetworkConnectionData data);
|
||||
Task<IEnumerable<NetworkConnectionData>> GetRecentConnectionsAsync(TimeSpan timeSpan);
|
||||
Task<NetworkStatistics> GetStatisticsAsync();
|
||||
}
|
||||
|
||||
public class NetworkDataStore : INetworkDataStore
|
||||
{
|
||||
private readonly ConcurrentQueue<NetworkConnectionData> _recentConnections;
|
||||
private readonly ConcurrentDictionary<string, ConnectionStatistics> _connectionStats;
|
||||
private readonly Timer _cleanupTimer;
|
||||
|
||||
public NetworkDataStore()
|
||||
{
|
||||
_recentConnections = new ConcurrentQueue<NetworkConnectionData>();
|
||||
_connectionStats = new ConcurrentDictionary<string, ConnectionStatistics>();
|
||||
|
||||
_cleanupTimer = new Timer(CleanupExpiredData, null,
|
||||
TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
|
||||
}
|
||||
|
||||
public async Task UpdateNetworkConnectionAsync(NetworkConnectionData data)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
_recentConnections.Enqueue(data);
|
||||
|
||||
var key = $"{data.ProcessName}:{data.LocalPort}";
|
||||
_connectionStats.AddOrUpdate(key,
|
||||
new ConnectionStatistics { Count = 1, LastSeen = DateTime.UtcNow },
|
||||
(k, v) => new ConnectionStatistics { Count = v.Count + 1, LastSeen = DateTime.UtcNow });
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<NetworkConnectionData>> GetRecentConnectionsAsync(TimeSpan timeSpan)
|
||||
{
|
||||
return await Task.FromResult(
|
||||
_recentConnections
|
||||
.Where(c => DateTime.UtcNow - c.Timestamp <= timeSpan)
|
||||
.ToList());
|
||||
}
|
||||
|
||||
public async Task<NetworkStatistics> GetStatisticsAsync()
|
||||
{
|
||||
return await Task.FromResult(new NetworkStatistics
|
||||
{
|
||||
TotalConnections = _recentConnections.Count,
|
||||
ActiveProcesses = _connectionStats.Count,
|
||||
LastUpdate = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
|
||||
private void CleanupExpiredData(object state)
|
||||
{
|
||||
var cutoff = DateTime.UtcNow.AddHours(-1);
|
||||
|
||||
while (_recentConnections.TryPeek(out var connection) &&
|
||||
connection.Timestamp < cutoff)
|
||||
{
|
||||
_recentConnections.TryDequeue(out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 13. 数据模型
|
||||
public class NetworkConnectionData
|
||||
{
|
||||
public string ProcessName { get; set; }
|
||||
public int ProcessId { get; set; }
|
||||
public string LocalAddress { get; set; }
|
||||
public int LocalPort { get; set; }
|
||||
public string RemoteAddress { get; set; }
|
||||
public int RemotePort { get; set; }
|
||||
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
||||
public string Protocol { get; set; }
|
||||
}
|
||||
|
||||
public class ConnectionStatistics
|
||||
{
|
||||
public int Count { get; set; }
|
||||
public DateTime LastSeen { get; set; }
|
||||
}
|
||||
|
||||
public class NetworkStatistics
|
||||
{
|
||||
public int TotalConnections { get; set; }
|
||||
public int ActiveProcesses { get; set; }
|
||||
public DateTime LastUpdate { get; set; }
|
||||
}
|
||||
|
||||
// 14. 增强的任务队列管理器
|
||||
public class EnhancedTaskQueueManager
|
||||
{
|
||||
private readonly EnhancedTaskQueue _taskQueue;
|
||||
private readonly NetworkDataStore _dataStore;
|
||||
|
||||
public EnhancedTaskQueueManager(TaskQueueOptions options = null)
|
||||
{
|
||||
var defaultOptions = new TaskQueueOptions
|
||||
{
|
||||
MaxConcurrency = Environment.ProcessorCount,
|
||||
MaxQueueSize = 5000,
|
||||
EnableGroupedExecution = true,
|
||||
EnablePriorityProcessing = true
|
||||
};
|
||||
|
||||
_taskQueue = new EnhancedTaskQueue(options ?? defaultOptions);
|
||||
_dataStore = new NetworkDataStore();
|
||||
}
|
||||
|
||||
// 普通任务入队
|
||||
public async Task<bool> EnqueueNetworkTask(NetworkConnectionData data)
|
||||
{
|
||||
var task = new NetworkConnectionTask(data, _dataStore);
|
||||
return await _taskQueue.EnqueueAsync(task);
|
||||
}
|
||||
|
||||
// 紧急任务插队
|
||||
public async Task<bool> EnqueueUrgentNetworkTask(NetworkConnectionData data)
|
||||
{
|
||||
var task = new NetworkConnectionTask(data, _dataStore);
|
||||
return await _taskQueue.EnqueueUrgentAsync(task);
|
||||
}
|
||||
|
||||
// 批量有序任务
|
||||
public async Task<bool> EnqueueSequentialDatabaseWrites(IEnumerable<object> dataList)
|
||||
{
|
||||
var tasks = dataList.Select(data => new DatabaseWriteTask(data,
|
||||
async (d, ct) => await WriteToDatabase(d),
|
||||
TaskPriority.Normal)).Cast<ITaskItem>();
|
||||
|
||||
return await _taskQueue.EnqueueSequentialAsync(tasks, "database_batch");
|
||||
}
|
||||
|
||||
// 获取详细统计信息
|
||||
public TaskStatistics GetDetailedStatistics()
|
||||
{
|
||||
return _taskQueue.GetStatistics();
|
||||
}
|
||||
|
||||
// 获取队列中的任务列表
|
||||
public List<ITaskItem> GetPendingTasks()
|
||||
{
|
||||
return _taskQueue.GetQueuedTasks();
|
||||
}
|
||||
|
||||
// 获取正在执行的任务
|
||||
public List<TaskInfo> GetActiveTasks()
|
||||
{
|
||||
return _taskQueue.GetRunningTasks();
|
||||
}
|
||||
|
||||
// 获取网络数据
|
||||
public async Task<IEnumerable<NetworkConnectionData>> GetRecentConnections(TimeSpan timeSpan)
|
||||
{
|
||||
return await _dataStore.GetRecentConnectionsAsync(timeSpan);
|
||||
}
|
||||
|
||||
private async Task WriteToDatabase(object data)
|
||||
{
|
||||
// 模拟数据库写入
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
public async Task StopAsync()
|
||||
{
|
||||
await _taskQueue.StopAsync();
|
||||
_taskQueue.Dispose();
|
||||
}
|
||||
}
|
||||
// 保留一个任务队列工具函数;
|
||||
// 提供公共阿德网络处理模板;
|
||||
// 尝试获取所有的网络连接信息;
|
||||
// 如何模拟低性能设设备的软件运行情况?
|
||||
// 提供公共的网络处理任务类,并将其投入使用;
|
||||
// 提供全局的公共的,唯一的信息类(提供软件开启状态;网络连接;等其他信息) 将网络信息及其他信息进程整合
|
||||
// 页面展示提供多种状态:(仅联网软件;已打开软件)
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace Application.Utils;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class SysHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取可用端口
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static int GetAvailablePort()
|
||||
{
|
||||
TcpListener listener = new TcpListener(IPAddress.Loopback, 0); // 端口号为0表示由系统自动分配
|
||||
listener.Start();
|
||||
int port = ((IPEndPoint)listener.LocalEndpoint).Port;
|
||||
listener.Stop();
|
||||
return port;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Core;
|
||||
|
||||
public class Class1
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Data;
|
||||
|
||||
public class Class1
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,81 @@
|
||||
using System.Diagnostics;
|
||||
using Infrastructure.Models;
|
||||
|
||||
namespace Infrastructure.GlobalCaches;
|
||||
|
||||
/// <summary>
|
||||
/// 进程信息缓存
|
||||
/// </summary>
|
||||
public sealed class ProcessCache
|
||||
{
|
||||
private static readonly Lazy<ProcessCache> _instance = new(() => new ProcessCache());
|
||||
|
||||
public static ProcessCache Instance => _instance.Value;
|
||||
|
||||
public static string GetProcessKey(int pid)
|
||||
{
|
||||
var process = Process.GetProcessById(pid);
|
||||
return $"{pid}-{process.StartTime:yyyyMMddHHmmss}";
|
||||
}
|
||||
|
||||
// 私有构造函数,禁止外部创建实例
|
||||
private ProcessCache()
|
||||
{
|
||||
// 初始化缓存等操作
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 缓存字典【给定初始量 1000 ,后期可根据需求调整,避免频繁扩容】
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, ProgramInfo> _cache = new(1000);
|
||||
|
||||
// 设置缓存
|
||||
public void Set(string key, ProgramInfo value)
|
||||
{
|
||||
lock (_cache)
|
||||
{
|
||||
_cache[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取缓存
|
||||
public ProgramInfo? Get(string key)
|
||||
{
|
||||
lock (_cache)
|
||||
{
|
||||
return _cache.GetValueOrDefault(key);
|
||||
}
|
||||
}
|
||||
|
||||
// 移除缓存
|
||||
public void Remove(string key)
|
||||
{
|
||||
lock (_cache)
|
||||
{
|
||||
_cache.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
PID 重用问题:
|
||||
|
||||
🧠 背景 recap:
|
||||
操作系统(如 Windows)中的每个连接通常可以获取一个 PID。
|
||||
但由于:
|
||||
|
||||
进程退出后,PID 会被操作系统复用(可能很快)
|
||||
|
||||
某些连接(如 TIME_WAIT、CLOSE_WAIT)在进程退出后仍然保留一段时间
|
||||
因此:不能仅仅根据 PID 匹配连接与进程。
|
||||
|
||||
✅ 解决方法:引入 进程启动时间 + 连接时间戳 双重判断
|
||||
✅ 核心原则:
|
||||
一个连接只能属于:连接开始时间 ≥ 进程启动时间 的进程
|
||||
|
||||
❗反面例子:
|
||||
如果连接时间是 18:00
|
||||
但 PID 对应的进程启动时间是 18:10
|
||||
→ 说明这个进程是后来启动的(PID 被重用了)
|
||||
→ ❌ 不应该关联这条连接
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.21" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="10.0.0-preview.4.25258.110" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
<ProjectReference Include="..\Utils.ETW\Utils.ETW.csproj" />
|
||||
<ProjectReference Include="..\WinDivertNet\WinDivertNet.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,89 @@
|
||||
using Utils.ETW.Models;
|
||||
|
||||
namespace Infrastructure.Models;
|
||||
|
||||
/// <summary>
|
||||
/// 进程信息【软件信息】
|
||||
/// </summary>
|
||||
public class ProgramInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 进程名称
|
||||
/// </summary>
|
||||
public string ProcessName { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 进程ID
|
||||
/// </summary>
|
||||
public int ProcessId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 启动时间
|
||||
/// </summary>
|
||||
public DateTime StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否退出
|
||||
/// </summary>
|
||||
public bool HasExited { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 退出时间
|
||||
/// </summary>
|
||||
public DateTime ExitTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 退出代码
|
||||
/// </summary>
|
||||
public int ExitCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 占用内存
|
||||
/// </summary>
|
||||
public long UseMemory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 线程数
|
||||
/// </summary>
|
||||
public int ThreadCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 主模块路径
|
||||
/// </summary>
|
||||
public string? MainModulePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 启动文件名
|
||||
/// </summary>
|
||||
public string? MainModuleName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件描述
|
||||
/// </summary>
|
||||
public string? FileDescription { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 产品名称
|
||||
/// </summary>
|
||||
public string? ProductName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 公司名称
|
||||
/// </summary>
|
||||
public string? CompanyName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 版本号
|
||||
/// </summary>
|
||||
public string? Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 版权
|
||||
/// </summary>
|
||||
public string? LegalCopyright { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
public string IconBase64 { get; set; } = "";
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Infrastructure.utils;
|
||||
|
||||
|
||||
public static class IconHelper
|
||||
{
|
||||
[DllImport("Shell32.dll", CharSet = CharSet.Auto)]
|
||||
private static extern IntPtr ExtractIcon(IntPtr hInst, string lpszExeFileName, int nIconIndex);
|
||||
|
||||
/// <summary>
|
||||
/// 获取 ICON
|
||||
/// </summary>
|
||||
/// <param name="exePath"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetIconBase64(string? exePath)
|
||||
{
|
||||
if (exePath == null && !File.Exists(exePath)) return "";
|
||||
IntPtr hIcon = ExtractIcon(IntPtr.Zero, exePath, 0);
|
||||
if (hIcon == IntPtr.Zero) return "";
|
||||
|
||||
using System.Drawing.Icon icon = Icon.FromHandle(hIcon);
|
||||
using Bitmap bmp = icon.ToBitmap();
|
||||
using MemoryStream ms = new MemoryStream();
|
||||
// 保存为 PNG 格式
|
||||
bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
|
||||
byte[] bytes = ms.ToArray();
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using Common.Logger;
|
||||
|
||||
namespace Infrastructure.utils;
|
||||
|
||||
using Microsoft.Diagnostics.Tracing.Parsers;
|
||||
using System;
|
||||
using Microsoft.Diagnostics.Tracing;
|
||||
using Microsoft.Diagnostics.Tracing.Parsers.Kernel;
|
||||
using Microsoft.Diagnostics.Tracing.Session;
|
||||
|
||||
public enum TCP_TABLE_CLASS
|
||||
{
|
||||
TCP_TABLE_BASIC_LISTENER,
|
||||
TCP_TABLE_BASIC_CONNECTIONS,
|
||||
TCP_TABLE_BASIC_ALL,
|
||||
TCP_TABLE_OWNER_PID_LISTENER,
|
||||
TCP_TABLE_OWNER_PID_CONNECTIONS,
|
||||
TCP_TABLE_OWNER_PID_ALL, // 使用这个
|
||||
TCP_TABLE_OWNER_MODULE_LISTENER,
|
||||
TCP_TABLE_OWNER_MODULE_CONNECTIONS,
|
||||
TCP_TABLE_OWNER_MODULE_ALL
|
||||
}
|
||||
|
||||
public enum UDP_TABLE_CLASS
|
||||
{
|
||||
UDP_TABLE_BASIC,
|
||||
UDP_TABLE_OWNER_PID,
|
||||
UDP_TABLE_OWNER_MODULE
|
||||
}
|
||||
|
||||
public static class NetUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// 联网内容监视
|
||||
/// </summary>
|
||||
public static void GetNetInfo()
|
||||
{
|
||||
if (TraceEventSession.IsElevated() == false)
|
||||
{
|
||||
Log.Information("请以管理员身份运行此程序。");
|
||||
return;
|
||||
}
|
||||
|
||||
using (var session = new TraceEventSession("NetTraceSession"))
|
||||
{
|
||||
// 启用 Kernel 网络事件
|
||||
session.EnableKernelProvider(
|
||||
KernelTraceEventParser.Keywords.NetworkTCPIP);
|
||||
|
||||
Log.Information("监听网络事件中(按 Ctrl+C 停止)...");
|
||||
session.Source.Kernel.TcpIpConnect += data =>
|
||||
{
|
||||
Log.Information(
|
||||
$"[TCP Connect] PID={data.ProcessID} {data.saddr}:{data.sport} -> {data.daddr}:{data.dport}");
|
||||
};
|
||||
session.Source.Kernel.TcpIpDisconnect += data =>
|
||||
{
|
||||
Log.Information(
|
||||
$"[TCP Disconnect] PID={data.ProcessID} {data.saddr}:{data.sport} -> {data.daddr}:{data.dport}");
|
||||
};
|
||||
session.Source.Kernel.TcpIpSend += data =>
|
||||
{
|
||||
Log.Information(
|
||||
$"[TCP Send] PID={data.ProcessID} {data.saddr}:{data.sport} -> {data.daddr}:{data.dport}, Size={data.size} bytes");
|
||||
};
|
||||
session.Source.Kernel.TcpIpRecv += data =>
|
||||
{
|
||||
Log.Information(
|
||||
$"[TCP Recv] PID={data.ProcessID} {data.saddr}:{data.sport} <- {data.daddr}:{data.dport}, Size={data.size} bytes");
|
||||
};
|
||||
session.Source.Kernel.UdpIpSend += data =>
|
||||
{
|
||||
Log.Information(
|
||||
$"[UDP Send] PID={data.ProcessID} {data.saddr}:{data.sport} -> {data.daddr}:{data.dport}, Size={data.size} bytes");
|
||||
};
|
||||
session.Source.Kernel.UdpIpRecv += data =>
|
||||
{
|
||||
Log.Information(
|
||||
$"[UDP Recv] PID={data.ProcessID} {data.saddr}:{data.sport} <- {data.daddr}:{data.dport}, Size={data.size} bytes");
|
||||
};
|
||||
session.Source.Process(); // 开始监听
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有连接
|
||||
/// </summary>
|
||||
public static void GetAllTcpConnections()
|
||||
{
|
||||
int buffSize = 0;
|
||||
GetExtendedTcpTable(IntPtr.Zero, ref buffSize, true, 2, TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_ALL, 0);
|
||||
IntPtr tcpTablePtr = Marshal.AllocHGlobal(buffSize);
|
||||
|
||||
try
|
||||
{
|
||||
if (GetExtendedTcpTable(tcpTablePtr, ref buffSize, true, 2,
|
||||
TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_ALL, 0) == 0)
|
||||
{
|
||||
int rowCount = Marshal.ReadInt32(tcpTablePtr);
|
||||
IntPtr rowPtr = IntPtr.Add(tcpTablePtr, 4);
|
||||
|
||||
for (int i = 0; i < rowCount; i++)
|
||||
{
|
||||
MIB_TCPROW_OWNER_PID row = Marshal.PtrToStructure<MIB_TCPROW_OWNER_PID>(rowPtr);
|
||||
IPAddress localIp = new IPAddress(row.localAddr);
|
||||
IPAddress remoteIp = new IPAddress(row.remoteAddr);
|
||||
int localPort = ntohs((ushort)row.localPort);
|
||||
int remotePort = ntohs((ushort)row.remotePort);
|
||||
|
||||
Log.Information(
|
||||
$"PID: {row.owningPid}, {localIp}:{localPort} -> {remoteIp}:{remotePort}, State: {row.state}");
|
||||
rowPtr = IntPtr.Add(rowPtr, Marshal.SizeOf<MIB_TCPROW_OWNER_PID>());
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(tcpTablePtr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 网络字节序转主机字节序
|
||||
/// </summary>
|
||||
/// <param name="netshort"></param>
|
||||
/// <returns></returns>
|
||||
private static ushort ntohs(ushort netshort) => (ushort)((netshort >> 8) | (netshort << 8));
|
||||
|
||||
[DllImport("iphlpapi.dll", SetLastError = true)]
|
||||
private static extern uint GetExtendedTcpTable(
|
||||
IntPtr pTcpTable,
|
||||
ref int dwOutBufLen,
|
||||
bool sort,
|
||||
int ipVersion,
|
||||
TCP_TABLE_CLASS tableClass,
|
||||
uint reserved = 0);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct MIB_TCPROW_OWNER_PID
|
||||
{
|
||||
public uint state; // 状态:如 ESTABLISHED, LISTENING
|
||||
public uint localAddr; // 本地 IP
|
||||
public uint localPort; // 本地端口(需要字节序转换)
|
||||
public uint remoteAddr; // 远程 IP
|
||||
public uint remotePort; // 远程端口
|
||||
public int owningPid; // 对应的进程 PID
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct MIB_TCPTABLE_OWNER_PID
|
||||
{
|
||||
public uint dwNumEntries; // 总连接数
|
||||
public MIB_TCPROW_OWNER_PID table; // 实际连接(数组)
|
||||
}
|
||||
|
||||
|
||||
[DllImport("iphlpapi.dll", SetLastError = true)]
|
||||
private static extern uint GetExtendedUdpTable(
|
||||
IntPtr pUdpTable,
|
||||
ref int dwOutBufLen,
|
||||
bool sort,
|
||||
int ipVersion,
|
||||
UDP_TABLE_CLASS tableClass,
|
||||
uint reserved = 0);
|
||||
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct MIB_UDPROW_OWNER_PID
|
||||
{
|
||||
public uint localAddr; // 本地 IP
|
||||
public uint localPort; // 本地端口(需要字节序转换)
|
||||
public int owningPid; // 进程 PID
|
||||
}
|
||||
|
||||
public static void GetAllUdpListeners()
|
||||
{
|
||||
int buffSize = 0;
|
||||
GetExtendedUdpTable(IntPtr.Zero, ref buffSize, true, 2, UDP_TABLE_CLASS.UDP_TABLE_OWNER_PID, 0);
|
||||
IntPtr udpTablePtr = Marshal.AllocHGlobal(buffSize);
|
||||
|
||||
try
|
||||
{
|
||||
if (GetExtendedUdpTable(udpTablePtr, ref buffSize, true, 2, UDP_TABLE_CLASS.UDP_TABLE_OWNER_PID, 0) == 0)
|
||||
{
|
||||
int rowCount = Marshal.ReadInt32(udpTablePtr);
|
||||
IntPtr rowPtr = IntPtr.Add(udpTablePtr, 4);
|
||||
|
||||
for (int i = 0; i < rowCount; i++)
|
||||
{
|
||||
MIB_UDPROW_OWNER_PID row = Marshal.PtrToStructure<MIB_UDPROW_OWNER_PID>(rowPtr);
|
||||
IPAddress localIp = new IPAddress(row.localAddr);
|
||||
int localPort = ntohs1((ushort)row.localPort);
|
||||
|
||||
Log.Information($"PID: {row.owningPid}, UDP {localIp}:{localPort}");
|
||||
rowPtr = IntPtr.Add(rowPtr, Marshal.SizeOf<MIB_UDPROW_OWNER_PID>());
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(udpTablePtr);
|
||||
}
|
||||
}
|
||||
|
||||
private static ushort ntohs1(ushort netshort) => (ushort)((netshort >> 8) | (netshort << 8));
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
using Common;
|
||||
using Common.ExpandException;
|
||||
using Common.Logger;
|
||||
|
||||
namespace Infrastructure.utils;
|
||||
|
||||
using Infrastructure.GlobalCaches;
|
||||
using Infrastructure.Models;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
public static class SysInfoUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// 程序自身信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetProgramDiagnostics()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
// === 程序自身信息 ===
|
||||
var asm = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
|
||||
sb.AppendLine("=== 程序信息 ===");
|
||||
sb.AppendLine($"程序集名称:{asm.GetName().Name}");
|
||||
sb.AppendLine($"版本号:{asm.GetName().Version}");
|
||||
sb.AppendLine($"路径:{asm.Location}");
|
||||
sb.AppendLine($"启动目录:{AppDomain.CurrentDomain.BaseDirectory}");
|
||||
sb.AppendLine($"命令行参数:{string.Join(" ", Environment.GetCommandLineArgs())}");
|
||||
sb.AppendLine();
|
||||
|
||||
// === 进程运行信息 ===
|
||||
var proc = Process.GetCurrentProcess();
|
||||
sb.AppendLine("=== 进程状态 ===");
|
||||
sb.AppendLine($"进程 ID:{proc.Id}");
|
||||
sb.AppendLine($"进程名称:{proc.ProcessName}");
|
||||
sb.AppendLine($"启动时间:{proc.StartTime}");
|
||||
sb.AppendLine($"运行时长:{DateTime.Now - proc.StartTime}");
|
||||
sb.AppendLine($"线程数:{proc.Threads.Count}");
|
||||
sb.AppendLine($"内存占用:{FormatBytes(proc.WorkingSet64)}");
|
||||
sb.AppendLine();
|
||||
|
||||
// === 系统环境信息 ===
|
||||
sb.AppendLine("=== 系统环境 ===");
|
||||
sb.AppendLine($"操作系统:{RuntimeInformation.OSDescription}");
|
||||
sb.AppendLine($"架构:{(Environment.Is64BitOperatingSystem ? "64位" : "32位")}");
|
||||
sb.AppendLine($"CPU 核心数:{Environment.ProcessorCount}");
|
||||
sb.AppendLine($"机器名:{Environment.MachineName}");
|
||||
sb.AppendLine($"当前用户:{Environment.UserName}");
|
||||
sb.AppendLine();
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定进程的信息
|
||||
/// </summary>
|
||||
/// <param name="pid"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ExceptionExpand"></exception>
|
||||
public static ProgramInfo InspectProcess(int pid)
|
||||
{
|
||||
try
|
||||
{
|
||||
var proc = Process.GetProcessById(pid);
|
||||
|
||||
#region 检查缓存
|
||||
|
||||
var pck = ProcessCache.GetProcessKey(pid);
|
||||
var programInfo = ProcessCache.Instance.Get(pck);
|
||||
if (programInfo != null)
|
||||
{
|
||||
return programInfo;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 获取进程信息
|
||||
|
||||
var exeInfo = new ProgramInfo
|
||||
{
|
||||
ProcessName = proc.ProcessName,
|
||||
ProcessId = proc.Id,
|
||||
StartTime = proc.StartTime,
|
||||
HasExited = proc.HasExited,
|
||||
ExitTime = proc.ExitTime,
|
||||
ExitCode = proc.ExitCode,
|
||||
UseMemory = proc.WorkingSet64,
|
||||
ThreadCount = proc.Threads.Count,
|
||||
MainModulePath = proc.MainModule?.FileName,
|
||||
MainModuleName = proc.MainModule?.ModuleName
|
||||
};
|
||||
|
||||
// MainModule 是一个涉及 native 调用的属性,不建议频繁调用,尤其在枚举大量进程时可能影响性能。
|
||||
|
||||
if (exeInfo.MainModulePath != null)
|
||||
{
|
||||
var versionInfo = FileVersionInfo.GetVersionInfo(exeInfo.MainModulePath);
|
||||
|
||||
exeInfo.FileDescription = versionInfo.FileDescription;
|
||||
exeInfo.ProductName = versionInfo.ProductName;
|
||||
exeInfo.CompanyName = versionInfo.CompanyName;
|
||||
exeInfo.Version = versionInfo.FileVersion;
|
||||
exeInfo.LegalCopyright = versionInfo.LegalCopyright;
|
||||
}
|
||||
|
||||
// 获取软件 Icon 【不一定存在】
|
||||
var iconBase64 = IconHelper.GetIconBase64(exeInfo.MainModulePath);
|
||||
exeInfo.IconBase64 = iconBase64;
|
||||
|
||||
#endregion
|
||||
|
||||
ProcessCache.Instance.Set(pck, exeInfo);
|
||||
return exeInfo;
|
||||
}
|
||||
// 只捕获能遇见的异常
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
Log.Information($"获取进程信息失败:{ex.Message}");
|
||||
throw ExceptionEnum.ProcessGetException;
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatBytes(long bytes)
|
||||
{
|
||||
string[] sizes = ["B", "KB", "MB", "GB", "TB"];
|
||||
double len = bytes;
|
||||
int order = 0;
|
||||
while (len >= 1024 && order < sizes.Length - 1)
|
||||
{
|
||||
order++;
|
||||
len /= 1024;
|
||||
}
|
||||
|
||||
return $"{len:0.##} {sizes[order]}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shell", "Shell\Shell.csproj", "{9AB85EA6-D0E7-4EE5-9B2E-EA4B71585540}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Data", "Data\Data.csproj", "{2BBC748F-62F3-4DE3-A4F2-2D483062736C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "Infrastructure\Infrastructure.csproj", "{67448B94-E362-4531-AB18-E7E6D540B579}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Application", "Application\Application.csproj", "{399AF9C6-701E-49B4-B165-FA9C7B7BFAF5}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{F16BB136-23AD-43F6-8D95-4C0D88FD8FBA}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common.Test", "Common.Test\Common.Test.csproj", "{B3F481C9-B351-449C-AE2B-CDEC29BD5DCC}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common.Wpf", "Common.Wpf\Common.Wpf.csproj", "{293477FA-3C1C-429F-9FBA-AD16A3E402BC}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common.Net", "Common.Net\Common.Net.csproj", "{AEE55360-757E-4B63-9478-2F201B95266A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Utils.ETW", "Utils.ETW\Utils.ETW.csproj", "{10CCB5B1-4672-4166-8CFC-24A48FC86A39}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Utils.ETW.Test", "Utils.ETW.Test\Utils.ETW.Test.csproj", "{107A4E9F-39C9-4501-9323-73C9B62932EF}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinDivertNet", "WinDivertNet\WinDivertNet.csproj", "{16805469-CDF3-4ED6-9E6C-70DDFB0DF8B3}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{9AB85EA6-D0E7-4EE5-9B2E-EA4B71585540}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9AB85EA6-D0E7-4EE5-9B2E-EA4B71585540}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9AB85EA6-D0E7-4EE5-9B2E-EA4B71585540}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9AB85EA6-D0E7-4EE5-9B2E-EA4B71585540}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2BBC748F-62F3-4DE3-A4F2-2D483062736C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2BBC748F-62F3-4DE3-A4F2-2D483062736C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2BBC748F-62F3-4DE3-A4F2-2D483062736C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2BBC748F-62F3-4DE3-A4F2-2D483062736C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{67448B94-E362-4531-AB18-E7E6D540B579}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{67448B94-E362-4531-AB18-E7E6D540B579}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{67448B94-E362-4531-AB18-E7E6D540B579}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{67448B94-E362-4531-AB18-E7E6D540B579}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{399AF9C6-701E-49B4-B165-FA9C7B7BFAF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{399AF9C6-701E-49B4-B165-FA9C7B7BFAF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{399AF9C6-701E-49B4-B165-FA9C7B7BFAF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{399AF9C6-701E-49B4-B165-FA9C7B7BFAF5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F16BB136-23AD-43F6-8D95-4C0D88FD8FBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F16BB136-23AD-43F6-8D95-4C0D88FD8FBA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F16BB136-23AD-43F6-8D95-4C0D88FD8FBA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F16BB136-23AD-43F6-8D95-4C0D88FD8FBA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B3F481C9-B351-449C-AE2B-CDEC29BD5DCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B3F481C9-B351-449C-AE2B-CDEC29BD5DCC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B3F481C9-B351-449C-AE2B-CDEC29BD5DCC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B3F481C9-B351-449C-AE2B-CDEC29BD5DCC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{293477FA-3C1C-429F-9FBA-AD16A3E402BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{293477FA-3C1C-429F-9FBA-AD16A3E402BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{293477FA-3C1C-429F-9FBA-AD16A3E402BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{293477FA-3C1C-429F-9FBA-AD16A3E402BC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AEE55360-757E-4B63-9478-2F201B95266A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AEE55360-757E-4B63-9478-2F201B95266A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AEE55360-757E-4B63-9478-2F201B95266A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AEE55360-757E-4B63-9478-2F201B95266A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{10CCB5B1-4672-4166-8CFC-24A48FC86A39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{10CCB5B1-4672-4166-8CFC-24A48FC86A39}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{10CCB5B1-4672-4166-8CFC-24A48FC86A39}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{10CCB5B1-4672-4166-8CFC-24A48FC86A39}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{107A4E9F-39C9-4501-9323-73C9B62932EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{107A4E9F-39C9-4501-9323-73C9B62932EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{107A4E9F-39C9-4501-9323-73C9B62932EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{107A4E9F-39C9-4501-9323-73C9B62932EF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{16805469-CDF3-4ED6-9E6C-70DDFB0DF8B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{16805469-CDF3-4ED6-9E6C-70DDFB0DF8B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{16805469-CDF3-4ED6-9E6C-70DDFB0DF8B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{16805469-CDF3-4ED6-9E6C-70DDFB0DF8B3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,43 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApplication_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6412d4331611499aab4eb63809a2a83bf60910_003Fd6_003F8ef46f2c_003FApplication_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArray_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F211e6f3b24fa438a92f1815153647ce2c8f908_003Fc8_003F8670d075_003FArray_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABoundedChannelOptions_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6c1b38ac9eb24f67b802360dcd86fc07208b0_003Fd0_003Ff86ab164_003FBoundedChannelOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AChannel_00602_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6c1b38ac9eb24f67b802360dcd86fc07208b0_003F10_003F645700ec_003FChannel_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AConcurrentDictionary_00602_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F4c583240ebf1454eb7477e1b97ab637b43928_003F90_003F523b730a_003FConcurrentDictionary_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADefaultInterpolatedStringHandler_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F211e6f3b24fa438a92f1815153647ce2c8f908_003Fe4_003Fdb4307f5_003FDefaultInterpolatedStringHandler_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADictionary_00602_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F211e6f3b24fa438a92f1815153647ce2c8f908_003F38_003Faa4e6832_003FDictionary_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnumerable_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcca5cfb955e146648d91eb22ffe4627a84930_003Fae_003Fc940e3b4_003FEnumerable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AETWTraceEventSource_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F25b12134b1124ffbab73641db2e54e5b310830_003F01_003F4bae5ee3_003FETWTraceEventSource_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFileVersionInfo_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F7fad91e527c1434bb313ddbc78144605b8a0_003F3f_003F911bc64d_003FFileVersionInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFile_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F211e6f3b24fa438a92f1815153647ce2c8f908_003F71_003F40854420_003FFile_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIcon_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F946b37ae1f1648b7a334a81e4b6ce11c64330_003F21_003F67e31def_003FIcon_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIDisposable_002Ecs_002Fl_003AC_0021_003FUsers_003Fyllyo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5aeb6167299d4cfba30c86624130edbcc90938_003F41_003F0f283a8f_003FIDisposable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIPAddress_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5dc54dc133694e6ca1812e5e56e958df388a0_003F68_003F0bc1a452_003FIPAddress_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIWebSocketConnection_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa7d96aff8e344c1d9aa8042f229d493cae00_003Fc1_003Fb154f2eb_003FIWebSocketConnection_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKernelTraceEventParser_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F25b12134b1124ffbab73641db2e54e5b310830_003F24_003Fd19d02f2_003FKernelTraceEventParser_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<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_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_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>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AQueue_00601_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F211e6f3b24fa438a92f1815153647ce2c8f908_003Fa1_003F352e385c_003FQueue_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AQueue_00601_002Ecs_002Fl_003AC_0021_003FUsers_003Fyllyo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5aeb6167299d4cfba30c86624130edbcc90938_003F2b_003Fb4ef3890_003FQueue_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AReaderWriterLockSlim_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F211e6f3b24fa438a92f1815153647ce2c8f908_003Fb5_003Fa78f431c_003FReaderWriterLockSlim_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AReadOnlyCollectionBase_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F4c1fd115dbe5414db1b4da1de3e96c5819910_003F04_003Fcae9fe22_003FReadOnlyCollectionBase_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASemaphoreSlim_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F211e6f3b24fa438a92f1815153647ce2c8f908_003Fe9_003F4d4110fd_003FSemaphoreSlim_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AString_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F211e6f3b24fa438a92f1815153647ce2c8f908_003Ff1_003F7b5a67e3_003FString_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATcpIpConnectTraceData_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F25b12134b1124ffbab73641db2e54e5b310830_003F93_003Fd6e6c065_003FTcpIpConnectTraceData_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATcpIpSendTraceData_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F25b12134b1124ffbab73641db2e54e5b310830_003F28_003Fce7c9cfa_003FTcpIpSendTraceData_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATimer_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F211e6f3b24fa438a92f1815153647ce2c8f908_003Fa8_003F04476824_003FTimer_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATimer_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffe2ae574758f4da99bb3bfcba69a0a40b6928_003F7f_003Fb5cb1e21_003FTimer_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATraceEventSession_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F25b12134b1124ffbab73641db2e54e5b310830_003F4e_003F99d3e433_003FTraceEventSession_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATraceEvent_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F25b12134b1124ffbab73641db2e54e5b310830_003F4d_003F09175cdc_003FTraceEvent_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AWebView2_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8aca2f2c90e1454c825fc8b69c53a97e14268_003F06_003F22dc0e77_003FWebView2_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=f4a8c497_002D43fb_002D4de1_002D9ffa_002D84076d7db2c5/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" Name="Test1" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||
<TestAncestor>
|
||||
<TestId>NUnit3x::B3F481C9-B351-449C-AE2B-CDEC29BD5DCC::net8.0::Common.Test.Tests.Test1</TestId>
|
||||
<TestId>NUnit3x::B3F481C9-B351-449C-AE2B-CDEC29BD5DCC::net8.0::Common.Test.Logger.LogTest.Test1</TestId>
|
||||
<TestId>NUnit3x::107A4E9F-39C9-4501-9323-73C9B62932EF::net8.0::Utils.ETW.Test.Tests.Test1</TestId>
|
||||
</TestAncestor>
|
||||
</SessionState></s:String>
|
||||
</wpf:ResourceDictionary>
|
||||
@@ -0,0 +1,9 @@
|
||||
<Application x:Class="Shell.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">
|
||||
<Application.Resources>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -0,0 +1,128 @@
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Net.Http;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using Application.Utils;
|
||||
using Common;
|
||||
using Common.Logger;
|
||||
using Common.Net.WebSocketConn;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Shell;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : System.Windows.Application
|
||||
{
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
// 异常处理
|
||||
ExceptionCatch();
|
||||
// 初始化日志
|
||||
var configModelLogging = AppConfig.Instance.ConfigModel.Logging;
|
||||
Log.Initialize(configModelLogging);
|
||||
base.OnStartup(e);
|
||||
|
||||
// ✅ 启动你的服务(如 WebSocket、HTTP、端口监听等)
|
||||
StartMyServer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异常捕捉
|
||||
/// </summary>
|
||||
private void ExceptionCatch()
|
||||
{
|
||||
DispatcherUnhandledException += App_DispatcherUnhandledException;
|
||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||
// 注册全局异常处理
|
||||
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理异常
|
||||
/// </summary>
|
||||
/// <param name="exception"></param>
|
||||
private void HandleException(Exception? exception)
|
||||
{
|
||||
Log.Error("未捕获异常", exception);
|
||||
// 错误记录,错误处理
|
||||
if (exception != null)
|
||||
{
|
||||
// todo:2023年10月29日 恭喜你发现了BUG页面
|
||||
MessageBox.Show($"恭喜你发现了BUG,请联系管理员解决:{exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
|
||||
{
|
||||
Log.Error(e.Exception, "UI线程异常");
|
||||
HandleException(e.Exception);
|
||||
// 根据异常类型决定是否继续运行
|
||||
if (CanRecover(e.Exception))
|
||||
{
|
||||
e.Handled = true; // 标记为已处理,防止应用崩溃
|
||||
// ShowUserFriendlyMessage("操作失败,请重试");
|
||||
}
|
||||
else
|
||||
{
|
||||
// ShowCriticalErrorDialog(e.Exception);
|
||||
// 让应用正常关闭而不是崩溃
|
||||
}
|
||||
}
|
||||
|
||||
private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
|
||||
{
|
||||
foreach (var ex in e.Exception.InnerExceptions)
|
||||
{
|
||||
Log.Error(e.Exception, "UI线程异常");
|
||||
}
|
||||
|
||||
e.SetObserved(); // 标记异常已被观察,防止应用终止
|
||||
}
|
||||
|
||||
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
var exception = e.ExceptionObject as Exception;
|
||||
Log.Error(exception, "非UI线程异常");
|
||||
|
||||
if (e.IsTerminating)
|
||||
{
|
||||
// 应用即将终止,进行紧急清理
|
||||
// EmergencyCleanup();
|
||||
Log.Fatal("应用程序即将因未处理异常而终止");
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanRecover(Exception exception)
|
||||
{
|
||||
return exception switch
|
||||
{
|
||||
ArgumentException => true,
|
||||
InvalidOperationException => true,
|
||||
HttpRequestException => true,
|
||||
TimeoutException => true,
|
||||
OutOfMemoryException => false,
|
||||
StackOverflowException => false,
|
||||
AccessViolationException => false,
|
||||
_ => true
|
||||
};
|
||||
}
|
||||
|
||||
private void StartMyServer()
|
||||
{
|
||||
int port = SysHelper.GetAvailablePort();
|
||||
|
||||
AppConfig.Instance.WebSocketPort = port;
|
||||
|
||||
AppConfig.Instance.WebSocketPath = $"ws://127.0.0.1:{port}";
|
||||
Log.Information4Ctx($"服务启动在端口: {AppConfig.Instance.WebSocketPort}");
|
||||
Log.Information($"服务完整地址: {AppConfig.Instance.WebSocketPath}");
|
||||
// TODO 2025年5月28日 22点25分 :开启 Http 网络本地服务,提供网页访问
|
||||
|
||||
// 启动WebSocket服务器
|
||||
WebSocketManager.Instance.Start(AppConfig.Instance.WebSocketPath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
@@ -0,0 +1,13 @@
|
||||
<Window x:Class="Shell.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
Title="MainWindow" Height="700" Width="1300">
|
||||
<Grid>
|
||||
<views:WebPanel/>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Shell;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3240.44" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Application\Application.csproj" />
|
||||
<ProjectReference Include="..\Common.Net\Common.Net.csproj" />
|
||||
<ProjectReference Include="..\Data\Data.csproj" />
|
||||
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\Utils.ETW\Utils.ETW.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="WinDivert64.sys">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="WinDivert.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="appsettings.Development.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="appsettings.Production.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,35 @@
|
||||
namespace Shell.Utils;
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
public static class TaskbarHelper
|
||||
{
|
||||
[DllImport("shell32.dll", SetLastError = true)]
|
||||
static extern UInt32 SHAppBarMessage(UInt32 dwMessage, ref APPBARDATA pData);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct APPBARDATA
|
||||
{
|
||||
public UInt32 cbSize;
|
||||
public IntPtr hWnd;
|
||||
public UInt32 uCallbackMessage;
|
||||
public UInt32 uEdge;
|
||||
public RECT rc;
|
||||
public Int32 lParam;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct RECT
|
||||
{
|
||||
public int left, top, right, bottom;
|
||||
}
|
||||
|
||||
public static RECT GetTaskbarPosition()
|
||||
{
|
||||
APPBARDATA data = new APPBARDATA();
|
||||
data.cbSize = (UInt32)Marshal.SizeOf(typeof(APPBARDATA));
|
||||
SHAppBarMessage(0x00000005, ref data); // ABM_GETTASKBARPOS
|
||||
return data.rc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Window x:Class="Shell.Views.TaskbarWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
WindowStyle="None"
|
||||
AllowsTransparency="True"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Background="Transparent"
|
||||
mc:Ignorable="d"
|
||||
Topmost="True"
|
||||
ShowInTaskbar="False"
|
||||
Title="MainWindow"
|
||||
ResizeMode="NoResize"
|
||||
Width="100" Height="50">
|
||||
<Grid>
|
||||
<TextBlock Text="↑ 0.1 MB/s
↓ 1.8 MB/s"
|
||||
Foreground="White"
|
||||
FontSize="14"
|
||||
FontFamily="Consolas"/>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using Shell.Utils;
|
||||
|
||||
namespace Shell.Views;
|
||||
|
||||
public partial class TaskbarWindow : Window
|
||||
{
|
||||
public TaskbarWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
// 获取任务栏位置
|
||||
var rect = TaskbarHelper.GetTaskbarPosition();
|
||||
|
||||
// 将窗口贴在任务栏右下角
|
||||
this.Left = rect.right - this.Width;
|
||||
this.Top = rect.bottom - this.Height;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<UserControl
|
||||
x:Class="Shell.Views.WebPanel"
|
||||
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:Shell.Views"
|
||||
xmlns:webview2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="300"
|
||||
d:DesignWidth="300">
|
||||
<webview2:WebView2
|
||||
Name="webView" />
|
||||
</UserControl>
|
||||
@@ -0,0 +1,165 @@
|
||||
using System.Windows.Controls;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Windows;
|
||||
using Application.Utils;
|
||||
using Common;
|
||||
using Common.Logger;
|
||||
using Common.Net;
|
||||
using Common.Net.WebSocketConn;
|
||||
using Infrastructure.utils;
|
||||
using Utils.ETW;
|
||||
using Utils.ETW.Core;
|
||||
using WinDivertNet.WinDivertWrapper;
|
||||
|
||||
namespace Shell.Views;
|
||||
|
||||
public partial class WebPanel : UserControl
|
||||
{
|
||||
public WebPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Class1.Main2();
|
||||
|
||||
InitWebView();
|
||||
|
||||
// 注册管理 WebSocket 事件
|
||||
WebSocketManger();
|
||||
}
|
||||
|
||||
private void WebSocketManger()
|
||||
{
|
||||
var webSocketManager = WebSocketManager.Instance;
|
||||
|
||||
// 获取网络状态
|
||||
webSocketManager.RegisterHandler("etwNetworkManger", async (cmd, socket) =>
|
||||
{
|
||||
Console.WriteLine("开始测试");
|
||||
|
||||
// 创建监听实例
|
||||
var etwNetworkManger = new EtwNetworkManger();
|
||||
|
||||
// 设置 ETW 监听
|
||||
etwNetworkManger.SetupEtwHandlers();
|
||||
|
||||
// 开始监听
|
||||
etwNetworkManger.StartCapture();
|
||||
});
|
||||
}
|
||||
|
||||
private async void InitWebView()
|
||||
{
|
||||
// 初始化 WebView2
|
||||
await webView.EnsureCoreWebView2Async();
|
||||
|
||||
// 加载前端页面:可以是本地文件或服务器地址
|
||||
// 示例 1:加载本地 HTML
|
||||
// string htmlPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "frontend", "index.html");
|
||||
// webView.CoreWebView2.Navigate($"file:///{htmlPath.Replace("\\", "/")}");
|
||||
|
||||
// 示例 2:加载远程网页
|
||||
webView.CoreWebView2.Navigate("http://localhost:3000");
|
||||
|
||||
webView.CoreWebView2.WebMessageReceived += (sender, args) =>
|
||||
{
|
||||
string json = args.WebMessageAsJson;
|
||||
|
||||
// 反序列化为 C# 对象
|
||||
var msg = JsonSerializer.Deserialize<JsMessage>(json);
|
||||
|
||||
Log.Information(json);
|
||||
Log.Information(msg?.Channel);
|
||||
|
||||
switch (msg?.Channel)
|
||||
{
|
||||
case "showMessage":
|
||||
MessageBox.Show(msg.Payload?.Content ?? "无内容", msg.Payload?.Title ?? "提示");
|
||||
InitWebView1();
|
||||
break;
|
||||
case "GetNetInfo":
|
||||
NetUtils.GetNetInfo();
|
||||
break;
|
||||
case "GetProgramDiagnostics":
|
||||
var programDiagnostics = SysInfoUtils.GetProgramDiagnostics();
|
||||
Log.Information(programDiagnostics);
|
||||
break;
|
||||
case "InspectProcess":
|
||||
SysInfoUtils.InspectProcess(23192);
|
||||
break;
|
||||
case "GetAllTcpConnections":
|
||||
NetUtils.GetAllTcpConnections();
|
||||
break;
|
||||
case "PacketSnifferStart":
|
||||
PacketSniffer.Start();
|
||||
break;
|
||||
case "GetWebSocketPath":
|
||||
WebSocketPath();
|
||||
break;
|
||||
case "CloseWebSocket":
|
||||
CloseWebSocket();
|
||||
break;
|
||||
case "OpenWebSocket":
|
||||
OpenWebSocket();
|
||||
break;
|
||||
default:
|
||||
Log.Information("没有任何函数被触发...");
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void OpenWebSocket()
|
||||
{
|
||||
int port = SysHelper.GetAvailablePort();
|
||||
AppConfig.Instance.WebSocketPort = port;
|
||||
AppConfig.Instance.WebSocketPath = $"ws://127.0.0.1:{port}";
|
||||
WebSocketManager.Instance.Start(AppConfig.Instance.WebSocketPath);
|
||||
}
|
||||
|
||||
private void CloseWebSocket()
|
||||
{
|
||||
WebSocketManager.Instance.Stop();
|
||||
}
|
||||
|
||||
private async void InitWebView1()
|
||||
{
|
||||
await webView.ExecuteScriptAsync("""
|
||||
window.externalFunctions.__BRIDGE_LISTEN__('showMessage','来自 C# 的问候');
|
||||
""");
|
||||
}
|
||||
|
||||
private async void WebSocketPath()
|
||||
{
|
||||
var instanceWebSocketPath = AppConfig.Instance.WebSocketPath;
|
||||
Log.Information($"instanceWebSocketPath -> :{instanceWebSocketPath}");
|
||||
string result = await webView.ExecuteScriptAsync($"""
|
||||
window.externalFunctions.__BRIDGE_LISTEN__('ListenWebSocketPath','{instanceWebSocketPath}');
|
||||
""");
|
||||
string result1 = await webView.ExecuteScriptAsync($"""
|
||||
window.externalFunctions.__BRIDGE_LISTEN__('GetWebSocketPath','{instanceWebSocketPath}');
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
||||
public class JsMessage
|
||||
{
|
||||
[JsonPropertyName("channel")] public string Channel { get; set; }
|
||||
[JsonPropertyName("payload")] public JsData Payload { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Channel:{Channel}, Payload:{Payload}";
|
||||
}
|
||||
}
|
||||
|
||||
public class JsData
|
||||
{
|
||||
[JsonPropertyName("title")] public string Title { get; set; }
|
||||
[JsonPropertyName("content")] public string Content { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Title:{Title}, Content:{Content}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"AppSettings": {
|
||||
"MaxConnection": 321
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"AppSettings": {
|
||||
"MaxConnection": 123
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"AppSettings": {
|
||||
"AppName": "MyApp",
|
||||
"MaxConnection": 444,
|
||||
"Version": "0.0.1",
|
||||
"Logging": {
|
||||
"EnableConsole": true,
|
||||
"EnableFile": false,
|
||||
"LogPath": "logs",
|
||||
"MaxFileSizeMB": 1,
|
||||
"RetentionDays": 30,
|
||||
"CompressDaysThreshold": 3,
|
||||
"MinimumLevel": "Information",
|
||||
"OutputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] [{ThreadId}] {Message:lj}{NewLine}{Exception}",
|
||||
"UseJsonFormat": false,
|
||||
"CompressCheckIntervalHours": 24
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<Application x:Class="UI.Shared.Wpf.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Application.Resources>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace UI.Shared.Wpf;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : System.Windows.Application
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace UI.Shared.Wpf;
|
||||
|
||||
public static class GlobalExceptionHandler
|
||||
{
|
||||
public static void RegisterGlobalExceptionHandlers(System.Windows.Application app)
|
||||
{
|
||||
// UI线程异常处理
|
||||
app.DispatcherUnhandledException += (sender, e) =>
|
||||
{
|
||||
HandleException(e.Exception, "UI线程异常");
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
// 非UI线程异常处理
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
|
||||
{
|
||||
if (e.ExceptionObject is Exception ex)
|
||||
HandleException(ex, "非UI线程异常");
|
||||
};
|
||||
|
||||
// 任务未观察到的异常处理
|
||||
TaskScheduler.UnobservedTaskException += (sender, e) =>
|
||||
{
|
||||
HandleException(e.Exception, "任务未观察异常");
|
||||
e.SetObserved();
|
||||
};
|
||||
}
|
||||
|
||||
private static void HandleException(Exception ex, string context)
|
||||
{
|
||||
// TODO: 替换为你自己的日志系统或上报服务
|
||||
string message = $"【{context}】{ex.Message}\n{ex.StackTrace}";
|
||||
MessageBox.Show(message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
// 例如 LogHelper.LogError(ex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
| 模块 | 说明 |
|
||||
| ---------- | ------------------------------------------------------------------------------------- |
|
||||
| 🔥 全局异常处理 | `App.xaml.cs` 注册的 `DispatcherUnhandledException`、`AppDomain.UnhandledException` 等逻辑封装 |
|
||||
| 🔧 异常日志捕捉器 | 异常信息收集、弹窗提示、自动日志写入 |
|
||||
| 🧠 通用行为类 | 拖拽移动窗口、全屏控制、窗口热键等 |
|
||||
| 🎨 通用控件 | Loading 动画、消息弹窗、状态提示组件 |
|
||||
| 💡 通用转换器 | `BoolToVisibilityConverter`, `EnumToDescriptionConverter` |
|
||||
| 🌈 样式资源 | `Generic.xaml`, Theme, 通用 `DataTemplate` |
|
||||
| 🛠 辅助类 | `UIHelper`, `WindowHelper`, `DialogManager` |
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="App.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<XamlRuntime>Wpf</XamlRuntime>
|
||||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Utils.ETW.Test;
|
||||
|
||||
public class Tests
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test1()
|
||||
{
|
||||
Class1.Main();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
|
||||
<PackageReference Include="NUnit" Version="3.14.0"/>
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.9.0"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="NUnit.Framework"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Utils.ETW\Utils.ETW.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,119 @@
|
||||
using Microsoft.Diagnostics.Tracing.Parsers;
|
||||
using Microsoft.Diagnostics.Tracing.Session;
|
||||
|
||||
namespace Utils.ETW;
|
||||
|
||||
public static class Class1
|
||||
{
|
||||
public static void Main()
|
||||
{
|
||||
Console.WriteLine($"这是ue安徽委会aweifawejiofawoi");
|
||||
|
||||
// if (TraceEventSession.IsElevated() == true)
|
||||
// {
|
||||
// Console.WriteLine("请以管理员身份运行此程序!");
|
||||
// return;
|
||||
// }
|
||||
|
||||
using (var session = new TraceEventSession("MyNetSession"))
|
||||
{
|
||||
// 启用网络内核事件(包括 TCP/UDP)
|
||||
session.EnableKernelProvider(KernelTraceEventParser.Keywords.NetworkTCPIP);
|
||||
|
||||
session.Source.Kernel.TcpIpRecv += data =>
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[RECV] PID: {data.ProcessID} {data.saddr}:{data.sport} → {data.daddr}:{data.dport} {data.size} bytes");
|
||||
};
|
||||
|
||||
session.Source.Kernel.TcpIpSend += data =>
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[SEND] PID: {data.ProcessID} {data.saddr}:{data.sport} → {data.daddr}:{data.dport} {data.size} bytes");
|
||||
};
|
||||
|
||||
Console.WriteLine("监听中... 按 Ctrl+C 退出");
|
||||
session.Source.Process(); // 开始监听事件(阻塞)
|
||||
}
|
||||
}
|
||||
|
||||
public static void Main2()
|
||||
{
|
||||
// Create real-time user session
|
||||
using (var session = new TraceEventSession("MyKernelSession"))
|
||||
{
|
||||
// Enable Kernel Network events
|
||||
session.EnableKernelProvider(
|
||||
KernelTraceEventParser.Keywords.NetworkTCPIP |
|
||||
KernelTraceEventParser.Keywords.Process |
|
||||
KernelTraceEventParser.Keywords.ImageLoad);
|
||||
|
||||
// Hook into real-time events
|
||||
session.Source.Kernel.TcpIpRecv += data =>
|
||||
{
|
||||
Console.WriteLine(
|
||||
$">>> TCP RECV: {data.ProcessID} -> {data.daddr}:{data.dport} [{data.size} bytes]");
|
||||
};
|
||||
|
||||
session.Source.Kernel.TcpIpSend += data =>
|
||||
{
|
||||
Console.WriteLine(
|
||||
$">>> TCP SEND: {data.ProcessID} -> {data.daddr}:{data.dport} [{data.size} bytes]");
|
||||
};
|
||||
|
||||
session.Source.Kernel.UdpIpRecv += data =>
|
||||
{
|
||||
Console.WriteLine(
|
||||
$">>> UDP RECV: {data.ProcessID} -> {data.daddr}:{data.dport} [{data.size} bytes]");
|
||||
};
|
||||
|
||||
session.Source.Kernel.UdpIpSend += data =>
|
||||
{
|
||||
Console.WriteLine(
|
||||
$">>> UDP SEND: {data.ProcessID} -> {data.daddr}:{data.dport} [{data.size} bytes]");
|
||||
};
|
||||
|
||||
/*
|
||||
* 当 新进程启动 时触发。
|
||||
🔧 事件信息:
|
||||
data.ProcessID:启动的进程 ID。
|
||||
data.ImageFileName:可执行文件的名称(例如 chrome.exe)。
|
||||
data.CommandLine:启动进程时使用的完整命令行参数。
|
||||
*/
|
||||
session.Source.Kernel.ProcessStart += data =>
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[PROCESS START] PID={data.ProcessID} Name={data.ImageFileName} Cmd={data.CommandLine}");
|
||||
};
|
||||
/*
|
||||
* 当 进程结束 时触发。
|
||||
🔧 事件信息:
|
||||
data.ProcessID:终止的进程 ID。
|
||||
data.ImageFileName:进程对应的可执行文件名。
|
||||
✅ 应用场景:
|
||||
用于记录进程的退出事件,可以用来做进程生命周期分析。
|
||||
*/
|
||||
session.Source.Kernel.ProcessStop += data =>
|
||||
{
|
||||
Console.WriteLine($"[PROCESS STOP] PID={data.ProcessID} Name={data.ImageFileName}");
|
||||
};
|
||||
|
||||
/*
|
||||
当某个模块(例如 DLL 或 EXE)被加载到某个进程时触发。
|
||||
🔧 事件信息:
|
||||
data.ProcessID:加载模块的进程 ID。
|
||||
data.FileName:被加载的模块路径(例如 C:\Windows\System32\kernel32.dll)。
|
||||
✅ 应用场景:
|
||||
可以监控某些敏感模块是否被加载,如某些反调试或注入行为。
|
||||
*/
|
||||
session.Source.Kernel.ImageLoad += data =>
|
||||
{
|
||||
Console.WriteLine($"[MODULE LOAD] PID={data.ProcessID} Image={data.FileName}");
|
||||
};
|
||||
|
||||
Console.CancelKeyPress += (o, e) => session.Dispose();
|
||||
|
||||
session.Source.Process();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Utils.ETW.EtwTracker;
|
||||
|
||||
namespace Utils.ETW.Core;
|
||||
|
||||
public class DnsTracker : INetTracker
|
||||
{
|
||||
public void SetupEtwHandlers(EtwNetworkCapture networkCapture)
|
||||
{
|
||||
networkCapture.OnDnsEvent += dnsEvent =>
|
||||
{
|
||||
|
||||
Console.WriteLine($"[DNS] 查询: {dnsEvent.QueryName} ({dnsEvent.QueryType})");
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,463 @@
|
||||
using Microsoft.Diagnostics.Tracing;
|
||||
using Microsoft.Diagnostics.Tracing.Parsers;
|
||||
using Microsoft.Diagnostics.Tracing.Session;
|
||||
using Utils.ETW.Models;
|
||||
|
||||
namespace Utils.ETW.Core;
|
||||
|
||||
|
||||
// ETW网络事件处理器
|
||||
public class EtwNetworkCapture(string sessionName = "NetworkETWSession") : IDisposable
|
||||
{
|
||||
private readonly TraceEventSession _session = new(sessionName);
|
||||
private readonly ETWTraceEventSource _source = new(sessionName, TraceEventSourceType.Session);
|
||||
|
||||
/// <summary>
|
||||
/// 是否正在监听
|
||||
/// </summary>
|
||||
private bool _isCapturing = false;
|
||||
|
||||
// 事件处理委托
|
||||
public delegate void NetworkEventHandler(NetworkEventData eventData);
|
||||
|
||||
public delegate void TcpConnectionEventHandler(TcpConnectionEventData eventData);
|
||||
|
||||
public delegate void UdpPacketEventHandler(UdpPacketEventData eventData);
|
||||
|
||||
public delegate void DnsEventHandler(DnsEventData eventData);
|
||||
|
||||
public delegate void HttpEventHandler(HttpEventData eventData);
|
||||
|
||||
public delegate void NetworkInterfaceEventHandler(NetworkInterfaceEventData eventData);
|
||||
|
||||
public delegate void NetworkErrorEventHandler(NetworkErrorEventData eventData);
|
||||
|
||||
// 事件处理器
|
||||
|
||||
/// <summary>
|
||||
/// 通用网络事件处理,所有的网络事件都会进入该函数
|
||||
/// </summary>
|
||||
public event NetworkEventHandler? OnNetworkEvent;
|
||||
public event TcpConnectionEventHandler? OnTcpConnectionEvent;
|
||||
public event UdpPacketEventHandler? OnUdpPacketEvent;
|
||||
public event DnsEventHandler? OnDnsEvent;
|
||||
public event HttpEventHandler? OnHttpEvent;
|
||||
public event NetworkInterfaceEventHandler? OnNetworkInterfaceEvent;
|
||||
public event NetworkErrorEventHandler? OnNetworkErrorEvent;
|
||||
|
||||
/// <summary>
|
||||
/// 开始监听
|
||||
/// </summary>
|
||||
public void StartCapture()
|
||||
{
|
||||
if (_isCapturing) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 启用网络相关的ETW提供程序
|
||||
EnableNetworkProviders();
|
||||
|
||||
// 设置事件处理器
|
||||
SetupEventHandlers();
|
||||
|
||||
_isCapturing = true;
|
||||
|
||||
// 开始处理事件(这是阻塞调用)
|
||||
Task.Run(() => { _source.Process(); });
|
||||
|
||||
Console.WriteLine("ETW网络捕获已启动...");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"启动ETW捕获失败: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置监听的 Providers
|
||||
/// </summary>
|
||||
private void EnableNetworkProviders()
|
||||
{
|
||||
// 启用内核网络事件
|
||||
_session.EnableKernelProvider(
|
||||
KernelTraceEventParser.Keywords.NetworkTCPIP
|
||||
);
|
||||
|
||||
|
||||
// 启用TCP/IP提供程序
|
||||
_session.EnableProvider("Microsoft-Windows-TCPIP",
|
||||
TraceEventLevel.Informational);
|
||||
|
||||
// 启用Winsock AFD提供程序
|
||||
/*
|
||||
*哪个进程创建了 socket
|
||||
每个 socket 的本地/远程地址、端口
|
||||
Socket 错误、阻塞、超时
|
||||
发送/接收的字节数
|
||||
使用了什么协议(TCP/UDP)
|
||||
*
|
||||
*/
|
||||
_session.EnableProvider("Microsoft-Windows-Winsock-AFD",
|
||||
TraceEventLevel.Informational);
|
||||
|
||||
// 启用DNS客户端提供程序
|
||||
_session.EnableProvider("Microsoft-Windows-DNS-Client",
|
||||
TraceEventLevel.Informational);
|
||||
|
||||
// 启用HTTP服务提供程序
|
||||
_session.EnableProvider("Microsoft-Windows-HttpService",
|
||||
TraceEventLevel.Informational);
|
||||
|
||||
// 启用网络配置文件提供程序
|
||||
_session.EnableProvider("Microsoft-Windows-NetworkProfile",
|
||||
TraceEventLevel.Informational);
|
||||
|
||||
// 监控 DNS 查询行为
|
||||
_session.EnableProvider("Microsoft-Windows-DNS-Client",
|
||||
TraceEventLevel.Informational);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置监听事件
|
||||
/// </summary>
|
||||
private void SetupEventHandlers()
|
||||
{
|
||||
var parser = new KernelTraceEventParser(_source);
|
||||
|
||||
// TCP连接事件
|
||||
parser.TcpIpConnect += data =>
|
||||
{
|
||||
var tcpEvent = new TcpConnectionEventData
|
||||
{
|
||||
Timestamp = data.TimeStamp,
|
||||
EventType = "TcpConnect",
|
||||
ProcessId = data.ProcessID,
|
||||
ThreadId = data.ThreadID,
|
||||
ProcessName = data.ProcessName,
|
||||
SourceIp = data.saddr,
|
||||
DestinationIp = data.daddr,
|
||||
SourcePort = data.sport,
|
||||
DestinationPort = data.dport,
|
||||
Protocol = "TCP",
|
||||
ConnectionState = "ESTABLISHED",
|
||||
Direction = TrafficDirection.Outbound
|
||||
};
|
||||
|
||||
OnTcpConnectionEvent?.Invoke(tcpEvent);
|
||||
OnNetworkEvent?.Invoke(tcpEvent);
|
||||
};
|
||||
|
||||
// TCP断开连接事件
|
||||
parser.TcpIpDisconnect += data =>
|
||||
{
|
||||
var tcpEvent = new TcpConnectionEventData
|
||||
{
|
||||
Timestamp = data.TimeStamp,
|
||||
EventType = "TcpDisconnect",
|
||||
ProcessId = data.ProcessID,
|
||||
ThreadId = data.ThreadID,
|
||||
ProcessName = data.ProcessName,
|
||||
SourceIp = data.saddr,
|
||||
DestinationIp = data.daddr,
|
||||
SourcePort = data.sport,
|
||||
DestinationPort = data.dport,
|
||||
Protocol = "TCP",
|
||||
ConnectionState = "CLOSED",
|
||||
Direction = TrafficDirection.Outbound
|
||||
};
|
||||
|
||||
OnTcpConnectionEvent?.Invoke(tcpEvent);
|
||||
OnNetworkEvent?.Invoke(tcpEvent);
|
||||
};
|
||||
|
||||
// TCP数据发送事件
|
||||
parser.TcpIpSend += data =>
|
||||
{
|
||||
var tcpEvent = new TcpConnectionEventData
|
||||
{
|
||||
Timestamp = data.TimeStamp,
|
||||
EventType = "TcpSend",
|
||||
ProcessId = data.ProcessID,
|
||||
ThreadId = data.ThreadID,
|
||||
ProcessName = data.ProcessName,
|
||||
SourceIp = data.saddr,
|
||||
DestinationIp = data.daddr,
|
||||
SourcePort = data.sport,
|
||||
DestinationPort = data.dport,
|
||||
Protocol = "TCP",
|
||||
DataLength = data.size,
|
||||
SequenceNumber = data.seqnum,
|
||||
Direction = TrafficDirection.Outbound
|
||||
};
|
||||
|
||||
OnTcpConnectionEvent?.Invoke(tcpEvent);
|
||||
OnNetworkEvent?.Invoke(tcpEvent);
|
||||
};
|
||||
|
||||
// TCP数据接收事件
|
||||
parser.TcpIpRecv += data =>
|
||||
{
|
||||
var tcpEvent = new TcpConnectionEventData
|
||||
{
|
||||
Timestamp = data.TimeStamp,
|
||||
EventType = "TcpReceive",
|
||||
ProcessId = data.ProcessID,
|
||||
ThreadId = data.ThreadID,
|
||||
ProcessName = data.ProcessName,
|
||||
SourceIp = data.saddr,
|
||||
DestinationIp = data.daddr,
|
||||
SourcePort = data.sport,
|
||||
DestinationPort = data.dport,
|
||||
Protocol = "TCP",
|
||||
DataLength = data.size,
|
||||
SequenceNumber = data.seqnum,
|
||||
Direction = TrafficDirection.Inbound
|
||||
};
|
||||
|
||||
OnTcpConnectionEvent?.Invoke(tcpEvent);
|
||||
OnNetworkEvent?.Invoke(tcpEvent);
|
||||
};
|
||||
|
||||
// UDP发送事件
|
||||
parser.UdpIpSend += data =>
|
||||
{
|
||||
var udpEvent = new UdpPacketEventData
|
||||
{
|
||||
Timestamp = data.TimeStamp,
|
||||
EventType = "UdpSend",
|
||||
ProcessId = data.ProcessID,
|
||||
ThreadId = data.ThreadID,
|
||||
ProcessName = data.ProcessName,
|
||||
SourceIp = data.saddr,
|
||||
DestinationIp = data.daddr,
|
||||
SourcePort = data.sport,
|
||||
DestinationPort = data.dport,
|
||||
Protocol = "UDP",
|
||||
DataLength = data.size,
|
||||
UdpLength = data.size,
|
||||
Direction = TrafficDirection.Outbound
|
||||
};
|
||||
|
||||
OnUdpPacketEvent?.Invoke(udpEvent);
|
||||
OnNetworkEvent?.Invoke(udpEvent);
|
||||
};
|
||||
|
||||
// UDP接收事件
|
||||
parser.UdpIpRecv += data =>
|
||||
{
|
||||
var udpEvent = new UdpPacketEventData
|
||||
{
|
||||
Timestamp = data.TimeStamp,
|
||||
EventType = "UdpReceive",
|
||||
ProcessId = data.ProcessID,
|
||||
ThreadId = data.ThreadID,
|
||||
ProcessName = data.ProcessName,
|
||||
SourceIp = data.saddr,
|
||||
DestinationIp = data.daddr,
|
||||
SourcePort = data.sport,
|
||||
DestinationPort = data.dport,
|
||||
Protocol = "UDP",
|
||||
DataLength = data.size,
|
||||
UdpLength = data.size,
|
||||
Direction = TrafficDirection.Inbound
|
||||
};
|
||||
|
||||
OnUdpPacketEvent?.Invoke(udpEvent);
|
||||
OnNetworkEvent?.Invoke(udpEvent);
|
||||
};
|
||||
|
||||
// 通用事件处理器,用于其他网络事件
|
||||
_source.Dynamic.All += data =>
|
||||
{
|
||||
// 处理DNS、HTTP等其他网络事件
|
||||
ProcessDynamicEvent(data);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通用事件处理器,用于其他网络事件
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
private void ProcessDynamicEvent(TraceEvent data)
|
||||
{
|
||||
// 根据提供程序名称和事件ID处理不同类型的网络事件
|
||||
switch (data.ProviderName)
|
||||
{
|
||||
case "Microsoft-Windows-DNS-Client":
|
||||
ProcessDnsEvent(data);
|
||||
break;
|
||||
case "Microsoft-Windows-HttpService":
|
||||
ProcessHttpEvent(data);
|
||||
break;
|
||||
case "Microsoft-Windows-NetworkProfile":
|
||||
ProcessNetworkInterfaceEvent(data);
|
||||
break;
|
||||
// 负责 TCP/IP 协议栈事件,如连接、发送、接收、断开、重传等
|
||||
// 用于监控 TCP 会话生命周期,是连接追踪的核心
|
||||
case "Microsoft-Windows-TCPIP":
|
||||
// AFD(Ancillary Function Driver for WinSock)是 Winsock API 的核心内核模块
|
||||
// 常用于检测哪一个进程创建了 socket,socket 和 TCP 会话的关联
|
||||
case "Microsoft-Windows-Winsock-AFD":
|
||||
// 也叫 Kernel-Network
|
||||
case "MSNT_SystemTrace":
|
||||
break;
|
||||
default:
|
||||
// 处理其他网络事件
|
||||
ProcessGenericNetworkEvent(data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessDnsEvent(TraceEvent data)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dnsEvent = new DnsEventData
|
||||
{
|
||||
Timestamp = data.TimeStamp,
|
||||
EventType = "DnsQuery",
|
||||
ProcessId = data.ProcessID,
|
||||
ThreadId = data.ThreadID,
|
||||
ProcessName = data.ProcessName,
|
||||
Protocol = "DNS"
|
||||
};
|
||||
|
||||
// 从事件数据中提取DNS特定信息
|
||||
if (data.PayloadNames.Contains("QueryName"))
|
||||
dnsEvent.QueryName = data.PayloadStringByName("QueryName");
|
||||
if (data.PayloadNames.Contains("QueryType"))
|
||||
dnsEvent.QueryType = data.PayloadStringByName("QueryType");
|
||||
|
||||
OnDnsEvent?.Invoke(dnsEvent);
|
||||
OnNetworkEvent?.Invoke(dnsEvent);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"处理DNS事件时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessHttpEvent(TraceEvent data)
|
||||
{
|
||||
try
|
||||
{
|
||||
var httpEvent = new HttpEventData
|
||||
{
|
||||
Timestamp = data.TimeStamp,
|
||||
EventType = "HttpRequest",
|
||||
ProcessId = data.ProcessID,
|
||||
ThreadId = data.ThreadID,
|
||||
ProcessName = data.ProcessName,
|
||||
Protocol = "HTTP"
|
||||
};
|
||||
|
||||
// 从事件数据中提取HTTP特定信息
|
||||
if (data.PayloadNames.Contains("Url"))
|
||||
httpEvent.Url = data.PayloadStringByName("Url");
|
||||
if (data.PayloadNames.Contains("Method"))
|
||||
httpEvent.HttpMethod = data.PayloadStringByName("Method");
|
||||
|
||||
OnHttpEvent?.Invoke(httpEvent);
|
||||
OnNetworkEvent?.Invoke(httpEvent);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"处理HTTP事件时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 网络配置发生变化
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
private void ProcessNetworkInterfaceEvent(TraceEvent data)
|
||||
{
|
||||
try
|
||||
{
|
||||
var interfaceEvent = new NetworkInterfaceEventData
|
||||
{
|
||||
Timestamp = data.TimeStamp,
|
||||
EventType = "NetworkInterface",
|
||||
ProcessId = data.ProcessID,
|
||||
ThreadId = data.ThreadID,
|
||||
ProcessName = data.ProcessName
|
||||
};
|
||||
|
||||
// 从事件数据中提取网络接口信息
|
||||
if (data.PayloadNames.Contains("InterfaceName"))
|
||||
interfaceEvent.InterfaceName = data.PayloadStringByName("InterfaceName");
|
||||
|
||||
OnNetworkInterfaceEvent?.Invoke(interfaceEvent);
|
||||
OnNetworkEvent?.Invoke(interfaceEvent);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"处理网络接口事件时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理其他网络事件
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
private void ProcessGenericNetworkEvent(TraceEvent data)
|
||||
{
|
||||
try
|
||||
{
|
||||
var networkEvent = new NetworkEventData
|
||||
{
|
||||
Timestamp = data.TimeStamp,
|
||||
EventType = data.EventName,
|
||||
ProcessId = data.ProcessID,
|
||||
ThreadId = data.ThreadID,
|
||||
ProcessName = data.ProcessName
|
||||
};
|
||||
Console.WriteLine($"data.PayloadName :=> {data.ProviderName}");
|
||||
|
||||
// 提取通用网络信息
|
||||
foreach (var payloadName in data.PayloadNames)
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = data.PayloadByName(payloadName);
|
||||
networkEvent.AdditionalProperties[payloadName] = value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略无法获取的字段
|
||||
}
|
||||
}
|
||||
|
||||
OnNetworkEvent?.Invoke(networkEvent);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"处理通用网络事件时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public void StopCapture()
|
||||
{
|
||||
if (!_isCapturing) return;
|
||||
|
||||
try
|
||||
{
|
||||
_isCapturing = false;
|
||||
_session.Stop();
|
||||
_source.Dispose();
|
||||
Console.WriteLine("ETW网络捕获已停止");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"停止ETW捕获时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StopCapture();
|
||||
_session.Dispose();
|
||||
_source.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Utils.ETW.EtwTracker;
|
||||
using Utils.ETW.Models;
|
||||
|
||||
namespace Utils.ETW.Core;
|
||||
|
||||
public class HttpTracker : INetTracker
|
||||
{
|
||||
public void SetupEtwHandlers(EtwNetworkCapture networkCapture)
|
||||
{
|
||||
networkCapture.OnHttpEvent += httpEvent =>
|
||||
{
|
||||
Console.WriteLine($"[HTTP] {httpEvent.HttpMethod} {httpEvent.Url}");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class NetworkProfileTracker : INetTracker
|
||||
{
|
||||
public void SetupEtwHandlers(EtwNetworkCapture networkCapture)
|
||||
{
|
||||
networkCapture.OnNetworkInterfaceEvent += interfaceEvent =>
|
||||
{
|
||||
Console.WriteLine($"[Interface] {interfaceEvent.InterfaceName}: {interfaceEvent.InterfaceState}");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class NetworkTracker : INetTracker
|
||||
{
|
||||
/// <summary>
|
||||
/// 捕获的事件量
|
||||
/// </summary>
|
||||
private List<NetworkEventData> capturedEvents = new List<NetworkEventData>();
|
||||
|
||||
public void SetupEtwHandlers(EtwNetworkCapture networkCapture)
|
||||
{
|
||||
networkCapture.OnNetworkEvent += networkEvent => { capturedEvents.Add(networkEvent); };
|
||||
}
|
||||
}
|
||||
|
||||
public class EtwNetworkManger
|
||||
{
|
||||
private UdpTracker udpTracker = new UdpTracker();
|
||||
private DnsTracker dnsTracker = new DnsTracker();
|
||||
private HttpTracker httpTracker = new HttpTracker();
|
||||
private NetworkProfileTracker networkProfileTracker = new NetworkProfileTracker();
|
||||
private NetworkTracker networkTracker = new NetworkTracker();
|
||||
private readonly TcpTracker _tcpTracker = new TcpTracker();
|
||||
|
||||
private EtwNetworkCapture networkCapture = new EtwNetworkCapture();
|
||||
|
||||
|
||||
private CancellationTokenSource _udpCleanupTokenSource = new();
|
||||
|
||||
/// <summary>
|
||||
/// 设置信息处理器
|
||||
/// </summary>
|
||||
public void SetupEtwHandlers()
|
||||
{
|
||||
// UDP
|
||||
// udpTracker.SetupEtwHandlers(networkCapture);
|
||||
// StartUdpSessionCleanupLoop();
|
||||
|
||||
// TCP
|
||||
// _tcpTracker.SetupEtwHandlers(networkCapture);
|
||||
|
||||
Console.WriteLine("进入了!!!!!!!!!!");
|
||||
// DNS
|
||||
dnsTracker.SetupEtwHandlers(networkCapture);
|
||||
|
||||
// httpTracker.SetupEtwHandlers(networkCapture);
|
||||
// networkProfileTracker.SetupEtwHandlers(networkCapture);
|
||||
// networkTracker.SetupEtwHandlers(networkCapture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始定时循环清理
|
||||
/// </summary>
|
||||
public void StartUdpSessionCleanupLoop()
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (!_udpCleanupTokenSource.Token.IsCancellationRequested)
|
||||
{
|
||||
udpTracker.CleanupExpiredUdpSessions();
|
||||
// 每10秒清理一次
|
||||
await Task.Delay(TimeSpan.FromSeconds(10), _udpCleanupTokenSource.Token);
|
||||
}
|
||||
}, _udpCleanupTokenSource.Token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止清理
|
||||
/// </summary>
|
||||
public void StopUdpSessionCleanupLoop()
|
||||
{
|
||||
_udpCleanupTokenSource.Cancel();
|
||||
}
|
||||
|
||||
public void StartCapture()
|
||||
{
|
||||
networkCapture.StartCapture();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
networkCapture.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using Utils.ETW.Models;
|
||||
|
||||
namespace Utils.ETW.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 网络信息缓存【全局 TCP、UDP 等连接信息存储】
|
||||
/// </summary>
|
||||
public sealed class NetworkCache
|
||||
{
|
||||
private static readonly Lazy<NetworkCache> _instance = new(() => new NetworkCache());
|
||||
|
||||
public static NetworkCache Instance => _instance.Value;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化字典大小 避免频繁扩容
|
||||
/// </summary>
|
||||
private static readonly int InitCapacity = 1_000;
|
||||
|
||||
// 私有构造函数,禁止外部创建实例
|
||||
private NetworkCache()
|
||||
{
|
||||
// 初始化缓存等操作
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 缓存字典
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, NetworkModel> _cache = new(InitCapacity);
|
||||
|
||||
// 设置缓存
|
||||
public void Set(string key, NetworkModel value)
|
||||
{
|
||||
lock (_cache)
|
||||
{
|
||||
_cache[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public NetworkModel? Get(string key)
|
||||
{
|
||||
lock (_cache)
|
||||
{
|
||||
return _cache.GetValueOrDefault(key);
|
||||
}
|
||||
}
|
||||
|
||||
// 移除缓存
|
||||
public void Remove(string key)
|
||||
{
|
||||
lock (_cache)
|
||||
{
|
||||
_cache.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using Utils.ETW.Models;
|
||||
|
||||
namespace Utils.ETW.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 网络信息管理【网络活动记录】
|
||||
/// </summary>
|
||||
public sealed class NetworkInfoManger
|
||||
{
|
||||
/// <summary>
|
||||
/// 网络日志
|
||||
/// </summary>
|
||||
Queue<string> networkEvents = new();
|
||||
|
||||
/// <summary>
|
||||
/// 最大日志记录条数
|
||||
/// </summary>
|
||||
public int NetworkEventsMaxSize = 1_000;
|
||||
|
||||
/// <summary>
|
||||
/// 活跃的 TCP 连接
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, NetworkModel> _activeTcpSessions = new();
|
||||
|
||||
private readonly ConcurrentDictionary<string, NetworkModel> _activeUdpSessions = new();
|
||||
|
||||
private static readonly Lazy<NetworkInfoManger> _instance = new(() => new NetworkInfoManger());
|
||||
|
||||
/// <summary>
|
||||
/// 端口流量记录
|
||||
/// </summary>
|
||||
public readonly Dictionary<int, ulong> PortTrafficSent = new();
|
||||
|
||||
public readonly Dictionary<int, ulong> PortTrafficReceived = new();
|
||||
|
||||
/// <summary>
|
||||
/// IP 流量记录
|
||||
/// </summary>
|
||||
public readonly Dictionary<IPAddress, ulong> SourceTrafficSent = new();
|
||||
|
||||
public readonly Dictionary<IPAddress, ulong> SourceTrafficReceived = new();
|
||||
|
||||
public static NetworkInfoManger Instance => _instance.Value;
|
||||
|
||||
private NetworkInfoManger()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
#region TCP 缓存信息
|
||||
|
||||
public void SetTcpCache(string key, NetworkModel value)
|
||||
{
|
||||
_activeTcpSessions[key] = value;
|
||||
}
|
||||
|
||||
public void SetTcpCache(NetworkModel value)
|
||||
{
|
||||
_activeTcpSessions[value.GetKey()] = value;
|
||||
}
|
||||
|
||||
public (bool tryRemove, NetworkModel? session) RemoveTcpCache(NetworkModel value)
|
||||
{
|
||||
return RemoveTcpCache(value.GetKey());
|
||||
}
|
||||
|
||||
public (bool tryRemove, NetworkModel? session) RemoveTcpCache(string str)
|
||||
{
|
||||
var tryRemove = _activeTcpSessions.TryRemove(str, out var session);
|
||||
return (tryRemove, session);
|
||||
}
|
||||
|
||||
public int GetTcpSize()
|
||||
=> _activeTcpSessions.Count;
|
||||
|
||||
#endregion
|
||||
|
||||
#region UDP 缓存信息
|
||||
|
||||
public void SetUdpCache(string key, NetworkModel value)
|
||||
{
|
||||
_activeUdpSessions[key] = value;
|
||||
}
|
||||
|
||||
public NetworkModel? GetUdpCache(NetworkModel value)
|
||||
{
|
||||
return GetUdpCache(value.GetKey());
|
||||
}
|
||||
|
||||
public NetworkModel[] GetAllUdpSessions()
|
||||
{
|
||||
return _activeUdpSessions.Values.ToArray();
|
||||
}
|
||||
|
||||
public void RemoveUdpSession(NetworkModel value)
|
||||
{
|
||||
_activeUdpSessions.TryRemove(value.GetKey(), out _);
|
||||
}
|
||||
|
||||
public NetworkModel? GetUdpCache(string key)
|
||||
{
|
||||
return _activeUdpSessions[key];
|
||||
}
|
||||
|
||||
public void SetUdpCache(NetworkModel value)
|
||||
{
|
||||
_activeUdpSessions[value.GetKey()] = value;
|
||||
}
|
||||
|
||||
public (bool tryRemove, NetworkModel? session) RemoveUdpCache(NetworkModel value)
|
||||
{
|
||||
return RemoveUdpCache(value.GetKey());
|
||||
}
|
||||
|
||||
public (bool tryRemove, NetworkModel? session) RemoveUdpCache(string str)
|
||||
{
|
||||
var tryRemove = _activeUdpSessions.TryRemove(str, out var session);
|
||||
return (tryRemove, session);
|
||||
}
|
||||
|
||||
public int GetUdpSize()
|
||||
=> _activeUdpSessions.Count;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 记录动作
|
||||
/// </summary>
|
||||
/// <param name="eventStr"></param>
|
||||
public void RecordEvent(string eventStr)
|
||||
{
|
||||
if (networkEvents.Count >= NetworkEventsMaxSize)
|
||||
networkEvents.Dequeue(); // 超出最大长度时,移除旧数据
|
||||
|
||||
networkEvents.Enqueue(eventStr);
|
||||
}
|
||||
|
||||
// todo: 2025年6月2日 22点00分 统计每个连接的时长、流量 (检测是否是僵尸进程【长连接,但低流量】);统计当前活跃连接
|
||||
// todo: 2025年6月2日 22点01分 按照 端口、IP 统计流量
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
using System.Net;
|
||||
using Utils.ETW.EtwTracker;
|
||||
using Utils.ETW.Models;
|
||||
|
||||
namespace Utils.ETW.Core;
|
||||
|
||||
public class UdpTracker : INetTracker
|
||||
{
|
||||
private Dictionary<int, int> portTraffic = new();
|
||||
private Dictionary<IPAddress, int> sourceTraffic = new();
|
||||
|
||||
public void SetupEtwHandlers(EtwNetworkCapture networkCapture)
|
||||
{
|
||||
networkCapture.OnUdpPacketEvent += (udpEvent) =>
|
||||
{
|
||||
Console.WriteLine($"[UDP] {udpEvent.EventType}: {udpEvent.SourceIp}:{udpEvent.SourcePort} -> " +
|
||||
$"{udpEvent.DestinationIp}:{udpEvent.DestinationPort} ({udpEvent.DataLength} bytes)");
|
||||
|
||||
switch (udpEvent.EventType)
|
||||
{
|
||||
// 数据发送
|
||||
case "UdpSend":
|
||||
UdpSend(udpEvent);
|
||||
break;
|
||||
// 数据接收
|
||||
case "UdpReceive":
|
||||
UdpReceive(udpEvent);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// 根据端口进行专门处理
|
||||
// switch (udpEvent.DestinationPort)
|
||||
// {
|
||||
// case 53: // DNS
|
||||
// ProcessDnsPacket(udpEvent);
|
||||
// break;
|
||||
// case 123: // NTP
|
||||
// ProcessNtpPacket(udpEvent);
|
||||
// break;
|
||||
// case 67:
|
||||
// case 68: // DHCP
|
||||
// ProcessDhcpPacket(udpEvent);
|
||||
// break;
|
||||
// default:
|
||||
// ProcessGenericUdpPacket(udpEvent);
|
||||
// break;
|
||||
// }
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据接收
|
||||
/// </summary>
|
||||
/// <param name="eventData"></param>
|
||||
private void UdpSend(UdpPacketEventData eventData)
|
||||
{
|
||||
var key = NetworkModel.GetKey(
|
||||
eventData.SourceIp,
|
||||
eventData.SourcePort,
|
||||
eventData.DestinationIp,
|
||||
eventData.DestinationPort,
|
||||
eventData.ProcessId,
|
||||
ProtocolType.UDP
|
||||
);
|
||||
|
||||
var existingSession = NetworkInfoManger.Instance.GetUdpCache(key);
|
||||
|
||||
var now = DateTime.Now;
|
||||
|
||||
if (existingSession == null)
|
||||
{
|
||||
// 第一次看到这个五元组,创建新会话
|
||||
var networkModel = new NetworkModel
|
||||
{
|
||||
ConnectType = ProtocolType.UDP,
|
||||
ProcessId = eventData.ProcessId,
|
||||
ThreadId = eventData.ThreadId,
|
||||
ProcessName = eventData.ProcessName,
|
||||
SourceIp = eventData.SourceIp,
|
||||
DestinationIp = eventData.DestinationIp,
|
||||
SourcePort = eventData.SourcePort,
|
||||
DestinationPort = eventData.DestinationPort,
|
||||
StartTime = now,
|
||||
LastSeenTime = now,
|
||||
BytesSent = 0,
|
||||
BytesReceived = 0,
|
||||
RecordTime = now,
|
||||
Direction = eventData.Direction,
|
||||
State = ConnectionState.Connecting,
|
||||
IsPartialConnection = true
|
||||
};
|
||||
|
||||
var session = new UdpPacketEventData
|
||||
{
|
||||
SourceIp = eventData.SourceIp,
|
||||
DestinationIp = eventData.DestinationIp,
|
||||
SourcePort = eventData.SourcePort,
|
||||
DestinationPort = eventData.DestinationPort,
|
||||
ProcessName = eventData.ProcessName
|
||||
};
|
||||
|
||||
NetworkInfoManger.Instance.SetUdpCache( networkModel);
|
||||
|
||||
string str = $"UDP 会话开始: {networkModel.SourceIp}:{networkModel.SourcePort} -> {networkModel.DestinationIp}:{networkModel.DestinationPort}";
|
||||
NetworkInfoManger.Instance.RecordEvent(str);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 已存在,更新会话状态
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据接收
|
||||
/// </summary>
|
||||
private void UdpReceive(UdpPacketEventData eventData)
|
||||
{
|
||||
var key = NetworkModel.GetKey(
|
||||
eventData.SourceIp,
|
||||
eventData.SourcePort,
|
||||
eventData.DestinationIp,
|
||||
eventData.DestinationPort,
|
||||
eventData.ProcessId,
|
||||
ProtocolType.UDP
|
||||
);
|
||||
|
||||
var existingSession = NetworkInfoManger.Instance.GetUdpCache(key);
|
||||
|
||||
var now = DateTime.Now;
|
||||
|
||||
if (existingSession == null)
|
||||
{
|
||||
// 第一次看到这个五元组,创建新会话
|
||||
var networkModel = new NetworkModel
|
||||
{
|
||||
ConnectType = ProtocolType.UDP,
|
||||
ProcessId = eventData.ProcessId,
|
||||
ThreadId = eventData.ThreadId,
|
||||
ProcessName = eventData.ProcessName,
|
||||
SourceIp = eventData.SourceIp,
|
||||
DestinationIp = eventData.DestinationIp,
|
||||
SourcePort = eventData.SourcePort,
|
||||
DestinationPort = eventData.DestinationPort,
|
||||
StartTime = now,
|
||||
LastSeenTime = now,
|
||||
BytesSent = 0,
|
||||
BytesReceived = 0,
|
||||
RecordTime = now,
|
||||
Direction = eventData.Direction,
|
||||
State = ConnectionState.Connecting,
|
||||
IsPartialConnection = true
|
||||
};
|
||||
|
||||
var session = new UdpPacketEventData
|
||||
{
|
||||
SourceIp = eventData.SourceIp,
|
||||
DestinationIp = eventData.DestinationIp,
|
||||
SourcePort = eventData.SourcePort,
|
||||
DestinationPort = eventData.DestinationPort,
|
||||
ProcessName = eventData.ProcessName
|
||||
};
|
||||
|
||||
NetworkInfoManger.Instance.SetUdpCache( networkModel);
|
||||
|
||||
string str = $"UDP 会话开始: {networkModel.SourceIp}:{networkModel.SourcePort} -> {networkModel.DestinationIp}:{networkModel.DestinationPort}";
|
||||
NetworkInfoManger.Instance.RecordEvent(str);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 已存在,更新会话状态
|
||||
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;
|
||||
|
||||
NetworkInfoManger.Instance.SourceTrafficReceived.TryAdd(eventData.SourceIp, 0);
|
||||
NetworkInfoManger.Instance.SourceTrafficReceived[eventData.SourceIp] += (ulong)eventData.DataLength;
|
||||
// UDP处理主要关注数据包本身,而不是连接状态
|
||||
UpdateTrafficStats(eventData);
|
||||
}
|
||||
|
||||
private void UpdateTrafficStats(UdpPacketEventData udpEvent)
|
||||
{
|
||||
// 统计端口流量
|
||||
portTraffic.TryAdd(udpEvent.DestinationPort, 0);
|
||||
portTraffic[udpEvent.DestinationPort] += udpEvent.DataLength;
|
||||
|
||||
// 统计源IP流量
|
||||
sourceTraffic.TryAdd(udpEvent.SourceIp, 0);
|
||||
sourceTraffic[udpEvent.SourceIp] += udpEvent.DataLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 因为 UDP 无断开标志,所以需要一个定期清理机制
|
||||
/// </summary>
|
||||
public void CleanupExpiredUdpSessions()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
var timeout = TimeSpan.FromSeconds(60); // 可调
|
||||
|
||||
foreach (var session in NetworkInfoManger.Instance.GetAllUdpSessions())
|
||||
{
|
||||
if (now - session.LastSeenTime > timeout)
|
||||
{
|
||||
NetworkInfoManger.Instance.RemoveUdpSession(session);
|
||||
|
||||
string str = $"UDP 会话结束: {session.SourceIp}:{session.SourcePort} -> {session.DestinationIp}:{session.DestinationPort}";
|
||||
NetworkInfoManger.Instance.RecordEvent(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ProcessDnsPacket(UdpPacketEventData udpEvent)
|
||||
{
|
||||
}
|
||||
|
||||
private void ProcessNtpPacket(UdpPacketEventData udpEvent)
|
||||
{
|
||||
}
|
||||
|
||||
private void ProcessDhcpPacket(UdpPacketEventData udpEvent)
|
||||
{
|
||||
Console.WriteLine($"[DHCP] DHCP通信: {udpEvent.SourceIp} <-> {udpEvent.DestinationIp}");
|
||||
}
|
||||
|
||||
private void ProcessGenericUdpPacket(UdpPacketEventData udpEvent)
|
||||
{
|
||||
// 对于未知UDP流量,主要进行安全检查
|
||||
if (udpEvent.DataLength > 1024 * 64) // 64KB
|
||||
{
|
||||
Console.WriteLine($"[UDP警告] 大型UDP数据包: {udpEvent.ProcessName} " +
|
||||
$"发送 {udpEvent.DataLength} 字节到 {udpEvent.DestinationIp}:{udpEvent.DestinationPort}");
|
||||
}
|
||||
}
|
||||
|
||||
public void PrintTrafficSummary()
|
||||
{
|
||||
Console.WriteLine("\n=== UDP流量统计 ===");
|
||||
Console.WriteLine("端口流量TOP 10:");
|
||||
foreach (var item in portTraffic.OrderByDescending(x => x.Value).Take(10))
|
||||
{
|
||||
Console.WriteLine($" 端口 {item.Key}: {item.Value / 1024}KB");
|
||||
}
|
||||
|
||||
Console.WriteLine("\n源IP流量TOP 10:");
|
||||
foreach (var item in sourceTraffic.OrderByDescending(x => x.Value).Take(10))
|
||||
{
|
||||
Console.WriteLine($" {item.Key}: {item.Value / 1024}KB");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Utils.ETW;
|
||||
|
||||
public enum Enums
|
||||
{
|
||||
TcpConnect,
|
||||
TcpDisconnect,
|
||||
TcpSend,
|
||||
TcpReceive,
|
||||
UdpSend,
|
||||
UdpReceive,
|
||||
DnsQuery,
|
||||
HttpRequest,
|
||||
NetworkInterface,
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Utils.ETW.Core;
|
||||
|
||||
namespace Utils.ETW.EtwTracker;
|
||||
|
||||
public interface INetTracker
|
||||
{
|
||||
/// <summary>
|
||||
/// 设置处理方式
|
||||
/// </summary>
|
||||
/// <param name="networkCapture">ETW 连接对象</param>
|
||||
void SetupEtwHandlers(EtwNetworkCapture networkCapture);
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using Common.Logger;
|
||||
using Utils.ETW.Core;
|
||||
using Utils.ETW.Models;
|
||||
|
||||
namespace Utils.ETW.EtwTracker;
|
||||
|
||||
/// <summary>
|
||||
/// TCP连接追踪器
|
||||
/// </summary>
|
||||
public class TcpTracker : INetTracker
|
||||
{
|
||||
/// <summary>
|
||||
/// 最大连接数
|
||||
/// </summary>
|
||||
private readonly int _maxConnections = 10_000;
|
||||
|
||||
/// <summary>
|
||||
/// 活跃会话
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<long, TcpConnectionSession> _activeSessions = new();
|
||||
|
||||
public void SetupEtwHandlers(EtwNetworkCapture networkCapture)
|
||||
{
|
||||
// TCP事件需要单独详细处理
|
||||
networkCapture.OnTcpConnectionEvent += tcpEvent =>
|
||||
{
|
||||
ProcessTcpEvent(tcpEvent);
|
||||
|
||||
// 额外的TCP特定检查
|
||||
CheckTcpSecurity(tcpEvent);
|
||||
MonitorTcpPerformance(tcpEvent);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安全性检查
|
||||
/// </summary>
|
||||
/// <param name="tcpEvent"></param>
|
||||
private void CheckTcpSecurity(TcpConnectionEventData tcpEvent)
|
||||
{
|
||||
// TCP安全检查
|
||||
if (tcpEvent.EventType == "TcpConnect")
|
||||
{
|
||||
// 检测端口扫描
|
||||
// 检测恶意外连
|
||||
// 检测异常连接模式
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 性能监视
|
||||
/// </summary>
|
||||
/// <param name="tcpEvent"></param>
|
||||
private void MonitorTcpPerformance(TcpConnectionEventData tcpEvent)
|
||||
{
|
||||
// TCP性能监控
|
||||
// 重传检测
|
||||
// 窗口大小分析
|
||||
// 连接建立延迟
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 不同连接状态事件
|
||||
/// </summary>
|
||||
/// <param name="tcpEvent"></param>
|
||||
public void ProcessTcpEvent(TcpConnectionEventData tcpEvent)
|
||||
{
|
||||
Console.WriteLine($"[TCP] {tcpEvent.EventType}: {tcpEvent.SourceIp}:{tcpEvent.SourcePort} -> " +
|
||||
$"{tcpEvent.DestinationIp}:{tcpEvent.DestinationPort} ({tcpEvent.ProcessName})");
|
||||
|
||||
switch (tcpEvent.EventType)
|
||||
{
|
||||
// 连接建立 - 需要记录连接开始时间
|
||||
case "TcpConnect":
|
||||
StartSession(tcpEvent);
|
||||
break;
|
||||
// 连接断开 - 计算连接持续时间,统计传输数据量
|
||||
case "TcpDisconnect":
|
||||
EndSession(tcpEvent);
|
||||
break;
|
||||
// 数据传输 - 累计传输字节数,检测异常流量
|
||||
case "TcpSend":
|
||||
UpdateSentData(tcpEvent);
|
||||
break;
|
||||
case "TcpReceive":
|
||||
UpdateReceivedData(tcpEvent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始监听
|
||||
/// </summary>
|
||||
/// <param name="tcpEvent"></param>
|
||||
private void StartSession(TcpConnectionEventData tcpEvent)
|
||||
{
|
||||
// 连接建立时间
|
||||
var lastSeenTime = DateTime.Now;
|
||||
|
||||
var networkModel = new NetworkModel
|
||||
{
|
||||
ConnectType = ProtocolType.TCP,
|
||||
ProcessId = tcpEvent.ProcessId,
|
||||
ThreadId = tcpEvent.ThreadId,
|
||||
ProcessName = tcpEvent.ProcessName,
|
||||
SourceIp = tcpEvent.SourceIp,
|
||||
DestinationIp = tcpEvent.DestinationIp,
|
||||
LastSeenTime = lastSeenTime,
|
||||
BytesSent = 0,
|
||||
BytesReceived = 0,
|
||||
State = ConnectionState.Connecting,
|
||||
SourcePort = tcpEvent.SourcePort,
|
||||
DestinationPort = tcpEvent.DestinationPort,
|
||||
StartTime = lastSeenTime,
|
||||
Direction = tcpEvent.Direction,
|
||||
RecordTime = lastSeenTime,
|
||||
IsPartialConnection = true,
|
||||
};
|
||||
|
||||
var session = new TcpConnectionSession
|
||||
{
|
||||
ConnectionId = tcpEvent.ConnectionId,
|
||||
StartTime = tcpEvent.Timestamp,
|
||||
SourceIp = tcpEvent.SourceIp,
|
||||
DestinationIp = tcpEvent.DestinationIp,
|
||||
SourcePort = tcpEvent.SourcePort,
|
||||
DestinationPort = tcpEvent.DestinationPort,
|
||||
ProcessName = tcpEvent.ProcessName
|
||||
};
|
||||
NetworkInfoManger.Instance.SetTcpCache(networkModel);
|
||||
|
||||
string str = $"连接建立: {networkModel.DestinationIp}:{networkModel.DestinationPort} ," +
|
||||
$"地址: {networkModel.SourceIp}:{networkModel.SourcePort}";
|
||||
NetworkInfoManger.Instance.RecordEvent(str);
|
||||
|
||||
// 业务逻辑处理
|
||||
AnalyzeNewConnection(session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结束会话
|
||||
/// </summary>
|
||||
/// <param name="tcpEvent"></param>
|
||||
private void EndSession(TcpConnectionEventData tcpEvent)
|
||||
{
|
||||
var key = NetworkModel.GetKey(
|
||||
tcpEvent.SourceIp,
|
||||
tcpEvent.SourcePort,
|
||||
tcpEvent.DestinationIp,
|
||||
tcpEvent.DestinationPort,
|
||||
tcpEvent.ProcessId,
|
||||
ProtocolType.TCP
|
||||
);
|
||||
|
||||
var removeTcpCache = NetworkInfoManger.Instance.RemoveTcpCache(key);
|
||||
|
||||
if (removeTcpCache.tryRemove)
|
||||
{
|
||||
if (removeTcpCache.session == null)
|
||||
{
|
||||
Log.Warning("移除成功,但获取数据为 Null !");
|
||||
return;
|
||||
}
|
||||
|
||||
removeTcpCache.session.EndTime = tcpEvent.Timestamp;
|
||||
removeTcpCache.session.State = ConnectionState.Disconnected;
|
||||
|
||||
string str = $"连接断开: {removeTcpCache.session.DestinationIp}:{removeTcpCache.session.DestinationPort} ," +
|
||||
$"地址: {removeTcpCache.session.SourceIp}:{removeTcpCache.session.SourcePort}";
|
||||
|
||||
NetworkInfoManger.Instance.RecordEvent(str);
|
||||
// 连接结束时的分析
|
||||
// AnalyzeCompletedConnection(session);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新发送的数据量
|
||||
/// </summary>
|
||||
/// <param name="tcpEvent"></param>
|
||||
private void UpdateSentData(TcpConnectionEventData tcpEvent)
|
||||
{
|
||||
var key = NetworkModel.GetKey(
|
||||
tcpEvent.SourceIp,
|
||||
tcpEvent.SourcePort,
|
||||
tcpEvent.DestinationIp,
|
||||
tcpEvent.DestinationPort,
|
||||
tcpEvent.ProcessId,
|
||||
ProtocolType.TCP
|
||||
);
|
||||
var (tryRemove, networkModel) = NetworkInfoManger.Instance.RemoveTcpCache(key);
|
||||
|
||||
if (tcpEvent.DataLength < 0)
|
||||
{
|
||||
throw new Exception("数据长度不可能为 0 !!");
|
||||
}
|
||||
|
||||
// 统计端口流量
|
||||
NetworkInfoManger.Instance.PortTrafficSent.TryAdd(tcpEvent.DestinationPort, 0);
|
||||
NetworkInfoManger.Instance.PortTrafficSent[tcpEvent.DestinationPort] += (ulong)tcpEvent.DataLength;
|
||||
|
||||
NetworkInfoManger.Instance.SourceTrafficSent.TryAdd(tcpEvent.SourceIp, 0);
|
||||
NetworkInfoManger.Instance.SourceTrafficSent[tcpEvent.SourceIp] += (ulong)tcpEvent.DataLength;
|
||||
|
||||
if (tryRemove)
|
||||
{
|
||||
if (networkModel == null)
|
||||
{
|
||||
Log.Warning("发送新数据, 但是数据获取为空 !");
|
||||
return;
|
||||
}
|
||||
|
||||
networkModel.BytesSent += tcpEvent.DataLength;
|
||||
networkModel.State = ConnectionState.Connected;
|
||||
networkModel.LastSeenTime = DateTime.Now;
|
||||
|
||||
// todo: 检测 1 分钟、10 分钟的数据传输量;统计 1 分钟、10 分钟平均速率
|
||||
|
||||
string str = $"发送: {networkModel.SourceIp}:{networkModel.SourcePort} {tcpEvent.DataLength} 字节";
|
||||
NetworkInfoManger.Instance.RecordEvent(str);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新接收的数据量
|
||||
/// </summary>
|
||||
/// <param name="tcpEvent"></param>
|
||||
private void UpdateReceivedData(TcpConnectionEventData tcpEvent)
|
||||
{
|
||||
var key = NetworkModel.GetKey(
|
||||
tcpEvent.SourceIp,
|
||||
tcpEvent.SourcePort,
|
||||
tcpEvent.DestinationIp,
|
||||
tcpEvent.DestinationPort,
|
||||
tcpEvent.ProcessId,
|
||||
ProtocolType.TCP
|
||||
);
|
||||
var (tryRemove, networkModel) = NetworkInfoManger.Instance.RemoveTcpCache(key);
|
||||
|
||||
if (tcpEvent.DataLength < 0)
|
||||
{
|
||||
throw new Exception("数据长度不可能为 0 !!");
|
||||
}
|
||||
|
||||
// 统计端口流量
|
||||
NetworkInfoManger.Instance.PortTrafficReceived.TryAdd(tcpEvent.DestinationPort, 0);
|
||||
NetworkInfoManger.Instance.PortTrafficReceived[tcpEvent.DestinationPort] += (ulong)tcpEvent.DataLength;
|
||||
|
||||
NetworkInfoManger.Instance.SourceTrafficReceived.TryAdd(tcpEvent.SourceIp, 0);
|
||||
NetworkInfoManger.Instance.SourceTrafficReceived[tcpEvent.SourceIp] += (ulong)tcpEvent.DataLength;
|
||||
|
||||
if (tryRemove)
|
||||
{
|
||||
if (networkModel == null)
|
||||
{
|
||||
Log.Warning("发送新数据, 但是数据获取为空 !");
|
||||
return;
|
||||
}
|
||||
|
||||
networkModel.BytesReceived += tcpEvent.DataLength;
|
||||
networkModel.State = ConnectionState.Connected;
|
||||
networkModel.LastSeenTime = DateTime.Now;
|
||||
|
||||
string str = $"接收: {networkModel.SourceIp}:{networkModel.SourcePort} {tcpEvent.DataLength} 字节";
|
||||
NetworkInfoManger.Instance.RecordEvent(str);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分析新连接
|
||||
/// </summary>
|
||||
/// <param name="session"></param>
|
||||
private void AnalyzeNewConnection(TcpConnectionSession session)
|
||||
{
|
||||
// 1. 安全检查
|
||||
// if (IsExternalConnection(session.DestinationIp))
|
||||
// {
|
||||
// Console.WriteLine(
|
||||
// $"[SECURITY] 外部连接警告: {session.ProcessName} -> {session.DestinationIp}:{session.DestinationPort}");
|
||||
// }
|
||||
|
||||
// 2. 业务监控
|
||||
// if (IsBusinessCriticalPort(session.DestinationPort))
|
||||
// {
|
||||
// Console.WriteLine($"[BUSINESS] 关键业务连接: {session.ProcessName} -> {session.DestinationPort}");
|
||||
// }
|
||||
|
||||
// 3. 性能监控
|
||||
var tcpSize = NetworkInfoManger.Instance.GetTcpSize();
|
||||
if (tcpSize > 1000)
|
||||
{
|
||||
Console.WriteLine($"[PERFORMANCE] 连接数量警告: 当前活跃连接 {tcpSize}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否是连接到外部
|
||||
/// </summary>
|
||||
/// <param name="ip"></param>
|
||||
/// <returns></returns>
|
||||
private bool IsExternalConnection(IPAddress ip)
|
||||
{
|
||||
// 简单检查是否为外部IP(实际应用中需要更复杂的逻辑)
|
||||
return !ip.ToString().StartsWith("192.168.") &&
|
||||
!ip.ToString().StartsWith("10.") &&
|
||||
!ip.ToString().StartsWith("172.") &&
|
||||
!IPAddress.IsLoopback(ip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否是业务端口
|
||||
/// </summary>
|
||||
/// <param name="port">要监测的端口号</param>
|
||||
/// <returns></returns>
|
||||
private bool IsBusinessCriticalPort(int port)
|
||||
{
|
||||
var businessPorts = new[]
|
||||
{ 80, 443, 3306, 5432, 1433, 6379 }; // HTTP, HTTPS, MySQL, PostgreSQL, SQL Server, Redis
|
||||
return businessPorts.Contains(port);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取活跃连接
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<TcpConnectionSession> GetActiveSessions()
|
||||
{
|
||||
return _activeSessions.Values.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取长连接的会话
|
||||
/// </summary>
|
||||
/// <param name="threshold">连接时长</param>
|
||||
/// <returns></returns>
|
||||
public List<TcpConnectionSession> GetLongRunningSessions(TimeSpan threshold)
|
||||
{
|
||||
return _activeSessions.Values
|
||||
.Where(s => s.Duration > threshold)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Utils.ETW;
|
||||
|
||||
/// <summary>
|
||||
/// 全局网络信息
|
||||
/// </summary>
|
||||
public sealed class GlobalNet
|
||||
{
|
||||
private static readonly Lazy<GlobalNet> _instance = new Lazy<GlobalNet>(() => new GlobalNet());
|
||||
|
||||
public static GlobalNet Instance => _instance.Value;
|
||||
|
||||
|
||||
|
||||
// 私有构造函数,防止外部实例化
|
||||
private GlobalNet()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
using System.Net;
|
||||
|
||||
namespace Utils.ETW.Models;
|
||||
|
||||
/// <summary>
|
||||
/// 网络事件公共数据结构定义
|
||||
/// </summary>
|
||||
public class NetworkEventData
|
||||
{
|
||||
/// <summary>
|
||||
/// 记录产生时间
|
||||
/// </summary>
|
||||
public DateTime Timestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 事件类型
|
||||
/// </summary>
|
||||
public string EventType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 进程 ID
|
||||
/// </summary>
|
||||
public int ProcessId { get; set; }
|
||||
|
||||
public int ThreadId { get; set; }
|
||||
public string ProcessName { get; set; }
|
||||
public IPAddress SourceIp { get; set; }
|
||||
public IPAddress DestinationIp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 源端口
|
||||
/// </summary>
|
||||
public int SourcePort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 目标端口
|
||||
/// </summary>
|
||||
public int DestinationPort { get; set; }
|
||||
|
||||
public string Protocol { get; set; }
|
||||
public int DataLength { get; set; }
|
||||
public TrafficDirection Direction { get; set; } // Inbound/Outbound
|
||||
public Dictionary<string, object> AdditionalProperties { get; set; }
|
||||
|
||||
public NetworkEventData()
|
||||
{
|
||||
AdditionalProperties = new Dictionary<string, object>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TCP 连接数据配置
|
||||
/// </summary>
|
||||
public class TcpConnectionSession : NetworkEventData
|
||||
{
|
||||
public long ConnectionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 开始时间
|
||||
/// </summary>
|
||||
public DateTime StartTime { get; set; }
|
||||
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 发送的字节数
|
||||
/// </summary>
|
||||
public long BytesSent { get; set; }
|
||||
|
||||
public long BytesReceived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 持续时间
|
||||
/// </summary>
|
||||
public TimeSpan Duration => (EndTime ?? DateTime.Now) - StartTime;
|
||||
|
||||
public List<string> Events { get; set; } = new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TCP连接事件数据
|
||||
/// </summary>
|
||||
public class TcpConnectionEventData : NetworkEventData
|
||||
{
|
||||
/// <summary>
|
||||
/// 连接状态
|
||||
/// </summary>
|
||||
public string ConnectionState { get; set; } // ESTABLISHED, CLOSED, LISTEN等
|
||||
public long ConnectionId { get; set; }
|
||||
/// <summary>
|
||||
/// 顺序
|
||||
/// </summary>
|
||||
public int SequenceNumber { get; set; }
|
||||
public int AcknowledgmentNumber { get; set; }
|
||||
public int WindowSize { get; set; }
|
||||
public string TcpFlags { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UDP数据包事件数据
|
||||
/// </summary>
|
||||
public class UdpPacketEventData : NetworkEventData
|
||||
{
|
||||
public int UdpLength { get; set; }
|
||||
public int Checksum { get; set; }
|
||||
}
|
||||
|
||||
// DNS查询事件数据
|
||||
public class DnsEventData : NetworkEventData
|
||||
{
|
||||
public string QueryName { get; set; }
|
||||
public string QueryType { get; set; }
|
||||
public string ResponseCode { get; set; }
|
||||
public List<string> ResolvedAddresses { get; set; }
|
||||
public int QueryId { get; set; }
|
||||
|
||||
public DnsEventData()
|
||||
{
|
||||
ResolvedAddresses = new List<string>();
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP事件数据
|
||||
public class HttpEventData : NetworkEventData
|
||||
{
|
||||
public string HttpMethod { get; set; }
|
||||
public string Url { get; set; }
|
||||
public int StatusCode { get; set; }
|
||||
public string UserAgent { get; set; }
|
||||
public string Host { get; set; }
|
||||
public Dictionary<string, string> Headers { get; set; }
|
||||
public long ContentLength { get; set; }
|
||||
|
||||
public HttpEventData()
|
||||
{
|
||||
Headers = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
|
||||
// 网络接口事件数据
|
||||
public class NetworkInterfaceEventData : NetworkEventData
|
||||
{
|
||||
public string InterfaceName { get; set; }
|
||||
public string InterfaceType { get; set; }
|
||||
public string MacAddress { get; set; }
|
||||
public string InterfaceState { get; set; } // Up/Down
|
||||
public long BytesSent { get; set; }
|
||||
public long BytesReceived { get; set; }
|
||||
public long PacketsSent { get; set; }
|
||||
public long PacketsReceived { get; set; }
|
||||
}
|
||||
|
||||
// 网络错误事件数据
|
||||
public class NetworkErrorEventData : NetworkEventData
|
||||
{
|
||||
public int ErrorCode { get; set; }
|
||||
public string ErrorDescription { get; set; }
|
||||
public string ErrorCategory { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
using System.Net;
|
||||
|
||||
namespace Utils.ETW.Models;
|
||||
|
||||
/// <summary>
|
||||
/// 网络连接模型
|
||||
/// </summary>
|
||||
public class NetworkModel
|
||||
{
|
||||
// RemoteCountry 地理位置归属地,供安全分析
|
||||
|
||||
/// <summary>
|
||||
/// 进程 ID
|
||||
/// </summary>
|
||||
public required int ProcessId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 线程 ID
|
||||
/// </summary>
|
||||
public required int ThreadId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 进程名称
|
||||
/// </summary>
|
||||
public required string ProcessName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 源地址
|
||||
/// </summary>
|
||||
public required IPAddress SourceIp { get; set; }
|
||||
|
||||
public required IPAddress DestinationIp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最新活动时间
|
||||
/// </summary>
|
||||
public required DateTime LastSeenTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 记录产生时间
|
||||
/// </summary>
|
||||
public required DateTime RecordTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 发送的数据量
|
||||
/// </summary>
|
||||
public long BytesSent { get; set; }
|
||||
|
||||
public long BytesReceived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 连接状态
|
||||
/// </summary>
|
||||
public required ConnectionState State { get; set; } // 如 ESTABLISHED, CLOSED, TIME_WAIT, 或 UDP/Unknown
|
||||
|
||||
/// <summary>
|
||||
/// 源端口
|
||||
/// </summary>
|
||||
public required int SourcePort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 目标端口
|
||||
/// </summary>
|
||||
public required int DestinationPort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 连接类型
|
||||
/// </summary>
|
||||
public required ProtocolType ConnectType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 开始时间
|
||||
/// </summary>
|
||||
public DateTime? StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 进站或是出站
|
||||
/// </summary>
|
||||
public TrafficDirection Direction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 结束时间
|
||||
/// </summary>
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否是完整会话【如果在软件打开前会话已经创建则不认为是完整会话】
|
||||
/// </summary>
|
||||
public required bool IsPartialConnection { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 会话持续时间
|
||||
/// </summary>
|
||||
public TimeSpan Duration => (EndTime ?? DateTime.Now) - (StartTime ?? RecordTime);
|
||||
|
||||
// 近 1 分钟平均网速
|
||||
// 近 1 分钟流量统计
|
||||
// 近 10 分钟流量统计
|
||||
|
||||
public string GetKey()
|
||||
{
|
||||
return GetKey(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取唯一 ID
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetKey(NetworkModel networkModel)
|
||||
{
|
||||
return GetKey(networkModel.SourceIp,
|
||||
networkModel.SourcePort,
|
||||
networkModel.DestinationIp,
|
||||
networkModel.DestinationPort,
|
||||
networkModel.ProcessId,
|
||||
networkModel.ConnectType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取唯一 ID
|
||||
/// </summary>
|
||||
/// <param name="sourceIp">源 Ip</param>
|
||||
/// <param name="sourcePort">源端口</param>
|
||||
/// <param name="destinationIp">目标 Ip</param>
|
||||
/// <param name="destinationPort">目标端口</param>
|
||||
/// <param name="processId">进程 ID</param>
|
||||
/// <param name="connectType">连接类型</param>
|
||||
/// <param name="recordTime">记录时间</param>
|
||||
/// <returns></returns>
|
||||
public static string GetKey(
|
||||
IPAddress sourceIp,
|
||||
int sourcePort,
|
||||
IPAddress destinationIp,
|
||||
int destinationPort,
|
||||
int processId,
|
||||
ProtocolType connectType)
|
||||
{
|
||||
var key =
|
||||
$"{sourceIp}:{sourcePort}-{destinationIp}:" +
|
||||
$"{destinationPort}-{processId}-{connectType}";
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
public enum ConnectionState
|
||||
{
|
||||
/// <summary>
|
||||
/// 正在尝试连接
|
||||
/// </summary>
|
||||
Connecting,
|
||||
Connected, // 当前连接活跃(发送/接收中)
|
||||
Disconnected, // 已断开或未活跃
|
||||
Listening, // 监听状态(如本地服务器)
|
||||
Unknown // 无法判断状态
|
||||
}
|
||||
|
||||
public enum ProtocolType
|
||||
{
|
||||
TCP,
|
||||
UDP,
|
||||
ICMP, // 可选
|
||||
Unknown
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 进出站类型
|
||||
/// </summary>
|
||||
public enum TrafficDirection
|
||||
{
|
||||
Inbound,
|
||||
Outbound,
|
||||
// Unknown
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.21" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Utils.ETW.Utils;
|
||||
|
||||
public static class SystemInfoHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 检查是否是管理员权限
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool IsRunAsAdministrator()
|
||||
{
|
||||
var identity = System.Security.Principal.WindowsIdentity.GetCurrent();
|
||||
var principal = new System.Security.Principal.WindowsPrincipal(identity);
|
||||
return principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System.Text;
|
||||
using Common.Logger;
|
||||
using WinDivertNet.WinDivertWrapper;
|
||||
|
||||
namespace WinDivertNet;
|
||||
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Log.Information("WinDivert 捕获启动...");
|
||||
PacketSniffer.Start();
|
||||
}
|
||||
|
||||
static void Main1(string[] args)
|
||||
{
|
||||
// 创建 WinDivert 控制器
|
||||
using (WinDivertController controller = new WinDivertController())
|
||||
{
|
||||
// 检查过滤器语法
|
||||
var checkResult = controller.CheckFilter("tcp.DstPort == 80");
|
||||
if (!checkResult.IsValid)
|
||||
{
|
||||
Log.Information($"过滤器语法错误: {checkResult.ErrorMessage}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 打开 WinDivert 句柄
|
||||
bool success = controller.Open(
|
||||
"tcp.DstPort == 80", // 过滤器
|
||||
WinDivert.WINDIVERT_LAYER_NETWORK, // 网络层
|
||||
0, // 优先级
|
||||
WinDivert.WINDIVERT_FLAG_SNIFF // 嗅探模式
|
||||
);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
Log.Information("打开 WinDivert 失败!");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Information("开始监听 HTTP 流量...");
|
||||
Log.Information("按 ESC 键退出");
|
||||
|
||||
// 设置队列长度
|
||||
controller.SetParam(WinDivert.WINDIVERT_PARAM_QUEUE_LENGTH, 8192);
|
||||
|
||||
byte[] packet = new byte[65535];
|
||||
WinDivert.WINDIVERT_ADDRESS addr = new WinDivert.WINDIVERT_ADDRESS();
|
||||
|
||||
// 创建线程处理数据包
|
||||
Thread thread = new Thread(() =>
|
||||
{
|
||||
while (controller.IsValid)
|
||||
{
|
||||
uint readLen = controller.Receive(packet, ref addr);
|
||||
if (readLen > 0)
|
||||
{
|
||||
// 解析数据包
|
||||
PacketHeaders headers = controller.ParsePacket(packet);
|
||||
if (headers.Success)
|
||||
{
|
||||
if (headers.HasTcpHeader && headers.HasIpHeader)
|
||||
{
|
||||
string srcIp = WinDivert.FormatIPv4Address(headers.IpHeader.SrcAddr);
|
||||
string dstIp = WinDivert.FormatIPv4Address(headers.IpHeader.DstAddr);
|
||||
ushort srcPort = headers.TcpHeader.SrcPort;
|
||||
ushort dstPort = headers.TcpHeader.DstPort;
|
||||
|
||||
Log.Information($"捕获到 TCP 数据包: {srcIp}:{srcPort} -> {dstIp}:{dstPort}");
|
||||
|
||||
// 如果有数据,则尝试输出前 100 个字节
|
||||
if (headers.Data != null && headers.DataLength > 0)
|
||||
{
|
||||
int len = (int)Math.Min(headers.DataLength, 100);
|
||||
string data = Encoding.ASCII.GetString(headers.Data, 0, len);
|
||||
Log.Information($"数据内容: {data}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
thread.IsBackground = true;
|
||||
thread.Start();
|
||||
|
||||
// 等待 ESC 键退出
|
||||
while (Console.ReadKey(true).Key != ConsoleKey.Escape)
|
||||
{
|
||||
}
|
||||
|
||||
// 关闭 WinDivert
|
||||
controller.Close();
|
||||
Log.Information("监听结束!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="WinDivert\WinDivert.dll" />
|
||||
<None Remove="WinDivert\WinDivert64.sys" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,229 @@
|
||||
using Common.Logger;
|
||||
|
||||
namespace WinDivertNet.WinDivertWrapper;
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
public static class PacketSniffer
|
||||
{
|
||||
public static void Start1()
|
||||
{
|
||||
Log.Information($"进入 12313 ");
|
||||
var handle = WinDivert.Open("true", WinDivert.WINDIVERT_LAYER_NETWORK, 0, WinDivert.WINDIVERT_FLAG_SNIFF);
|
||||
|
||||
byte[] buffer = new byte[65535];
|
||||
WinDivert.WINDIVERT_ADDRESS addr = default;
|
||||
uint readLen = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (WinDivert.Recv(handle, buffer, (uint)buffer.Length, ref addr, ref readLen))
|
||||
{
|
||||
ParsePacketAndLog(buffer, readLen, addr);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Information($"接收数据包时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
void ParsePacketAndLog(byte[] buffer, uint length, WinDivert.WINDIVERT_ADDRESS addr)
|
||||
{
|
||||
Log.Information($"length: {length}");
|
||||
if (buffer == null || length == 0)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// IPv4 数据包的最小长度为 20 字节
|
||||
if (length >= 20)
|
||||
{
|
||||
// 检查版本 (IPv4 = 4, 位于第一个字节的高 4 位)
|
||||
int version = (buffer[0] >> 4) & 0xF;
|
||||
|
||||
if (version == 4) // IPv4
|
||||
{
|
||||
int headerLength = (buffer[0] & 0xF) * 4; // IHL * 4 = 字节数
|
||||
byte protocol = buffer[9]; // 协议字段
|
||||
|
||||
// 源 IP 和目标 IP
|
||||
uint srcIp = (uint)((buffer[12]) | (buffer[13] << 8) | (buffer[14] << 16) | (buffer[15] << 24));
|
||||
uint dstIp = (uint)((buffer[16]) | (buffer[17] << 8) | (buffer[18] << 16) | (buffer[19] << 24));
|
||||
|
||||
string srcIpStr = $"{buffer[12]}.{buffer[13]}.{buffer[14]}.{buffer[15]}";
|
||||
string dstIpStr = $"{buffer[16]}.{buffer[17]}.{buffer[18]}.{buffer[19]}";
|
||||
|
||||
Log.Information($"IPv4: Src={srcIpStr}, Dst={dstIpStr}, Protocol={protocol}");
|
||||
|
||||
// 如果有足够的数据,解析 TCP 或 UDP
|
||||
if (length >= (uint)(headerLength + 4) && (protocol == 6 || protocol == 17))
|
||||
{
|
||||
// 获取端口 (TCP 和 UDP 的前 4 字节都是源端口和目标端口)
|
||||
int offset = headerLength;
|
||||
ushort srcPort = (ushort)((buffer[offset] << 8) | buffer[offset + 1]);
|
||||
ushort dstPort = (ushort)((buffer[offset + 2] << 8) | buffer[offset + 3]);
|
||||
|
||||
if (protocol == 6) // TCP
|
||||
{
|
||||
Log.Information($"TCP: SrcPort={srcPort}, DstPort={dstPort}");
|
||||
}
|
||||
else if (protocol == 17) // UDP
|
||||
{
|
||||
Log.Information($"UDP: SrcPort={srcPort}, DstPort={dstPort}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (version == 6 && length >= 40) // IPv6 (头部固定 40 字节)
|
||||
{
|
||||
// IPv6 解析代码...
|
||||
// 这里略过,需要时可以添加
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Information($"解析数据包时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Start()
|
||||
{
|
||||
Log.Information("WinDivert调试工具启动...");
|
||||
|
||||
// 检查是否以管理员身份运行
|
||||
bool isAdmin = new System.Security.Principal.WindowsPrincipal(
|
||||
System.Security.Principal.WindowsIdentity.GetCurrent()
|
||||
).IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
|
||||
|
||||
if (!isAdmin)
|
||||
{
|
||||
Log.Information("错误: 此程序需要管理员权限才能使用WinDivert!");
|
||||
Log.Information("请右键点击程序,选择'以管理员身份运行'然后重试。");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Information("步骤1: 尝试打开WinDivert...");
|
||||
|
||||
// 最简单的过滤器
|
||||
string filter = "true";
|
||||
IntPtr handle =
|
||||
WinDivert.WinDivertOpen(filter, WinDivert.WINDIVERT_LAYER_NETWORK, 0, WinDivert.WINDIVERT_FLAG_SNIFF);
|
||||
|
||||
// 检查句柄是否有效
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
int errorCode = Marshal.GetLastWin32Error();
|
||||
Log.Information($"错误: 无法打开WinDivert,错误码: {errorCode}");
|
||||
PrintWin32Error(errorCode);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Information("成功: WinDivert句柄已打开");
|
||||
|
||||
// 尝试接收一个数据包
|
||||
Log.Information("\n步骤2: 尝试接收数据包...");
|
||||
|
||||
// 为数据包分配缓冲区
|
||||
byte[] buffer = new byte[65535]; // 最大以太网帧大小
|
||||
|
||||
// 设置接收地址结构
|
||||
WinDivert.WINDIVERT_ADDRESS addr = new WinDivert.WINDIVERT_ADDRESS
|
||||
{
|
||||
Reserved = new byte[8] // 确保Reserved被初始化
|
||||
};
|
||||
|
||||
// 接收数据包
|
||||
uint readLen = 0;
|
||||
|
||||
// 尝试直接使用基本的Recv函数
|
||||
Log.Information("尝试WinDivertRecv...");
|
||||
bool recvResult = WinDivert.WinDivertRecv(handle, buffer, (uint)buffer.Length, ref addr, ref readLen);
|
||||
|
||||
if (!recvResult)
|
||||
{
|
||||
int errorCode = Marshal.GetLastWin32Error();
|
||||
Log.Information($"错误: WinDivertRecv失败,错误码: {errorCode}");
|
||||
PrintWin32Error(errorCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Information($"成功: 接收到数据包,大小: {readLen} 字节");
|
||||
|
||||
// 打印数据包的前16个字节
|
||||
string hexData = BitConverter.ToString(buffer, 0, Math.Min((int)readLen, 16));
|
||||
Log.Information($"数据包前16字节: {hexData}");
|
||||
}
|
||||
|
||||
// 重置并尝试使用Ex版本
|
||||
readLen = 0;
|
||||
addr = new WinDivert.WINDIVERT_ADDRESS { Reserved = new byte[8] };
|
||||
|
||||
Log.Information("\n尝试WinDivertRecvEx...");
|
||||
bool recvExResult =
|
||||
WinDivert.WinDivertRecvEx(handle, buffer, (uint)buffer.Length, 0, ref addr, ref readLen, IntPtr.Zero);
|
||||
|
||||
if (!recvExResult)
|
||||
{
|
||||
int errorCode = Marshal.GetLastWin32Error();
|
||||
Log.Information($"错误: WinDivertRecvEx失败,错误码: {errorCode}");
|
||||
PrintWin32Error(errorCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Information($"成功: 接收到数据包,大小: {readLen} 字节");
|
||||
|
||||
// 打印数据包的前16个字节
|
||||
string hexData = BitConverter.ToString(buffer, 0, Math.Min((int)readLen, 16));
|
||||
Log.Information($"数据包前16字节: {hexData}");
|
||||
}
|
||||
|
||||
// 最后关闭句柄
|
||||
WinDivert.WinDivertClose(handle);
|
||||
Log.Information("\nWinDivert句柄已关闭");
|
||||
|
||||
Log.Information("\n调试信息收集完成。请检查以上输出以确定问题所在。");
|
||||
}
|
||||
|
||||
// 辅助函数:打印Win32错误信息
|
||||
private static void PrintWin32Error(int errorCode)
|
||||
{
|
||||
switch (errorCode)
|
||||
{
|
||||
case 5:
|
||||
Log.Information("访问被拒绝。请确认程序以管理员权限运行。");
|
||||
break;
|
||||
case 87:
|
||||
Log.Information("参数错误。过滤器语法可能不正确。");
|
||||
break;
|
||||
case 123:
|
||||
Log.Information("文件名、目录名或卷标语法不正确。请检查WinDivert.dll是否位于正确的路径。");
|
||||
break;
|
||||
case 126:
|
||||
Log.Information("找不到指定的模块。无法找到WinDivert.dll文件。");
|
||||
break;
|
||||
case 193:
|
||||
Log.Information("不是有效的Win32应用程序。WinDivert.dll可能是错误的版本(32位/64位)。");
|
||||
break;
|
||||
case 997:
|
||||
Log.Information("正在进行重叠操作。WinDivert可能需要异步调用。");
|
||||
break;
|
||||
case 995:
|
||||
Log.Information("由于线程退出或应用程序请求,已中止I/O操作。");
|
||||
break;
|
||||
case 998:
|
||||
Log.Information("无效访问WinDivert。请检查是否有另一个进程正在使用相同的过滤器。");
|
||||
break;
|
||||
case 1450:
|
||||
Log.Information("系统资源不足,无法完成请求的服务。");
|
||||
break;
|
||||
default:
|
||||
Log.Information($"未知错误。Windows错误码: {errorCode}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,662 @@
|
||||
namespace WinDivertNet.WinDivertWrapper;
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// WinDivert 的 C# 封装类
|
||||
/// </summary>
|
||||
public static class WinDivert
|
||||
{
|
||||
#region 常量定义
|
||||
|
||||
// 层级常量
|
||||
public const uint WINDIVERT_LAYER_NETWORK = 0;
|
||||
public const uint WINDIVERT_LAYER_NETWORK_FORWARD = 1;
|
||||
public const uint WINDIVERT_LAYER_FLOW = 2;
|
||||
public const uint WINDIVERT_LAYER_SOCKET = 3;
|
||||
public const uint WINDIVERT_LAYER_REFLECT = 4;
|
||||
|
||||
// 标志常量
|
||||
public const ulong WINDIVERT_FLAG_SNIFF = 1;
|
||||
public const ulong WINDIVERT_FLAG_DROP = 2;
|
||||
public const ulong WINDIVERT_FLAG_RECV_ONLY = 4;
|
||||
public const ulong WINDIVERT_FLAG_SEND_ONLY = 8;
|
||||
public const ulong WINDIVERT_FLAG_NO_INSTALL = 16;
|
||||
public const ulong WINDIVERT_FLAG_FRAGMENTS = 32;
|
||||
public const ulong WINDIVERT_FLAG_RECV_PARTIAL = 64;
|
||||
public const ulong WINDIVERT_FLAG_SEND_PARTIAL = 128;
|
||||
|
||||
// 事件常量
|
||||
public const byte WINDIVERT_EVENT_NETWORK_PACKET = 0;
|
||||
public const byte WINDIVERT_EVENT_FLOW_ESTABLISHED = 1;
|
||||
public const byte WINDIVERT_EVENT_FLOW_DELETED = 2;
|
||||
public const byte WINDIVERT_EVENT_SOCKET_BIND = 3;
|
||||
public const byte WINDIVERT_EVENT_SOCKET_CONNECT = 4;
|
||||
public const byte WINDIVERT_EVENT_SOCKET_LISTEN = 5;
|
||||
public const byte WINDIVERT_EVENT_SOCKET_ACCEPT = 6;
|
||||
public const byte WINDIVERT_EVENT_REFLECT_OPEN = 7;
|
||||
public const byte WINDIVERT_EVENT_REFLECT_CLOSE = 8;
|
||||
|
||||
// 方向常量
|
||||
public const byte WINDIVERT_DIRECTION_OUTBOUND = 0;
|
||||
public const byte WINDIVERT_DIRECTION_INBOUND = 1;
|
||||
|
||||
// 参数常量
|
||||
public const int WINDIVERT_PARAM_QUEUE_LENGTH = 0;
|
||||
public const int WINDIVERT_PARAM_QUEUE_TIME = 1;
|
||||
public const int WINDIVERT_PARAM_QUEUE_SIZE = 2;
|
||||
public const int WINDIVERT_PARAM_VERSION_MAJOR = 3;
|
||||
public const int WINDIVERT_PARAM_VERSION_MINOR = 4;
|
||||
|
||||
// 校验和常量
|
||||
public const uint WINDIVERT_HELPER_NO_REPLACE = 0;
|
||||
public const uint WINDIVERT_HELPER_NO_ICMPV6 = 0;
|
||||
public const uint WINDIVERT_HELPER_NO_ICMP = 0;
|
||||
public const uint WINDIVERT_HELPER_NO_TCP = 0;
|
||||
public const uint WINDIVERT_HELPER_NO_UDP = 0;
|
||||
public const uint WINDIVERT_HELPER_NO_IP = 0;
|
||||
public const uint WINDIVERT_HELPER_NO_IPV6 = 0;
|
||||
|
||||
// 优先级范围
|
||||
public const short WINDIVERT_PRIORITY_HIGHEST = 30000;
|
||||
public const short WINDIVERT_PRIORITY_LOWEST = -30000;
|
||||
public const short WINDIVERT_PRIORITY_DEFAULT = 0;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 结构体定义
|
||||
|
||||
/// <summary>
|
||||
/// WinDivert 地址结构
|
||||
/// </summary>
|
||||
// [StructLayout(LayoutKind.Sequential)]
|
||||
// public struct WINDIVERT_ADDRESS
|
||||
// {
|
||||
// public long Timestamp; // 时间戳
|
||||
// public byte Layer; // 层级
|
||||
// public byte Event; // 事件
|
||||
// public byte Flags; // 标志
|
||||
// public byte Reserved1; // 保留
|
||||
// public uint IfIdx; // 接口索引
|
||||
// public uint SubIfIdx; // 子接口索引
|
||||
// public byte Direction; // 方向
|
||||
// public byte Reserved2; // 保留
|
||||
// public ulong Reserved3; // 保留
|
||||
// }
|
||||
// 确保结构体布局正确 - 使用StructLayout和FieldOffset
|
||||
// 完全匹配WinDivert 2.0版本的结构体定义
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct WINDIVERT_ADDRESS
|
||||
{
|
||||
public long Timestamp;
|
||||
public byte Layer;
|
||||
public byte Event;
|
||||
public ushort Flags;
|
||||
public uint IfIdx;
|
||||
public uint SubIfIdx;
|
||||
|
||||
// 注意:在WinDivert 2.0中,Reserved字段是64位(8字节)对齐的
|
||||
// 所以我们使用固定大小的字节数组
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
public byte[] Reserved;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// IPv4 头结构
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct WINDIVERT_IPHDR
|
||||
{
|
||||
public byte HdrLength4; // 组合字段: 版本 (4 bits) + 头长度 (4 bits)
|
||||
public byte TOS; // 服务类型
|
||||
public ushort Length; // 总长度
|
||||
public ushort Id; // 标识
|
||||
public ushort FragOff0; // 组合字段: 标志 (3 bits) + 分片偏移 (13 bits)
|
||||
public byte TTL; // 生存时间
|
||||
public byte Protocol; // 协议
|
||||
public ushort Checksum; // 检验和
|
||||
public uint SrcAddr; // 源地址
|
||||
public uint DstAddr; // 目标地址
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IPv6 头结构
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct WINDIVERT_IPV6HDR
|
||||
{
|
||||
public uint FlowLabel0; // 组合字段: 版本 (4 bits) + 通信类 (8 bits) + 流标签 (20 bits)
|
||||
public ushort Length; // 有效载荷长度
|
||||
public byte NextHdr; // 下一个头
|
||||
public byte HopLimit; // 跳数限制
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
||||
public byte[] SrcAddr; // 源地址
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
||||
public byte[] DstAddr; // 目标地址
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TCP 头结构
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct WINDIVERT_TCPHDR
|
||||
{
|
||||
public ushort SrcPort; // 源端口
|
||||
public ushort DstPort; // 目标端口
|
||||
public uint SeqNum; // 序列号
|
||||
public uint AckNum; // 确认号
|
||||
public ushort Reserved10; // 组合字段: 数据偏移 (4 bits) + 保留 (6 bits) + 标志 (6 bits)
|
||||
public ushort Window; // 窗口大小
|
||||
public ushort Checksum; // 检验和
|
||||
public ushort UrgPtr; // 紧急指针
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UDP 头结构
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct WINDIVERT_UDPHDR
|
||||
{
|
||||
public ushort SrcPort; // 源端口
|
||||
public ushort DstPort; // 目标端口
|
||||
public ushort Length; // 长度
|
||||
public ushort Checksum; // 检验和
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ICMP 头结构
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct WINDIVERT_ICMPHDR
|
||||
{
|
||||
public byte Type; // 类型
|
||||
public byte Code; // 代码
|
||||
public ushort Checksum; // 检验和
|
||||
public uint Body; // 消息体
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ICMPv6 头结构
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct WINDIVERT_ICMPV6HDR
|
||||
{
|
||||
public byte Type; // 类型
|
||||
public byte Code; // 代码
|
||||
public ushort Checksum; // 检验和
|
||||
public uint Body; // 消息体
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 原生 API 导入
|
||||
|
||||
[DllImport("WinDivert.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
|
||||
public static extern IntPtr WinDivertOpen(
|
||||
[MarshalAs(UnmanagedType.LPStr)] string filter,
|
||||
uint layer,
|
||||
short priority,
|
||||
ulong flags);
|
||||
|
||||
[DllImport("WinDivert.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
|
||||
public static extern bool WinDivertRecv(
|
||||
IntPtr handle,
|
||||
[Out] byte[] pPacket,
|
||||
uint packetLen,
|
||||
ref WINDIVERT_ADDRESS pAddr,
|
||||
ref uint readLen);
|
||||
|
||||
[DllImport("WinDivert.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
|
||||
public static extern bool WinDivertRecvEx(
|
||||
IntPtr handle,
|
||||
[Out] byte[] pPacket,
|
||||
uint packetLen,
|
||||
uint recvFlags,
|
||||
[In, Out] ref WINDIVERT_ADDRESS pAddr,
|
||||
[In, Out] ref uint readLen,
|
||||
IntPtr lpOverlapped);
|
||||
|
||||
[DllImport("WinDivert.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern bool WinDivertSend(
|
||||
IntPtr handle,
|
||||
byte[] pPacket,
|
||||
uint packetLen,
|
||||
ref WINDIVERT_ADDRESS pAddr,
|
||||
ref uint writeLen);
|
||||
|
||||
[DllImport("WinDivert.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern bool WinDivertSendEx(
|
||||
IntPtr handle,
|
||||
byte[] pPacket,
|
||||
uint packetLen,
|
||||
uint flags,
|
||||
ref WINDIVERT_ADDRESS pAddr,
|
||||
ref uint writeLen,
|
||||
IntPtr overlapped);
|
||||
|
||||
[DllImport("WinDivert.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
|
||||
public static extern bool WinDivertClose(
|
||||
IntPtr handle);
|
||||
|
||||
// 获取错误信息的帮助函数
|
||||
[DllImport("WinDivert.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern uint WinDivertHelperNtohs(ushort value);
|
||||
|
||||
// 获取WinDivert特定错误
|
||||
[DllImport("WinDivert.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern IntPtr WinDivertHelperFormatFilter(
|
||||
[MarshalAs(UnmanagedType.LPStr)] string filter,
|
||||
WinDivertFormatFlags formatFlags,
|
||||
IntPtr buffer,
|
||||
uint bufferLen);
|
||||
|
||||
// 用于格式化过滤器的标志
|
||||
[Flags]
|
||||
public enum WinDivertFormatFlags : uint
|
||||
{
|
||||
Normal = 0,
|
||||
NoNumbers = 1,
|
||||
Aliases = 2,
|
||||
}
|
||||
|
||||
[DllImport("WinDivert.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern bool WinDivertSetParam(
|
||||
IntPtr handle,
|
||||
int param,
|
||||
ulong value);
|
||||
|
||||
[DllImport("WinDivert.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern bool WinDivertGetParam(
|
||||
IntPtr handle,
|
||||
int param,
|
||||
ref ulong value);
|
||||
|
||||
[DllImport("WinDivert.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern uint WinDivertHelperCalcChecksums(
|
||||
byte[] pPacket,
|
||||
uint packetLen,
|
||||
ref WINDIVERT_ADDRESS pAddr,
|
||||
uint flags);
|
||||
|
||||
[DllImport("WinDivert.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern bool WinDivertHelperParsePacket(
|
||||
byte[] pPacket,
|
||||
uint packetLen,
|
||||
out IntPtr ppIpHdr,
|
||||
out IntPtr ppIpv6Hdr,
|
||||
out IntPtr ppIcmpHdr,
|
||||
out IntPtr ppIcmpv6Hdr,
|
||||
out IntPtr ppTcpHdr,
|
||||
out IntPtr ppUdpHdr,
|
||||
out IntPtr ppData,
|
||||
out ushort pDataLen
|
||||
);
|
||||
// 安全包装 - 处理异常大小问题
|
||||
public static bool SafeRecv(IntPtr handle, byte[] buffer, ref WINDIVERT_ADDRESS addr, out byte[] packet)
|
||||
{
|
||||
packet = null;
|
||||
|
||||
// 确保结构体正确初始化
|
||||
if (addr.Reserved == null)
|
||||
{
|
||||
addr.Reserved = new byte[8];
|
||||
}
|
||||
|
||||
uint readLen = 0;
|
||||
|
||||
// 使用基础函数调用
|
||||
bool result = WinDivertRecv(handle, buffer, (uint)buffer.Length, ref addr, ref readLen);
|
||||
|
||||
if (!result) return false;
|
||||
|
||||
// *** 关键修复:处理异常大小 ***
|
||||
// 通过数据包的第一个字节验证它是IPv4还是IPv6数据包
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
byte version = (byte)(buffer[0] >> 4);
|
||||
|
||||
// IPv4数据包版本是4,IPv6是6
|
||||
if (version == 4 || version == 6)
|
||||
{
|
||||
// 检查IP头部中的长度字段
|
||||
// 对于IPv4,长度在字节3-4中;对于IPv6,长度在字节5-6中
|
||||
ushort ipLength;
|
||||
|
||||
if (version == 4 && buffer.Length >= 4)
|
||||
{
|
||||
ipLength = (ushort)((buffer[2] << 8) | buffer[3]);
|
||||
|
||||
// IPv4报头最小20字节,最大65535字节
|
||||
if (ipLength >= 20 && ipLength <= 65535)
|
||||
{
|
||||
readLen = ipLength;
|
||||
}
|
||||
else if (buffer.Length >= 20) // 如果长度字段无效,至少读取IPv4头
|
||||
{
|
||||
readLen = 1500; // 使用标准以太网MTU大小
|
||||
}
|
||||
}
|
||||
else if (version == 6 && buffer.Length >= 6)
|
||||
{
|
||||
ipLength = (ushort)((buffer[4] << 8) | buffer[5]);
|
||||
|
||||
// IPv6报头固定40字节,加上有效载荷
|
||||
if (ipLength + 40 <= 65535)
|
||||
{
|
||||
readLen = (uint)(ipLength + 40);
|
||||
}
|
||||
else if (buffer.Length >= 40) // 如果长度字段无效,至少读取IPv6头
|
||||
{
|
||||
readLen = 1500; // 使用标准以太网MTU大小
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果仍然出现异常大小,强制使用安全值
|
||||
if (readLen > 9000 || readLen == 0)
|
||||
{
|
||||
// 设置为安全的默认值(标准以太网MTU)
|
||||
readLen = 1500;
|
||||
}
|
||||
}
|
||||
|
||||
// 只提取有效数据
|
||||
packet = new byte[readLen];
|
||||
Array.Copy(buffer, packet, readLen);
|
||||
return true;
|
||||
}
|
||||
[DllImport("WinDivert.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern bool WinDivertHelperParseIPv4Address(
|
||||
[MarshalAs(UnmanagedType.LPStr)] string str,
|
||||
ref uint pAddr);
|
||||
|
||||
[DllImport("WinDivert.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern bool WinDivertHelperParseIPv6Address(
|
||||
[MarshalAs(UnmanagedType.LPStr)] string str,
|
||||
byte[] pAddr);
|
||||
|
||||
[DllImport("WinDivert.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern bool WinDivertHelperFormatIPv4Address(
|
||||
uint addr,
|
||||
[MarshalAs(UnmanagedType.LPStr)] StringBuilder buffer,
|
||||
uint bufLen);
|
||||
|
||||
[DllImport("WinDivert.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern bool WinDivertHelperFormatIPv6Address(
|
||||
byte[] addr,
|
||||
[MarshalAs(UnmanagedType.LPStr)] StringBuilder buffer,
|
||||
uint bufLen);
|
||||
|
||||
[DllImport("WinDivert.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern bool WinDivertHelperCheckFilter(
|
||||
[MarshalAs(UnmanagedType.LPStr)] string filter,
|
||||
uint layer,
|
||||
[MarshalAs(UnmanagedType.LPStr)] StringBuilder errorStr,
|
||||
uint errorStrLen);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 辅助方法
|
||||
|
||||
/// <summary>
|
||||
/// 打开 WinDivert 句柄
|
||||
/// </summary>
|
||||
/// <param name="filter">过滤器表达式</param>
|
||||
/// <param name="layer">层级</param>
|
||||
/// <param name="priority">优先级</param>
|
||||
/// <param name="flags">标志</param>
|
||||
/// <returns>WinDivert 句柄</returns>
|
||||
public static IntPtr Open(string filter, uint layer, short priority, ulong flags) =>
|
||||
WinDivertOpen(filter, layer, priority, flags);
|
||||
|
||||
/// <summary>
|
||||
/// 接收数据包
|
||||
/// </summary>
|
||||
/// <param name="handle">WinDivert 句柄</param>
|
||||
/// <param name="buffer">数据缓冲区</param>
|
||||
/// <param name="len">缓冲区长度</param>
|
||||
/// <param name="addr">地址结构</param>
|
||||
/// <param name="readLen">实际读取长度</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public static bool Recv(IntPtr handle, byte[] buffer, uint bufferLength, ref WINDIVERT_ADDRESS addr,
|
||||
ref uint readLen)
|
||||
{
|
||||
// 明确初始化readLen为0
|
||||
readLen = 0;
|
||||
|
||||
// 确保WINDIVERT_ADDRESS结构体初始化正确
|
||||
if (addr.Reserved == null)
|
||||
{
|
||||
addr.Reserved = new byte[8];
|
||||
}
|
||||
|
||||
return WinDivertRecv(handle, buffer, bufferLength, ref addr, ref readLen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 扩展接收数据包
|
||||
/// </summary>
|
||||
/// <param name="handle">WinDivert 句柄</param>
|
||||
/// <param name="buffer">数据缓冲区</param>
|
||||
/// <param name="len">缓冲区长度</param>
|
||||
/// <param name="flags">标志</param>
|
||||
/// <param name="addr">地址结构</param>
|
||||
/// <param name="readLen">实际读取长度</param>
|
||||
/// <param name="overlapped">重叠 I/O 结构</param>
|
||||
/// <returns>是否成功</returns>
|
||||
// 更安全的Recv包装
|
||||
public static bool RecvEx(IntPtr handle, byte[] buffer, ref WINDIVERT_ADDRESS addr, ref uint readLen)
|
||||
{
|
||||
// 确保结构体正确初始化
|
||||
if (addr.Reserved == null)
|
||||
addr.Reserved = new byte[8];
|
||||
|
||||
// 清零readLen
|
||||
readLen = 0;
|
||||
|
||||
// 使用Ex版本,提供更多控制和稳定性
|
||||
return WinDivertRecvEx(
|
||||
handle,
|
||||
buffer,
|
||||
(uint)buffer.Length,
|
||||
0, // 默认接收标志
|
||||
ref addr,
|
||||
ref readLen,
|
||||
IntPtr.Zero // 非异步调用
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送数据包
|
||||
/// </summary>
|
||||
/// <param name="handle">WinDivert 句柄</param>
|
||||
/// <param name="buffer">数据缓冲区</param>
|
||||
/// <param name="len">缓冲区长度</param>
|
||||
/// <param name="addr">地址结构</param>
|
||||
/// <param name="writeLen">实际写入长度</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public static bool Send(IntPtr handle, byte[] buffer, uint len, ref WINDIVERT_ADDRESS addr, ref uint writeLen) =>
|
||||
WinDivertSend(handle, buffer, len, ref addr, ref writeLen);
|
||||
|
||||
/// <summary>
|
||||
/// 扩展发送数据包
|
||||
/// </summary>
|
||||
/// <param name="handle">WinDivert 句柄</param>
|
||||
/// <param name="buffer">数据缓冲区</param>
|
||||
/// <param name="len">缓冲区长度</param>
|
||||
/// <param name="flags">标志</param>
|
||||
/// <param name="addr">地址结构</param>
|
||||
/// <param name="writeLen">实际写入长度</param>
|
||||
/// <param name="overlapped">重叠 I/O 结构</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public static bool SendEx(IntPtr handle, byte[] buffer, uint len, uint flags, ref WINDIVERT_ADDRESS addr,
|
||||
ref uint writeLen, IntPtr overlapped) =>
|
||||
WinDivertSendEx(handle, buffer, len, flags, ref addr, ref writeLen, overlapped);
|
||||
|
||||
/// <summary>
|
||||
/// 关闭 WinDivert 句柄
|
||||
/// </summary>
|
||||
/// <param name="handle">WinDivert 句柄</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public static bool Close(IntPtr handle) =>
|
||||
WinDivertClose(handle);
|
||||
|
||||
/// <summary>
|
||||
/// 设置参数
|
||||
/// </summary>
|
||||
/// <param name="handle">WinDivert 句柄</param>
|
||||
/// <param name="param">参数类型</param>
|
||||
/// <param name="value">参数值</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public static bool SetParam(IntPtr handle, int param, ulong value) =>
|
||||
WinDivertSetParam(handle, param, value);
|
||||
|
||||
/// <summary>
|
||||
/// 获取参数
|
||||
/// </summary>
|
||||
/// <param name="handle">WinDivert 句柄</param>
|
||||
/// <param name="param">参数类型</param>
|
||||
/// <param name="value">参数值</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public static bool GetParam(IntPtr handle, int param, ref ulong value) =>
|
||||
WinDivertGetParam(handle, param, ref value);
|
||||
|
||||
/// <summary>
|
||||
/// 计算校验和
|
||||
/// </summary>
|
||||
/// <param name="buffer">数据缓冲区</param>
|
||||
/// <param name="len">缓冲区长度</param>
|
||||
/// <param name="addr">地址结构</param>
|
||||
/// <param name="flags">标志</param>
|
||||
/// <returns>校验和结果</returns>
|
||||
public static uint CalcChecksums(byte[] buffer, uint len, ref WINDIVERT_ADDRESS addr, uint flags) =>
|
||||
WinDivertHelperCalcChecksums(buffer, len, ref addr, flags);
|
||||
|
||||
/// <summary>
|
||||
/// 解析数据包【传递至此的 buffer 必须是新的!必须是空的!不能是用过的!】
|
||||
/// </summary>
|
||||
/// <param name="buffer">数据缓冲区</param>
|
||||
/// <param name="len">缓冲区长度</param>
|
||||
/// <param name="ppIpHdr">IPv4 头指针</param>
|
||||
/// <param name="ppIpv6Hdr">IPv6 头指针</param>
|
||||
/// <param name="ppIcmpHdr">ICMP 头指针</param>
|
||||
/// <param name="ppIcmpv6Hdr">ICMPv6 头指针</param>
|
||||
/// <param name="ppTcpHdr">TCP 头指针</param>
|
||||
/// <param name="ppUdpHdr">UDP 头指针</param>
|
||||
/// <param name="ppData">数据指针</param>
|
||||
/// <param name="pDataLen">数据长度</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public static bool ParsePacket(
|
||||
byte[] buffer,
|
||||
uint len,
|
||||
ref IntPtr ppIpHdr,
|
||||
ref IntPtr ppIpv6Hdr,
|
||||
ref IntPtr ppIcmpHdr,
|
||||
ref IntPtr ppIcmpv6Hdr,
|
||||
ref IntPtr ppTcpHdr,
|
||||
ref IntPtr ppUdpHdr,
|
||||
ref IntPtr ppData,
|
||||
ref ushort pDataLen
|
||||
)
|
||||
{
|
||||
return WinDivertHelperParsePacket(
|
||||
buffer,
|
||||
len,
|
||||
out ppIpHdr,
|
||||
out ppIpv6Hdr,
|
||||
out ppIcmpHdr,
|
||||
out ppIcmpv6Hdr,
|
||||
out ppTcpHdr,
|
||||
out ppUdpHdr,
|
||||
out ppData,
|
||||
out pDataLen
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析 IPv4 地址
|
||||
/// </summary>
|
||||
/// <param name="str">IP 地址字符串</param>
|
||||
/// <param name="pAddr">地址结果</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public static bool ParseIPv4Address(string str, ref uint pAddr) =>
|
||||
WinDivertHelperParseIPv4Address(str, ref pAddr);
|
||||
|
||||
/// <summary>
|
||||
/// 解析 IPv6 地址
|
||||
/// </summary>
|
||||
/// <param name="str">IP 地址字符串</param>
|
||||
/// <param name="pAddr">地址结果</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public static bool ParseIPv6Address(string str, byte[] pAddr) =>
|
||||
WinDivertHelperParseIPv6Address(str, pAddr);
|
||||
|
||||
/// <summary>
|
||||
/// 格式化 IPv4 地址
|
||||
/// </summary>
|
||||
/// <param name="addr">IP 地址</param>
|
||||
/// <returns>格式化后的字符串</returns>
|
||||
public static string FormatIPv4Address(uint addr)
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder(16);
|
||||
if (WinDivertHelperFormatIPv4Address(addr, buffer, 16))
|
||||
{
|
||||
return buffer.ToString();
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化 IPv6 地址
|
||||
/// </summary>
|
||||
/// <param name="addr">IP 地址</param>
|
||||
/// <returns>格式化后的字符串</returns>
|
||||
public static string FormatIPv6Address(byte[] addr)
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder(46);
|
||||
if (WinDivertHelperFormatIPv6Address(addr, buffer, 46))
|
||||
{
|
||||
return buffer.ToString();
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查过滤器语法
|
||||
/// </summary>
|
||||
/// <param name="filter">过滤器表达式</param>
|
||||
/// <param name="layer">层级</param>
|
||||
/// <param name="errorStr">错误信息</param>
|
||||
/// <param name="errorStrLen">错误信息长度</param>
|
||||
/// <returns>是否有效</returns>
|
||||
public static bool CheckFilter(string filter, uint layer, StringBuilder errorStr, uint errorStrLen) =>
|
||||
WinDivertHelperCheckFilter(filter, layer, errorStr, errorStrLen);
|
||||
|
||||
#endregion
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct PacketHeaders
|
||||
{
|
||||
public IntPtr IpHdr;
|
||||
public IntPtr Ipv6Hdr;
|
||||
public IntPtr IcmpHdr;
|
||||
public IntPtr Icmpv6Hdr;
|
||||
public IntPtr TcpHdr;
|
||||
public IntPtr UdpHdr;
|
||||
public IntPtr Data;
|
||||
public uint DataLen;
|
||||
}
|
||||
|
||||
[DllImport("WinDivert.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern bool WinDivertHelperParsePacketEx(
|
||||
[In] byte[] pPacket,
|
||||
uint packetLen,
|
||||
ref PacketHeaders headers);
|
||||
}
|
||||
@@ -0,0 +1,609 @@
|
||||
using Common.Logger;
|
||||
|
||||
namespace WinDivertNet.WinDivertWrapper;
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
/// <summary>
|
||||
/// WinDivert 高级封装类
|
||||
/// </summary>
|
||||
public class WinDivertController : IDisposable
|
||||
{
|
||||
private IntPtr _handle;
|
||||
private bool _disposed = false;
|
||||
public IntPtr Handle => _handle;
|
||||
/// <summary>
|
||||
/// 获取句柄是否有效
|
||||
/// </summary>
|
||||
public bool IsValid => _handle != IntPtr.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public WinDivertController()
|
||||
{
|
||||
_handle = IntPtr.Zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 析构函数
|
||||
/// </summary>
|
||||
~WinDivertController()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打开 WinDivert 句柄
|
||||
/// </summary>
|
||||
/// <param name="filter">过滤器表达式</param>
|
||||
/// <param name="layer">层级</param>
|
||||
/// <param name="priority">优先级</param>
|
||||
/// <param name="flags">标志</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public bool Open(string filter, uint layer = WinDivert.WINDIVERT_LAYER_NETWORK,
|
||||
short priority = WinDivert.WINDIVERT_PRIORITY_DEFAULT,
|
||||
ulong flags = 0)
|
||||
{
|
||||
if (IsValid)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
_handle = WinDivert.WinDivertOpen(filter, layer, priority, flags);
|
||||
|
||||
if (_handle == IntPtr.Zero)
|
||||
{
|
||||
int errorCode = Marshal.GetLastWin32Error();
|
||||
Log.Information($"WinDivert打开失败,错误码: {errorCode}");
|
||||
}
|
||||
|
||||
return IsValid;
|
||||
}
|
||||
public bool TryGetPacket(out byte[] packet, out WinDivert.WINDIVERT_ADDRESS address)
|
||||
{
|
||||
packet = null;
|
||||
address = new WinDivert.WINDIVERT_ADDRESS { Reserved = new byte[8] };
|
||||
|
||||
if (!IsValid) return false;
|
||||
|
||||
byte[] buffer = new byte[65535]; // 足够大的缓冲区
|
||||
|
||||
// 使用我们的SafeRecv函数处理异常大小问题
|
||||
return WinDivert.SafeRecv(_handle, buffer, ref address, out packet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭 WinDivert 句柄
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
if (IsValid)
|
||||
{
|
||||
WinDivert.Close(_handle);
|
||||
_handle = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 接收数据包
|
||||
/// </summary>
|
||||
/// <param name="buffer">数据缓冲区</param>
|
||||
/// <param name="addr">地址结构</param>
|
||||
/// <returns>实际读取的字节数,失败返回 0</returns>
|
||||
public uint Receive(byte[] buffer, ref WinDivert.WINDIVERT_ADDRESS addr)
|
||||
{
|
||||
if (!IsValid || buffer == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint readLen = 0;
|
||||
if (WinDivert.Recv(_handle, buffer, (uint)buffer.Length, ref addr, ref readLen))
|
||||
{
|
||||
Log.Information($"Receive: {readLen}");
|
||||
return readLen;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步接收数据包
|
||||
/// </summary>
|
||||
/// <param name="buffer">数据缓冲区</param>
|
||||
/// <param name="addr">地址结构</param>
|
||||
/// <returns>实际读取的字节数的任务</returns>
|
||||
public Task<uint> ReceiveAsync(byte[] buffer, WinDivert.WINDIVERT_ADDRESS addr)
|
||||
{
|
||||
return Task.Run(() => Receive(buffer, ref addr));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送数据包
|
||||
/// </summary>
|
||||
/// <param name="buffer">数据缓冲区</param>
|
||||
/// <param name="addr">地址结构</param>
|
||||
/// <returns>实际写入的字节数,失败返回 0</returns>
|
||||
public uint Send(byte[] buffer, ref WinDivert.WINDIVERT_ADDRESS addr)
|
||||
{
|
||||
if (!IsValid || buffer == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint writeLen = 0;
|
||||
if (WinDivert.Send(_handle, buffer, (uint)buffer.Length, ref addr, ref writeLen))
|
||||
{
|
||||
return writeLen;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步发送数据包
|
||||
/// </summary>
|
||||
/// <param name="buffer">数据缓冲区</param>
|
||||
/// <param name="addr">地址结构</param>
|
||||
/// <returns>实际写入的字节数的任务</returns>
|
||||
public Task<uint> SendAsync(byte[] buffer, WinDivert.WINDIVERT_ADDRESS addr)
|
||||
{
|
||||
return Task.Run(() => Send(buffer, ref addr));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置参数
|
||||
/// </summary>
|
||||
/// <param name="param">参数类型</param>
|
||||
/// <param name="value">参数值</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public bool SetParam(int param, ulong value)
|
||||
{
|
||||
if (!IsValid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return WinDivert.SetParam(_handle, param, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取参数
|
||||
/// </summary>
|
||||
/// <param name="param">参数类型</param>
|
||||
/// <param name="value">参数值</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public bool GetParam(int param, ref ulong value)
|
||||
{
|
||||
if (!IsValid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return WinDivert.GetParam(_handle, param, ref value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验包数据的校验和
|
||||
/// </summary>
|
||||
/// <param name="buffer">数据缓冲区</param>
|
||||
/// <param name="addr">地址结构</param>
|
||||
/// <param name="flags">标志</param>
|
||||
/// <returns>校验和结果</returns>
|
||||
public uint CalcChecksums(byte[] buffer, ref WinDivert.WINDIVERT_ADDRESS addr, uint flags = 0)
|
||||
{
|
||||
if (buffer == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return WinDivert.CalcChecksums(buffer, (uint)buffer.Length, ref addr, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查过滤器语法
|
||||
/// </summary>
|
||||
/// <param name="filter">过滤器表达式</param>
|
||||
/// <param name="layer">层级</param>
|
||||
/// <returns>检查结果和错误信息</returns>
|
||||
public (bool IsValid, string ErrorMessage) CheckFilter(string filter,
|
||||
uint layer = WinDivert.WINDIVERT_LAYER_NETWORK)
|
||||
{
|
||||
StringBuilder errorStr = new StringBuilder(256);
|
||||
bool isValid = WinDivert.CheckFilter(filter, layer, errorStr, 256);
|
||||
return (isValid, errorStr.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析数据包
|
||||
/// </summary>
|
||||
/// <param name="packet">数据包</param>
|
||||
/// <returns>解析结果</returns>
|
||||
public PacketHeaders ParsePacket(byte[] packet)
|
||||
{
|
||||
if (packet == null)
|
||||
{
|
||||
return new PacketHeaders();
|
||||
}
|
||||
|
||||
IntPtr ppIpHdr = IntPtr.Zero;
|
||||
IntPtr ppIpv6Hdr = IntPtr.Zero;
|
||||
IntPtr ppIcmpHdr = IntPtr.Zero;
|
||||
IntPtr ppIcmpv6Hdr = IntPtr.Zero;
|
||||
IntPtr ppTcpHdr = IntPtr.Zero;
|
||||
IntPtr ppUdpHdr = IntPtr.Zero;
|
||||
IntPtr ppData = IntPtr.Zero;
|
||||
ushort pDataLen = 0;
|
||||
|
||||
bool success = WinDivert.ParsePacket(packet, (uint)packet.Length, ref ppIpHdr, ref ppIpv6Hdr,
|
||||
ref ppIcmpHdr, ref ppIcmpv6Hdr, ref ppTcpHdr, ref ppUdpHdr, ref ppData, ref pDataLen);
|
||||
|
||||
PacketHeaders headers = new PacketHeaders
|
||||
{
|
||||
Success = success,
|
||||
DataLength = pDataLen
|
||||
};
|
||||
|
||||
if (success)
|
||||
{
|
||||
if (ppIpHdr != IntPtr.Zero)
|
||||
{
|
||||
headers.IpHeader = Marshal.PtrToStructure<WinDivert.WINDIVERT_IPHDR>(ppIpHdr);
|
||||
headers.HasIpHeader = true;
|
||||
}
|
||||
|
||||
if (ppIpv6Hdr != IntPtr.Zero)
|
||||
{
|
||||
headers.Ipv6Header = Marshal.PtrToStructure<WinDivert.WINDIVERT_IPV6HDR>(ppIpv6Hdr);
|
||||
headers.HasIpv6Header = true;
|
||||
}
|
||||
|
||||
if (ppTcpHdr != IntPtr.Zero)
|
||||
{
|
||||
headers.TcpHeader = Marshal.PtrToStructure<WinDivert.WINDIVERT_TCPHDR>(ppTcpHdr);
|
||||
headers.HasTcpHeader = true;
|
||||
}
|
||||
|
||||
if (ppUdpHdr != IntPtr.Zero)
|
||||
{
|
||||
headers.UdpHeader = Marshal.PtrToStructure<WinDivert.WINDIVERT_UDPHDR>(ppUdpHdr);
|
||||
headers.HasUdpHeader = true;
|
||||
}
|
||||
|
||||
if (ppIcmpHdr != IntPtr.Zero)
|
||||
{
|
||||
headers.IcmpHeader = Marshal.PtrToStructure<WinDivert.WINDIVERT_ICMPHDR>(ppIcmpHdr);
|
||||
headers.HasIcmpHeader = true;
|
||||
}
|
||||
|
||||
if (ppIcmpv6Hdr != IntPtr.Zero)
|
||||
{
|
||||
headers.Icmpv6Header = Marshal.PtrToStructure<WinDivert.WINDIVERT_ICMPV6HDR>(ppIcmpv6Hdr);
|
||||
headers.HasIcmpv6Header = true;
|
||||
}
|
||||
|
||||
if (ppData != IntPtr.Zero && pDataLen > 0)
|
||||
{
|
||||
headers.Data = new byte[pDataLen];
|
||||
Marshal.Copy(ppData, headers.Data, 0, (int)pDataLen);
|
||||
}
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改包的源 IP 地址
|
||||
/// </summary>
|
||||
/// <param name="packet">数据包</param>
|
||||
/// <param name="sourceIp">新的源 IP 地址</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public bool ModifySourceIpv4Address(byte[] packet, string sourceIp)
|
||||
{
|
||||
if (packet == null || string.IsNullOrEmpty(sourceIp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PacketHeaders headers = ParsePacket(packet);
|
||||
if (!headers.Success || !headers.HasIpHeader)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint addr = 0;
|
||||
if (!WinDivert.ParseIPv4Address(sourceIp, ref addr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 计算 IP 头的偏移量
|
||||
int ipHdrOffset = Marshal.SizeOf<WinDivert.WINDIVERT_IPHDR>();
|
||||
|
||||
// 修改源 IP 地址
|
||||
byte[] addrBytes = BitConverter.GetBytes(addr);
|
||||
Array.Copy(addrBytes, 0, packet, ipHdrOffset - 8, 4);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改包的目标 IP 地址
|
||||
/// </summary>
|
||||
/// <param name="packet">数据包</param>
|
||||
/// <param name="destinationIp">新的目标 IP 地址</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public bool ModifyDestinationIpv4Address(byte[] packet, string destinationIp)
|
||||
{
|
||||
if (packet == null || string.IsNullOrEmpty(destinationIp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PacketHeaders headers = ParsePacket(packet);
|
||||
if (!headers.Success || !headers.HasIpHeader)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint addr = 0;
|
||||
if (!WinDivert.ParseIPv4Address(destinationIp, ref addr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 计算 IP 头的偏移量
|
||||
int ipHdrOffset = Marshal.SizeOf<WinDivert.WINDIVERT_IPHDR>();
|
||||
|
||||
// 修改目标 IP 地址
|
||||
byte[] addrBytes = BitConverter.GetBytes(addr);
|
||||
Array.Copy(addrBytes, 0, packet, ipHdrOffset - 4, 4);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改包的源端口
|
||||
/// </summary>
|
||||
/// <param name="packet">数据包</param>
|
||||
/// <param name="sourcePort">新的源端口</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public bool ModifySourcePort(byte[] packet, ushort sourcePort)
|
||||
{
|
||||
if (packet == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PacketHeaders headers = ParsePacket(packet);
|
||||
if (!headers.Success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int offset = -1;
|
||||
|
||||
if (headers.HasTcpHeader)
|
||||
{
|
||||
// 计算 TCP 头的偏移量(简化计算,实际应该考虑 IP 选项)
|
||||
offset = (headers.HasIpHeader ? 20 : 0) + (headers.HasIpv6Header ? 40 : 0);
|
||||
}
|
||||
else if (headers.HasUdpHeader)
|
||||
{
|
||||
// 计算 UDP 头的偏移量(简化计算,实际应该考虑 IP 选项)
|
||||
offset = (headers.HasIpHeader ? 20 : 0) + (headers.HasIpv6Header ? 40 : 0);
|
||||
}
|
||||
|
||||
if (offset < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 修改源端口
|
||||
byte[] portBytes = BitConverter.GetBytes(sourcePort);
|
||||
Array.Copy(portBytes, 0, packet, offset, 2);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改包的目标端口
|
||||
/// </summary>
|
||||
/// <param name="packet">数据包</param>
|
||||
/// <param name="destinationPort">新的目标端口</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public bool ModifyDestinationPort(byte[] packet, ushort destinationPort)
|
||||
{
|
||||
if (packet == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PacketHeaders headers = ParsePacket(packet);
|
||||
if (!headers.Success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int offset = -1;
|
||||
|
||||
if (headers.HasTcpHeader)
|
||||
{
|
||||
// 计算 TCP 头的偏移量(简化计算,实际应该考虑 IP 选项)
|
||||
offset = (headers.HasIpHeader ? 20 : 0) + (headers.HasIpv6Header ? 40 : 0) + 2;
|
||||
}
|
||||
else if (headers.HasUdpHeader)
|
||||
{
|
||||
// 计算 UDP 头的偏移量(简化计算,实际应该考虑 IP 选项)
|
||||
offset = (headers.HasIpHeader ? 20 : 0) + (headers.HasIpv6Header ? 40 : 0) + 2;
|
||||
}
|
||||
|
||||
if (offset < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 修改目标端口
|
||||
byte[] portBytes = BitConverter.GetBytes(destinationPort);
|
||||
Array.Copy(portBytes, 0, packet, offset, 2);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重新计算包的校验和
|
||||
/// </summary>
|
||||
/// <param name="packet">数据包</param>
|
||||
/// <param name="addr">地址结构</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public bool RecalculateChecksums(byte[] packet, ref WinDivert.WINDIVERT_ADDRESS addr)
|
||||
{
|
||||
if (packet == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint result = WinDivert.CalcChecksums(packet, (uint)packet.Length, ref addr, 0);
|
||||
return result > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
/// <param name="disposing">是否为显式释放</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// 释放托管资源
|
||||
}
|
||||
|
||||
// 释放非托管资源
|
||||
Close();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据包头信息
|
||||
/// </summary>
|
||||
public class PacketHeaders
|
||||
{
|
||||
/// <summary>
|
||||
/// 解析是否成功
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否包含 IPv4 头
|
||||
/// </summary>
|
||||
public bool HasIpHeader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否包含 IPv6 头
|
||||
/// </summary>
|
||||
public bool HasIpv6Header { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否包含 TCP 头
|
||||
/// </summary>
|
||||
public bool HasTcpHeader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否包含 UDP 头
|
||||
/// </summary>
|
||||
public bool HasUdpHeader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否包含 ICMP 头
|
||||
/// </summary>
|
||||
public bool HasIcmpHeader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否包含 ICMPv6 头
|
||||
/// </summary>
|
||||
public bool HasIcmpv6Header { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// IPv4 头
|
||||
/// </summary>
|
||||
public WinDivert.WINDIVERT_IPHDR IpHeader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// IPv6 头
|
||||
/// </summary>
|
||||
public WinDivert.WINDIVERT_IPV6HDR Ipv6Header { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// TCP 头
|
||||
/// </summary>
|
||||
public WinDivert.WINDIVERT_TCPHDR TcpHeader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// UDP 头
|
||||
/// </summary>
|
||||
public WinDivert.WINDIVERT_UDPHDR UdpHeader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ICMP 头
|
||||
/// </summary>
|
||||
public WinDivert.WINDIVERT_ICMPHDR IcmpHeader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ICMPv6 头
|
||||
/// </summary>
|
||||
public WinDivert.WINDIVERT_ICMPV6HDR Icmpv6Header { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据部分
|
||||
/// </summary>
|
||||
public byte[] Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据长度
|
||||
/// </summary>
|
||||
public uint DataLength { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("PacketHeaders {");
|
||||
sb.AppendLine($" Success: {Success},");
|
||||
sb.AppendLine($" HasIpHeader: {HasIpHeader},");
|
||||
sb.AppendLine($" HasIpv6Header: {HasIpv6Header},");
|
||||
sb.AppendLine($" HasTcpHeader: {HasTcpHeader},");
|
||||
sb.AppendLine($" HasUdpHeader: {HasUdpHeader},");
|
||||
sb.AppendLine($" HasIcmpHeader: {HasIcmpHeader},");
|
||||
sb.AppendLine($" HasIcmpv6Header: {HasIcmpv6Header},");
|
||||
|
||||
sb.AppendLine($" IpHeader: {(HasIpHeader ? IpHeader.ToString() : "N/A")},");
|
||||
sb.AppendLine($" Ipv6Header: {(HasIpv6Header ? Ipv6Header.ToString() : "N/A")},");
|
||||
sb.AppendLine($" TcpHeader: {(HasTcpHeader ? TcpHeader.ToString() : "N/A")},");
|
||||
sb.AppendLine($" UdpHeader: {(HasUdpHeader ? UdpHeader.ToString() : "N/A")},");
|
||||
sb.AppendLine($" IcmpHeader: {(HasIcmpHeader ? IcmpHeader.ToString() : "N/A")},");
|
||||
sb.AppendLine($" Icmpv6Header: {(HasIcmpv6Header ? Icmpv6Header.ToString() : "N/A")},");
|
||||
|
||||
sb.AppendLine($" DataLength: {DataLength},");
|
||||
sb.AppendLine(
|
||||
$" Data: {(Data != null ? BitConverter.ToString(Data, 0, (int)Math.Min(Data.Length, 32)) + (Data.Length > 32 ? "..." : "") : "null")}");
|
||||
sb.AppendLine("}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user