Skip to content

Commit 15628eb

Browse files
committed
更新目标框架,重构队列类并改进异步操作
1 parent 0ac517d commit 15628eb

File tree

2 files changed

+116
-47
lines changed

2 files changed

+116
-47
lines changed

Pings.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<OutputType>Exe</OutputType>
4-
<TargetFramework>net8.0</TargetFramework>
4+
<TargetFramework>net9.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77
<PublishAot>true</PublishAot>

Program.cs

Lines changed: 115 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,90 @@
44
using System.Net;
55
using System.Net.NetworkInformation;
66
using System.Text;
7+
using System.Threading.Channels;
8+
79
namespace Pings
810
{
9-
public class ObservableQueue<T> : Queue<T>
11+
public class ObservableQueue<T> : IDisposable
1012
{
13+
public bool IsBounded { get; init; } = false;
14+
private readonly Channel<T> _channel;
15+
private bool _disposed;
16+
17+
public ObservableQueue(ChannelOptions? options = null, int? capacity = null)
18+
{
19+
if (options is null && capacity is null)
20+
{
21+
_channel = Channel.CreateUnbounded<T>();
22+
return;
23+
}
24+
if (options is null && capacity is not null && capacity > 0)
25+
{
26+
_channel = Channel.CreateBounded<T>((int)capacity);
27+
IsBounded = true;
28+
return;
29+
}
30+
if (options is null && capacity is not null && capacity < 0)
31+
throw new ArgumentException("Queue type is bounded, but the capacity parameter less than zero.", nameof(capacity));
32+
33+
if (options is BoundedChannelOptions boundedOptions)
34+
{
35+
_channel = Channel.CreateBounded<T>(boundedOptions);
36+
IsBounded = true;
37+
}
38+
else if (options is UnboundedChannelOptions unboundedOptions)
39+
{
40+
_channel = Channel.CreateUnbounded<T>(unboundedOptions);
41+
}
42+
else
43+
{
44+
throw new ArgumentNullException(nameof(options), "Unable to confirm queue type.");
45+
}
46+
}
47+
public ObservableQueue() : this(null, null) { }
48+
public ObservableQueue(int capacity) : this(null, capacity) { }
49+
public ObservableQueue(ChannelOptions options) : this(options, null) { }
50+
51+
1152
public event Action<T>? Enqueued;
1253
public event Action<T>? Dequeued;
13-
public new void Enqueue(T item)
54+
55+
public async Task EnqueueAsync(T item)
1456
{
15-
base.Enqueue(item);
57+
ObjectDisposedException.ThrowIf(_disposed, nameof(ObservableQueue<T>));
58+
await _channel.Writer.WriteAsync(item);
1659
Enqueued?.Invoke(item);
1760
}
18-
public new T Dequeue()
61+
62+
public async Task<T> DequeueAsync()
1963
{
20-
T item = base.Dequeue();
64+
ObjectDisposedException.ThrowIf(_disposed, nameof(ObservableQueue<T>));
65+
T item = await _channel.Reader.ReadAsync();
2166
Dequeued?.Invoke(item);
2267
return item;
2368
}
69+
70+
public int Count => _channel.Reader.Count;
71+
72+
public void Dispose()
73+
{
74+
Dispose(true);
75+
GC.SuppressFinalize(this);
76+
}
77+
78+
protected virtual void Dispose(bool disposing)
79+
{
80+
if (!_disposed)
81+
{
82+
if (disposing)
83+
{
84+
_channel.Writer.Complete();
85+
}
86+
_disposed = true;
87+
}
88+
}
2489
}
90+
2591
public static class IPStatusExtensions
2692
{
2793
public static string ToChineseString(this IPStatus status)
@@ -69,7 +135,7 @@ public ICMPMonitor(CancellationTokenSource cancellationTokenSource, Logging? log
69135
Logging = logging;
70136
Tasks = [];
71137
TaskMap = [];
72-
TasksTable = new() { Caption = new TableTitle("当前丢包(L) / 确认警告(C) / 退出(Q)") };
138+
TasksTable = new() { Caption = new TableTitle("确认警告(C) / 退出(Q)") };
73139
TasksTable.AddColumns("名称", "IP/域名", "状态", "延迟", "警告/日志");
74140
TasksTable.Centered();
75141
}
@@ -89,13 +155,13 @@ public void AddHost(ICMPTestTask newTask)
89155
{
90156
TasksTable.Rows.Update(TaskMap[task.IP], 3, new Text($"{(int)task.Delay.TotalMilliseconds}ms"));
91157
};
92-
newTask.OpenWarning += (task) =>
158+
newTask.OpenWarning += async (task) =>
93159
{
94160
Logging?.Log($"{task.Name}({task.IP}) 触发警告 当前状态:{task.State.ToChineseString()}");
95161

96-
TasksTable.Rows.Update(TaskMap[task.IP], 4, new Text(task.Warnings.Peek(), new Style(Color.Yellow, Color.Red, Decoration.Bold)));
162+
TasksTable.Rows.Update(TaskMap[task.IP], 4, new Text(await task.Warnings.DequeueAsync(), new Style(Color.Yellow, Color.Red, Decoration.Bold)));
97163
};
98-
newTask.ConfirmWarning += (task) =>
164+
newTask.ConfirmWarning += async (task) =>
99165
{
100166
if (!task.IsWarning)
101167
{
@@ -105,30 +171,25 @@ public void AddHost(ICMPTestTask newTask)
105171
}
106172
else
107173
{
108-
TasksTable.Rows.Update(TaskMap[task.IP], 4, new Text(task.Warnings.Peek(), new Style(Color.Yellow, Color.Red, Decoration.Bold)));
174+
TasksTable.Rows.Update(TaskMap[task.IP], 4, new Text(await task.Warnings.DequeueAsync(), new Style(Color.Yellow, Color.Red, Decoration.Bold)));
109175
}
110176
};
111-
newTask.StatusChanged += (task) =>
177+
newTask.StatusChanged += async (task) =>
112178
{
113179
Logging?.Log($"{task.Name}({task.IP}) {task.State.ToChineseString()} {(int)task.Delay.TotalMilliseconds}ms");
114180

115181
TasksTable.Rows.Update(TaskMap[task.IP], 2, task.State == IPStatus.Success ? new Text(task.State.ToChineseString()) : new Text(task.State.ToChineseString(), new Style(Color.Yellow, Color.Red, Decoration.Bold)));
116182

117183
task.LastLog = $"{task.State.ToChineseString()} [{DateTime.Now:yyyy-MM-dd HH:mm:ss}] ";
118-
if (task.State != IPStatus.Success) task.Warnings.Enqueue($"{task.State.ToChineseString()} [{DateTime.Now:yyyy-MM-dd HH:mm:ss}]");
184+
if (task.State != IPStatus.Success) await task.Warnings.EnqueueAsync($"{task.State.ToChineseString()} [{DateTime.Now:yyyy-MM-dd HH:mm:ss}]");
119185
};
120186
newTask.DelayExceptionOccurred += (task) =>
121187
{
122188
Logging?.Log($"{task.Name}({task.IP}) 延迟波动 {(int)task.PreviousDelay.TotalMilliseconds}ms -> {(int)task.Delay.TotalMilliseconds}ms");
123189

124190
task.LastLog = $"延迟波动 {(int)task.PreviousDelay.TotalMilliseconds}ms -> {(int)task.Delay.TotalMilliseconds}ms";
125191
};
126-
newTask.RecentLossRateRecorded += (task) =>
127-
{
128-
if (task.RecentLossRate > (double)1 / task.MaxRecentPackets) Logging?.Log($"丢包率 {task.RecentLossRate:F2}%");
129-
130-
task.LastLog = $"丢包率 {task.RecentLossRate:F2}%";
131-
};
192+
132193
Tasks.Add(newTask);
133194
}
134195
}
@@ -143,18 +204,15 @@ public class ICMPTestTask
143204
public event Action<ICMPTestTask>? StatusChanged;
144205
public event Action<ICMPTestTask>? DelayChanged;
145206
public event Action<ICMPTestTask>? DelayExceptionOccurred;
146-
public event Action<ICMPTestTask>? RecentLossRateRecorded;
147207

148208
public string Name { get; init; }
149209
public string IP { get; init; }
150210
public int MaxRecentPackets { get; init; }
151211
public TimeSpan SignificantDelayThreshold { get; init; }
152212
public TimeSpan PreviousDelay { get; set; } = DefaultDelay;
153-
public ObservableQueue<string> Warnings { get; set; } = new();
154-
private Queue<IPStatus> RecentPackets { get; set; } = new();
155-
213+
public ObservableQueue<string> Warnings { get; set; }
214+
private ObservableQueue<IPStatus> RecentPackets { get; set; }
156215
public bool IsWarning => Warnings.Count > 0;
157-
public double RecentLossRate => RecentPackets.Count == 0 ? 0 : (double)RecentPackets.Count(status => status != IPStatus.Success) / RecentPackets.Count * 100;
158216

159217
private string? lastLog;
160218
public string LastLog
@@ -220,21 +278,17 @@ private bool IsSignificantDelayChange()
220278
&& Delay > PreviousDelay
221279
&& (Delay - PreviousDelay).Duration() > SignificantDelayThreshold;
222280
}
223-
private void UpdateRecentPackets(IPStatus state)
224-
{
225-
RecentPackets.Enqueue(state);
226-
if (RecentPackets.Count > MaxRecentPackets)
227-
{
228-
RecentPackets.Dequeue();
229-
}
230-
}
281+
231282
public ICMPTestTask(CancellationTokenSource CTS, string name, string ip, int timeout, int maxRecentPackets, int significantDelayThreshold)
232283
{
233284
Name = name;
234285
IP = ip;
235286
MaxRecentPackets = maxRecentPackets;
236287
SignificantDelayThreshold = TimeSpan.FromMilliseconds(significantDelayThreshold);
237288

289+
Warnings = new();
290+
RecentPackets = new(maxRecentPackets);
291+
238292
Warnings.Enqueued += warning => OpenWarning?.Invoke(this);
239293
Warnings.Dequeued += warning => ConfirmWarning?.Invoke(this);
240294

@@ -251,24 +305,22 @@ public ICMPTestTask(CancellationTokenSource CTS, string name, string ip, int tim
251305
stopwatch.Restart();
252306
reply = ping.Send(IP, timeout);
253307
stopwatch.Stop();
254-
State = reply.Status;
255308
Delay = State == IPStatus.Success
256309
? TimeSpan.FromMilliseconds(reply.RoundtripTime)
257310
: DefaultDelay;
311+
State = reply.Status;
258312
}
259313
catch
260314
{
261-
State = IPStatus.Unknown;
262315
Delay = DefaultDelay;
263-
RecentLossRateRecorded?.Invoke(this);
316+
State = IPStatus.Unknown;
264317
}
265318

266-
UpdateRecentPackets(State);
319+
await RecentPackets.EnqueueAsync(State);
267320

268321
pingCounter++;
269322
if (pingCounter == MaxRecentPackets)
270323
{
271-
RecentLossRateRecorded?.Invoke(this);
272324
pingCounter = 0;
273325
}
274326

@@ -356,6 +408,8 @@ public struct ICMPTaskConfig
356408
private int? timeout;
357409
private int? maxRecentPackets;
358410
private int? significantDelayThreshold;
411+
private double? packetLossDuration;
412+
private int? packetLossCount;
359413

360414
public string Name
361415
{
@@ -411,6 +465,26 @@ public int SignificantDelayThreshold
411465
significantDelayThreshold = value;
412466
}
413467
}
468+
public TimeSpan PacketLossDuration
469+
{
470+
get => TimeSpan.FromSeconds(packetLossDuration ?? (Timeout / 1000) * 30);
471+
set
472+
{
473+
if (value.TotalMilliseconds < Timeout)
474+
throw new ArgumentOutOfRangeException(nameof(PacketLossDuration), $"PacketLossDuration must be greater than {nameof(Timeout)}.");
475+
packetLossDuration = value.TotalSeconds;
476+
}
477+
}
478+
public int PacketLossCount
479+
{
480+
get => packetLossCount ?? 5;
481+
set
482+
{
483+
if (value < 0)
484+
throw new ArgumentOutOfRangeException(nameof(PacketLossCount), "PacketLossCount must be non-negative.");
485+
packetLossCount = value;
486+
}
487+
}
414488

415489
public ICMPTaskConfig(params string[] raw)
416490
{
@@ -423,6 +497,8 @@ public ICMPTaskConfig(params string[] raw)
423497
if (raw.Length > 2 && int.TryParse(raw[2], out int parsedTimeout)) Timeout = parsedTimeout;
424498
if (raw.Length > 3 && int.TryParse(raw[3], out int parsedMaxRecentPackets)) MaxRecentPackets = parsedMaxRecentPackets;
425499
if (raw.Length > 4 && int.TryParse(raw[4], out int parsedSignificantDelayThreshold)) SignificantDelayThreshold = parsedSignificantDelayThreshold;
500+
if (raw.Length > 5 && double.TryParse(raw[5], out double parsedPacketLossDuration)) PacketLossDuration = TimeSpan.FromSeconds(parsedPacketLossDuration);
501+
if (raw.Length > 6 && int.TryParse(raw[6], out int parsedPacketLossCount)) PacketLossCount = parsedPacketLossCount;
426502
}
427503

428504
private static bool IsValidIP(string ip)
@@ -457,9 +533,9 @@ static void Main(string[] args)
457533
{
458534
ICMPMonitor monitor = new(CTS, Logging);
459535
string[] configLines = File.ReadAllLines(configPath).Select(line => line.Trim()).Where(line => line.Split(' ').Length > 1).ToArray();
460-
foreach (var line in configLines.Select(line => new ICMPTaskConfig(line.Split(' '))))
536+
foreach (var item in configLines.Select(line => new ICMPTaskConfig(line.Split(' '))))
461537
{
462-
monitor.AddHost(new(CTS, line));
538+
monitor.AddHost(new(CTS, item));
463539
}
464540
AnsiConsole.Clear();
465541
AnsiConsole.WriteLine();
@@ -480,18 +556,11 @@ static void Main(string[] args)
480556
CTS.Cancel();
481557
break;
482558
}
483-
if (key.Key == ConsoleKey.L)
484-
{
485-
foreach (var task in monitor.Tasks.FindAll(i => !i.IsWarning))
486-
{
487-
task.LastLog = $"丢包率 {task.RecentLossRate:F2}%";
488-
}
489-
}
490559
if (key.Key == ConsoleKey.C)
491560
{
492561
foreach (var task in monitor.Tasks.FindAll(i => i.IsWarning))
493562
{
494-
task.Warnings.Dequeue();
563+
_ = task.Warnings.DequeueAsync();
495564
}
496565
}
497566
}

0 commit comments

Comments
 (0)