Initial commit - Pingerino network monitoring application
هذا الالتزام موجود في:
255
Services/PingService.cs
Normal file
255
Services/PingService.cs
Normal file
@@ -0,0 +1,255 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Pingerino.Services.Interfaces;
|
||||
|
||||
namespace Pingerino.Services
|
||||
{
|
||||
public class PingService : IPingService
|
||||
{
|
||||
private readonly ILoggingService _logger;
|
||||
private readonly List<long> _pingRoundTripTimes = new List<long>();
|
||||
private readonly List<double> _jitterValues = new List<double>();
|
||||
private readonly List<double> _packetLossValues = new List<double>();
|
||||
private readonly object _lockObject = new object();
|
||||
private Timer _pingTimer;
|
||||
private string _currentIpAddress;
|
||||
private bool _disposed;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
public event EventHandler<PingResultEventArgs> PingCompleted;
|
||||
public event EventHandler<NetworkStatisticsEventArgs> StatisticsUpdated;
|
||||
|
||||
public bool IsRunning { get; private set; }
|
||||
|
||||
public PingService(ILoggingService logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public async System.Threading.Tasks.Task StartPingingAsync(string ipAddress, int intervalMs, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (IsRunning)
|
||||
{
|
||||
await StopPingingAsync();
|
||||
}
|
||||
|
||||
_currentIpAddress = ipAddress;
|
||||
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
|
||||
_pingTimer = new Timer(async _ => await PingCallback(), null, 0, intervalMs);
|
||||
IsRunning = true;
|
||||
|
||||
_logger.LogInformation($"Started pinging {ipAddress} with interval {intervalMs}ms");
|
||||
}
|
||||
|
||||
public async System.Threading.Tasks.Task StopPingingAsync()
|
||||
{
|
||||
if (!IsRunning) return;
|
||||
|
||||
_pingTimer?.Dispose();
|
||||
_cancellationTokenSource?.Cancel();
|
||||
IsRunning = false;
|
||||
|
||||
_logger.LogInformation("Stopped pinging");
|
||||
await System.Threading.Tasks.Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async System.Threading.Tasks.Task<PingResult> PingOnceAsync(string ipAddress, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!NetworkInterface.GetIsNetworkAvailable())
|
||||
{
|
||||
return new PingResult
|
||||
{
|
||||
IpAddress = ipAddress,
|
||||
IsSuccess = false,
|
||||
Status = "No network connection available",
|
||||
ErrorMessage = "Network unavailable"
|
||||
};
|
||||
}
|
||||
|
||||
using (var ping = new Ping())
|
||||
{
|
||||
var reply = await ping.SendPingAsync(ipAddress);
|
||||
|
||||
var result = new PingResult
|
||||
{
|
||||
IpAddress = ipAddress,
|
||||
IsSuccess = reply.Status == IPStatus.Success,
|
||||
RoundTripTime = reply.RoundtripTime,
|
||||
Status = reply.Status.ToString()
|
||||
};
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
UpdateStatistics(reply.RoundtripTime);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Error pinging {ipAddress}", ex);
|
||||
return new PingResult
|
||||
{
|
||||
IpAddress = ipAddress,
|
||||
IsSuccess = false,
|
||||
Status = "Error",
|
||||
ErrorMessage = ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearStatistics()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
_pingRoundTripTimes.Clear();
|
||||
_jitterValues.Clear();
|
||||
_packetLossValues.Clear();
|
||||
}
|
||||
|
||||
_logger.LogInformation("Statistics cleared");
|
||||
}
|
||||
|
||||
public NetworkStatistics GetCurrentStatistics()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
var stats = new NetworkStatistics();
|
||||
|
||||
if (_pingRoundTripTimes.Count > 0)
|
||||
{
|
||||
stats.MaxPing = _pingRoundTripTimes.Max();
|
||||
stats.MinPing = _pingRoundTripTimes.Min();
|
||||
stats.AveragePing = _pingRoundTripTimes.Average();
|
||||
stats.TotalPings = _pingRoundTripTimes.Count;
|
||||
stats.SuccessfulPings = _pingRoundTripTimes.Count;
|
||||
}
|
||||
|
||||
if (_jitterValues.Count > 0)
|
||||
{
|
||||
stats.MaxJitter = _jitterValues.Max();
|
||||
stats.MinJitter = _jitterValues.Min();
|
||||
stats.AverageJitter = _jitterValues.Average();
|
||||
stats.Jitter = stats.AverageJitter;
|
||||
}
|
||||
|
||||
if (_packetLossValues.Count > 0)
|
||||
{
|
||||
stats.MaxPacketLoss = _packetLossValues.Max();
|
||||
stats.MinPacketLoss = _packetLossValues.Min();
|
||||
stats.AveragePacketLoss = _packetLossValues.Average();
|
||||
stats.PacketLoss = stats.AveragePacketLoss;
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task PingCallback()
|
||||
{
|
||||
if (_cancellationTokenSource?.Token.IsCancellationRequested == true)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var result = await PingOnceAsync(_currentIpAddress, _cancellationTokenSource?.Token ?? CancellationToken.None);
|
||||
PingCompleted?.Invoke(this, new PingResultEventArgs(result, DateTime.Now));
|
||||
|
||||
// Calculate and update network statistics periodically
|
||||
await CalculateNetworkStatisticsAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Error in ping callback", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStatistics(long roundTripTime)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
_pingRoundTripTimes.Add(roundTripTime);
|
||||
|
||||
// Calculate jitter if we have previous measurements
|
||||
if (_pingRoundTripTimes.Count > 1)
|
||||
{
|
||||
var previousTime = _pingRoundTripTimes[_pingRoundTripTimes.Count - 2];
|
||||
var jitter = Math.Abs(roundTripTime - previousTime);
|
||||
_jitterValues.Add(jitter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task CalculateNetworkStatisticsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Calculate packet loss over the last 10 pings
|
||||
var packetLoss = await CalculatePacketLossAsync(_currentIpAddress, 10);
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
_packetLossValues.Add(packetLoss);
|
||||
}
|
||||
|
||||
var stats = GetCurrentStatistics();
|
||||
StatisticsUpdated?.Invoke(this, new NetworkStatisticsEventArgs(stats));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Error calculating network statistics", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task<double> CalculatePacketLossAsync(string ipAddress, int count)
|
||||
{
|
||||
int failedPings = 0;
|
||||
var tasks = new List<System.Threading.Tasks.Task<bool>>();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
tasks.Add(TestSinglePingAsync(ipAddress));
|
||||
}
|
||||
|
||||
var results = await System.Threading.Tasks.Task.WhenAll(tasks);
|
||||
failedPings = results.Count(r => !r);
|
||||
|
||||
return (double)failedPings / count * 100;
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task<bool> TestSinglePingAsync(string ipAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var ping = new Ping())
|
||||
{
|
||||
var reply = await ping.SendPingAsync(ipAddress);
|
||||
return reply.Status == IPStatus.Success;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
_pingTimer?.Dispose();
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_cancellationTokenSource?.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
المرجع في مشكلة جديدة
حظر مستخدم