背景

远程操控位于家中的电脑时,偶尔会遇到路由器或光猫当机的情况,网络突然断开,电脑却还没关机,只能等待下次断电路由器或光猫重启才能重新连接上电脑,这就非常耗电,于是有了这个小工具!

过程

由于本人十分的菜,尽管也曾憧憬过成为一个程序员,但一直三分热度、半途而废,到最后不了了之。所以你可以猜到代码并不是我写的。

代码由 Gemini 提供思路,DeepSeek 写成,经历了大约三天的时间,期间不断的请 AI 修改、编译,手动测试,最后终于能够成功运行。经初步短期测试,应该没有什么大问题,故分享出来,代码NetIdleShutdown.cs如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
using System;
using System.Diagnostics;
using System.Net.NetworkInformation;
using System.Threading;
using System.Runtime.InteropServices;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Linq;

namespace NetIdleShutdown
{
/// <summary>
/// 网络空闲关机程序
///
/// 主要功能:
/// 1. 检测网络连接状态(双重检测:Ping + HTTP)
/// 2. 检测用户活动状态
/// 3. 当网络断开且用户无操作时,自动执行关机操作
///
/// 检测逻辑:
/// - 使用并行线程同时检测多个目标,提高检测速度
/// - 如果所有Ping目标都失败,则直接判断为网络离线
/// - 如果有至少一个Ping成功,则继续进行HTTP检测
/// - 必须同时满足Ping和HTTP检测都至少有一个成功,才判断为在线
///
/// 配置说明:
/// - 程序默认配置在AppConfig类中
/// - 可修改默认值或通过构造函数传入自定义配置
/// </summary>
public class NetIdleShutdown : IDisposable
{
#region 配置类
/// <summary>
/// 应用程序配置类
/// 包含所有可配置的参数和检测目标列表
/// </summary>
public class AppConfig
{
// 核心配置参数
public int MaxNoNetMinutes = 3; // 无网络多少分钟后关机
public int PingTimeoutMs = 1500; // Ping超时时间(毫秒)- 控制线程等待时间
public int HttpTimeoutMs = 5000; // HTTP请求超时时间(毫秒)- 控制线程等待时间
public int CheckIntervalSeconds = 60; // 检测间隔时间(秒)- 主循环等待时间
public int UserIdleThresholdSeconds = 60; // 用户空闲时间阈值(秒)- 判定用户无活动的阈值

// 检测目标列表
public List<string> PingTargets; // Ping检测目标IP地址列表
public List<string> HttpTargets; // HTTP检测目标URL列表

/// <summary>
/// 构造函数,初始化默认配置和检测目标
/// </summary>
public AppConfig()
{
// 初始化Ping检测目标列表
// 包含国内外常用的公共DNS服务器,提高检测可靠性
PingTargets = new List<string>
{
"223.5.5.5", // 阿里DNS - 国内常用
"119.29.29.29", // 腾讯DNS - 国内常用
"180.76.76.76", // 百度DNS - 国内常用
"1.1.1.1", // Cloudflare DNS - 国际常用
"8.8.8.8", // Google DNS - 国际常用
"114.114.114.114", // 114DNS - 国内常用
"208.67.222.222", // OpenDNS - 国际备用
"9.9.9.9" // Quad9 DNS - 国际备用
};

// 初始化HTTP检测目标列表
// 使用各大公司的网络连通性检测服务,通常返回204状态码
HttpTargets = new List<string>
{
"http://connect.rom.miui.com/generate_204", // 小米网络检测服务
"http://connectivitycheck.gstatic.com/generate_204", // Google网络检测服务
"http://www.gstatic.com/generate_204", // Google备用检测服务
"http://www.msftncsi.com/ncsi.txt", // Microsoft网络检测服务
"http://captive.apple.com/hotspot-detect.html", // Apple网络检测服务
"http://connectivitycheck.platform.hicloud.com/generate_204" // 华为网络检测服务
};
}
}
#endregion

#region Windows API声明
/// <summary>
/// 获取系统最后输入时间的Windows API函数
/// 用于检测用户活动状态
/// </summary>
/// <param name="plii">LASTINPUTINFO结构体引用</param>
/// <returns>获取成功返回true,失败返回false</returns>
[DllImport("user32.dll")]
private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);

/// <summary>
/// 最后输入信息结构体
/// 用于存储用户最后输入的时间信息
/// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct LASTINPUTINFO
{
public uint cbSize; // 结构体大小
public uint dwTime; // 最后输入时间(系统启动后的毫秒数)
}
#endregion

#region 私有字段
private readonly AppConfig _config; // 程序配置实例
private int _noNetCounter = 0; // 无网络计数器
private bool _isRunning = true; // 程序运行标志
private readonly object _counterLock = new object(); // 计数器同步锁
#endregion

#region 构造函数
/// <summary>
/// 构造函数
/// </summary>
/// <param name="config">自定义配置,如果为null则使用默认配置</param>
public NetIdleShutdown(AppConfig config = null)
{
// 使用传入的配置或创建默认配置
_config = config ?? new AppConfig();

// 记录配置信息
Console.WriteLine("[SYSTEM] 程序配置已加载");
Console.WriteLine("[SYSTEM] 无网络关机阈值: " + _config.MaxNoNetMinutes + " 分钟");
Console.WriteLine("[SYSTEM] 检测间隔: " + _config.CheckIntervalSeconds + " 秒");
Console.WriteLine("[SYSTEM] 用户空闲阈值: " + _config.UserIdleThresholdSeconds + " 秒");
}
#endregion

#region 主程序入口
/// <summary>
/// 启动网络守护程序主循环
/// </summary>
public void Run()
{
try
{
// 显示程序标题和配置信息
PrintBanner();

// 主循环:持续检测网络和用户状态
while (_isRunning)
{
// 执行一次完整的检测周期
PerformCheckCycle();

// 等待指定的间隔时间,每秒检查一次是否停止
for (int i = 0; i < _config.CheckIntervalSeconds && _isRunning; i++)
{
Thread.Sleep(1000);
}
}
}
catch (Exception ex)
{
// 记录严重错误并重新抛出
LogError("程序运行异常", ex);
throw;
}
}

/// <summary>
/// 停止守护程序
/// 设置运行标志为false,使主循环退出
/// </summary>
public void Stop()
{
_isRunning = false;
LogInfo("程序已收到停止指令");
}
#endregion

#region 检测周期
/// <summary>
/// 执行一个完整的检测周期
/// 包括网络检测、用户活动检测和状态处理
/// </summary>
private void PerformCheckCycle()
{
var timestamp = DateTime.Now;
LogInfo("开始第 " + (_noNetCounter + 1) + " 次检测");

// 检测网络连接状态
bool isOnline = CheckConnectivity();

// 根据网络状态进行相应处理
if (isOnline)
{
HandleOnlineStatus(timestamp);
}
else
{
HandleOfflineStatus(timestamp);
}
}

/// <summary>
/// 处理在线状态
/// 网络正常时重置计数器并记录状态
/// </summary>
/// <param name="timestamp">检测时间戳</param>
private void HandleOnlineStatus(DateTime timestamp)
{
lock (_counterLock)
{
// 如果之前有离线记录,则重置计数器
if (_noNetCounter > 0)
{
LogInfo("网络恢复正常,无网络计数器已重置");
_noNetCounter = 0;
}
}
LogInfo("网络状态: 在线");
}

/// <summary>
/// 处理离线状态
/// 网络断开时检查用户活动并更新计数器
/// </summary>
/// <param name="timestamp">检测时间戳</param>
private void HandleOfflineStatus(DateTime timestamp)
{
// 检测用户是否有输入活动
bool hasUserActivity = HasUserInputActivity();

if (hasUserActivity)
{
// 有用户活动,重置计数器
lock (_counterLock)
{
if (_noNetCounter > 0)
{
LogInfo("检测到用户活动,无网络计数器已重置");
_noNetCounter = 0;
}
}
LogInfo("网络状态: 离线 (用户活动中,不计数)");
}
else
{
// 无用户活动,增加计数器
lock (_counterLock)
{
_noNetCounter++;
}

LogInfo("网络状态: 离线");
LogInfo("无网络计数器: " + _noNetCounter + "/" + _config.MaxNoNetMinutes);

// 检查是否达到关机条件
if (_noNetCounter >= _config.MaxNoNetMinutes)
{
ExecuteShutdown();
}
}
}
#endregion

#region 网络检测
/// <summary>
/// 检测网络连接状态
///
/// 检测逻辑:
/// 1. 并行执行Ping检测
/// 2. 如果所有Ping目标都失败,直接判断为离线
/// 3. 如果至少有一个Ping成功,则继续进行HTTP检测
/// 4. 必须同时满足Ping和HTTP检测都至少有一个成功,才判断为在线
///
/// 优化:网络完全断开时,最快800ms即可判断为离线
/// </summary>
/// <returns>true表示网络在线,false表示网络离线</returns>
private bool CheckConnectivity()
{
LogInfo("开始网络连通性检测");

// 第一步:并行执行Ping检测
bool pingSuccess = PerformPingTestsParallel();

// 第二步:如果Ping检测全部失败,直接判断为离线
if (!pingSuccess)
{
LogInfo("Ping检测结果: 全部失败,直接判定为网络离线");
return false;
}

// 第三步:Ping检测通过,继续进行HTTP检测
LogInfo("Ping检测结果: 至少有一个成功,开始HTTP检测");
bool httpSuccess = PerformHttpTestsParallel();

// 第四步:必须同时满足Ping和HTTP检测都成功
bool finalResult = pingSuccess && httpSuccess;

// 记录详细检测结果
if (finalResult)
{
LogInfo("网络检测最终结果: 在线 (Ping和HTTP检测均通过)");
}
else
{
LogInfo("网络检测最终结果: 离线 (Ping: " + (pingSuccess ? "成功" : "失败") +
", HTTP: " + (httpSuccess ? "成功" : "失败") + ")");
}

return finalResult;
}

/// <summary>
/// 并行执行Ping测试
///
/// 实现方式:
/// 1. 为每个Ping目标创建一个独立线程
/// 2. 所有线程同时执行,提高检测速度
/// 3. 使用ConcurrentDictionary存储线程安全的结果
/// 4. 使用Interlocked.Increment保证计数器的线程安全
///
/// 超时控制:
/// 每个线程最多等待PingTimeoutMs毫秒
/// 超时线程将被强制终止
/// </summary>
/// <returns>true表示至少有一个Ping成功,false表示全部失败</returns>
private bool PerformPingTestsParallel()
{
LogInfo("开始并行Ping测试 (目标数: " + _config.PingTargets.Count + ")");

int successCount = 0; // 成功计数器
var results = new ConcurrentDictionary<string, bool>(); // 结果存储
var tasks = new List<Thread>(); // 线程列表

// 为每个Ping目标创建线程
foreach (var target in _config.PingTargets)
{
var thread = new Thread(() =>
{
bool result = PingHostFast(target);
results[target] = result;
if (result)
{
Interlocked.Increment(ref successCount);
}
});

thread.Start();
tasks.Add(thread);
}

// 等待所有线程完成,最多等待PingTimeoutMs毫秒
foreach (var thread in tasks)
{
if (!thread.Join(_config.PingTimeoutMs))
{
// 线程超时,强制终止
thread.Abort();
}
}

// 输出Ping测试详细结果
var resultBuilder = new StringBuilder();
resultBuilder.AppendLine("PING检测详细结果:");
foreach (var target in _config.PingTargets)
{
bool result;
if (results.TryGetValue(target, out result))
{
string status = result ? "成功" : "失败";
resultBuilder.AppendLine(" " + target.PadRight(15) + " => " + status);
}
else
{
resultBuilder.AppendLine(" " + target.PadRight(15) + " => 超时");
}
}

LogInfo(resultBuilder.ToString());
LogInfo("PING检测统计: 成功 " + successCount + "/" + _config.PingTargets.Count);

return successCount > 0;
}

/// <summary>
/// 并行执行HTTP测试
///
/// 检测原理:
/// 使用HTTP HEAD方法请求网络连通性检测服务
/// 这些服务通常返回204状态码或2xx状态码
/// 只要返回2xx状态码就认为成功
/// </summary>
/// <returns>true表示至少有一个HTTP请求成功,false表示全部失败</returns>
private bool PerformHttpTestsParallel()
{
LogInfo("开始并行HTTP测试 (目标数: " + _config.HttpTargets.Count + ")");

int successCount = 0;
var results = new ConcurrentDictionary<string, bool>();
var tasks = new List<Thread>();

// 为每个HTTP目标创建线程
foreach (var url in _config.HttpTargets)
{
var thread = new Thread(() =>
{
bool result = TestHttpConnectionFast(url);
results[url] = result;
if (result)
{
Interlocked.Increment(ref successCount);
}
});

thread.Start();
tasks.Add(thread);
}

// 等待所有线程完成,最多等待HttpTimeoutMs毫秒
foreach (var thread in tasks)
{
if (!thread.Join(_config.HttpTimeoutMs))
{
// 线程超时,强制终止
thread.Abort();
}
}

// 输出HTTP测试详细结果
var resultBuilder = new StringBuilder();
resultBuilder.AppendLine("HTTP检测详细结果:");
foreach (var url in _config.HttpTargets)
{
bool result;
if (results.TryGetValue(url, out result))
{
string status = result ? "成功" : "失败";
resultBuilder.AppendLine(" " + url + " => " + status);
}
else
{
resultBuilder.AppendLine(" " + url + " => 超时");
}
}

LogInfo(resultBuilder.ToString());
LogInfo("HTTP检测统计: 成功 " + successCount + "/" + _config.HttpTargets.Count);

return successCount > 0;
}

/// <summary>
/// 快速Ping单个主机
///
/// 优化点:
/// 1. 使用较短的超时时间(800ms)加快检测速度
/// 2. 每次创建新的Ping实例,避免资源竞争
/// 3. 简化异常处理,只返回成功/失败
/// </summary>
/// <param name="host">目标主机IP地址</param>
/// <returns>true表示Ping成功,false表示失败或超时</returns>
private bool PingHostFast(string host)
{
try
{
using (var ping = new Ping())
{
// 使用较短的超时时间加快检测速度
var reply = ping.Send(host, 800);
return reply != null && reply.Status == IPStatus.Success;
}
}
catch (Exception)
{
// 发生任何异常都视为失败
return false;
}
}

/// <summary>
/// 快速测试HTTP连接
///
/// 技术要点:
/// 1. 使用HEAD方法,只获取响应头,不下载内容
/// 2. 设置较短的超时时间(2000ms)加快检测速度
/// 3. 设置合适的User-Agent,避免被某些服务拒绝
/// 4. 关闭KeepAlive,避免连接池影响
/// </summary>
/// <param name="url">目标URL地址</param>
/// <returns>true表示HTTP请求成功,false表示失败或超时</returns>
private bool TestHttpConnectionFast(string url)
{
try
{
// 创建HTTP请求
var request = WebRequest.Create(url) as HttpWebRequest;
if (request == null)
{
return false;
}

// 配置请求参数
request.Timeout = 2000; // 2秒超时
request.Method = "HEAD"; // 使用HEAD方法
request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
request.Accept = "*/*";
request.KeepAlive = false;

// 发送请求并获取响应
using (var response = request.GetResponse() as HttpWebResponse)
{
if (response != null)
{
// 2xx状态码都视为成功
return (int)response.StatusCode >= 200 && (int)response.StatusCode <= 299;
}
return false;
}
}
catch (WebException ex)
{
// WebException特殊处理:尝试获取响应状态码
if (ex.Response != null)
{
using (var response = ex.Response as HttpWebResponse)
{
if (response != null)
{
// 即使发生WebException,如果返回2xx状态码也算成功
return (int)response.StatusCode >= 200 && (int)response.StatusCode <= 299;
}
}
}
return false;
}
catch (Exception)
{
// 其他异常都视为失败
return false;
}
}
#endregion

#region 用户活动检测
/// <summary>
/// 检测用户输入活动
///
/// 检测原理:
/// 1. 通过Windows API获取系统最后输入时间
/// 2. 计算当前时间与最后输入时间的差值
/// 3. 如果差值小于阈值,则认为用户有活动
///
/// 注意:只检测键盘和鼠标输入活动
/// </summary>
/// <returns>true表示用户有输入活动,false表示用户空闲</returns>
private bool HasUserInputActivity()
{
try
{
uint idleTimeMs = GetUserIdleTime();
uint thresholdMs = (uint)_config.UserIdleThresholdSeconds * 1000;
bool hasActivity = idleTimeMs < thresholdMs;

// 记录详细的空闲时间信息
LogInfo("用户活动检测: " + (hasActivity ? "有活动" : "无活动") +
" (空闲时间: " + (idleTimeMs / 1000) + " 秒)");

return hasActivity;
}
catch (Exception ex)
{
// 用户活动检测失败时,默认返回无活动(保守策略)
LogError("用户活动检测异常", ex);
return false;
}
}

/// <summary>
/// 获取用户空闲时间(毫秒)
///
/// 实现方式:
/// 1. 调用GetLastInputInfo获取系统最后输入时间
/// 2. 计算当前系统运行时间与最后输入时间的差值
/// 3. 返回用户空闲的毫秒数
///
/// 注意:Environment.TickCount在49.7天后会回绕,但这里影响不大
/// </summary>
/// <returns>用户空闲时间(毫秒)</returns>
private uint GetUserIdleTime()
{
var lastInputInfo = new LASTINPUTINFO();
lastInputInfo.cbSize = (uint)Marshal.SizeOf(lastInputInfo);

if (GetLastInputInfo(ref lastInputInfo))
{
// 计算空闲时间 = 当前时间 - 最后输入时间
return (uint)Environment.TickCount - lastInputInfo.dwTime;
}

// API调用失败时返回最大值,表示用户已长时间空闲
return uint.MaxValue;
}
#endregion

#region 关机操作
/// <summary>
/// 执行关机操作
///
/// 执行步骤:
/// 1. 检查管理员权限
/// 2. 使用shutdown.exe执行关机命令
/// 3. 设置立即关机(/s /f /t 0)
///
/// 参数说明:
/// /s: 关机
/// /f: 强制关闭所有应用程序
/// /t 0: 延迟0秒
/// </summary>
private void ExecuteShutdown()
{
try
{
LogWarning("已达到关机条件,准备执行关机操作");

// 权限检查:关机需要管理员权限
if (!IsRunningAsAdministrator())
{
LogError("执行关机操作需要管理员权限,请以管理员身份运行本程序");
return;
}

// 配置关机命令参数
var psi = new ProcessStartInfo
{
FileName = "shutdown.exe",
Arguments = "/s /f /t 0",
CreateNoWindow = true,
UseShellExecute = false
};

// 执行关机命令
using (var process = Process.Start(psi))
{
// 等待命令执行
Thread.Sleep(1000);
}

LogWarning("关机命令已发送,系统将立即关闭");
Stop(); // 停止程序主循环
}
catch (Exception ex)
{
LogError("执行关机操作失败", ex);
}
}

/// <summary>
/// 检查是否以管理员权限运行
///
/// 实现方式:
/// 1. 获取当前Windows身份标识
/// 2. 检查是否属于Administrators组
///
/// 注意:Windows Vista及以上版本需要UAC提升
/// </summary>
/// <returns>true表示以管理员权限运行,false表示不是管理员</returns>
private bool IsRunningAsAdministrator()
{
try
{
var identity = System.Security.Principal.WindowsIdentity.GetCurrent();
var principal = new System.Security.Principal.WindowsPrincipal(identity);
return principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
}
catch
{
// 权限检查失败时,默认返回false(保守策略)
return false;
}
}
#endregion

#region 日志输出
/// <summary>
/// 输出程序标题和配置信息
/// </summary>
private void PrintBanner()
{
Console.WriteLine("===============================================");
Console.WriteLine("网络空闲关机程序 - NetIdleShutdown");
Console.WriteLine("版本: 1.0");
Console.WriteLine("作者: System");
Console.WriteLine("===============================================");
Console.WriteLine("功能说明:");
Console.WriteLine("- 当网络断开且用户无操作时,自动执行关机");
Console.WriteLine("- 检测策略: 双重检测 (Ping + HTTP)");
Console.WriteLine("- 检测方式: 并行多线程检测");
Console.WriteLine("===============================================");
Console.WriteLine("当前配置:");
Console.WriteLine("- 无网络关机阈值: " + _config.MaxNoNetMinutes + " 分钟");
Console.WriteLine("- 检测间隔: " + _config.CheckIntervalSeconds + " 秒");
Console.WriteLine("- 用户空闲阈值: " + _config.UserIdleThresholdSeconds + " 秒");
Console.WriteLine("- Ping检测目标: " + _config.PingTargets.Count + " 个");
Console.WriteLine("- HTTP检测目标: " + _config.HttpTargets.Count + " 个");
Console.WriteLine("===============================================");
Console.WriteLine("");
}

/// <summary>
/// 记录信息级别日志
/// 用于记录程序正常运行状态信息
/// </summary>
/// <param name="message">日志消息</param>
private void LogInfo(string message)
{
Console.WriteLine("[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "] [INFO] " + message);
}

/// <summary>
/// 记录警告级别日志
/// 用于记录需要注意但不影响程序继续运行的情况
/// </summary>
/// <param name="message">日志消息</param>
private void LogWarning(string message)
{
Console.WriteLine("[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "] [WARN] " + message);
}

/// <summary>
/// 记录错误级别日志
/// 用于记录程序运行中的错误和异常
/// </summary>
/// <param name="message">日志消息</param>
/// <param name="ex">异常对象(可选)</param>
private void LogError(string message, Exception ex = null)
{
Console.WriteLine("[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "] [ERROR] " + message);
if (ex != null)
{
Console.WriteLine("[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "] [ERROR] 异常类型: " + ex.GetType().Name);
Console.WriteLine("[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "] [ERROR] 异常消息: " + ex.Message);
}
}
#endregion

#region 资源清理
/// <summary>
/// 释放程序资源
/// 实现IDisposable接口,支持using语句
/// </summary>
public void Dispose()
{
_isRunning = false;
LogInfo("程序资源已释放");
}
#endregion
}

/// <summary>
/// 程序主入口类
/// 包含Main方法和程序启动逻辑
/// </summary>
public static class Program
{
/// <summary>
/// 应用程序主入口点
///
/// 主要流程:
/// 1. 创建NetIdleShutdown实例
/// 2. 设置Ctrl+C中断处理
/// 3. 运行网络检测主循环
/// 4. 处理异常并优雅退出
/// </summary>
/// <param name="args">命令行参数(当前版本未使用)</param>
public static void Main(string[] args)
{
Console.WriteLine("[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "] [SYSTEM] 程序启动中...");

using (var shutdown = new NetIdleShutdown())
{
// 设置控制台关闭事件处理(Ctrl+C)
Console.CancelKeyPress += (sender, e) =>
{
Console.WriteLine("[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "] [SYSTEM] 收到中断信号,正在停止程序...");
shutdown.Stop();
e.Cancel = true; // 防止程序立即退出
};

try
{
Console.WriteLine("[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "] [SYSTEM] 程序已启动,开始网络监控");
shutdown.Run();
}
catch (Exception ex)
{
Console.WriteLine("[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "] [SYSTEM] 程序运行失败: " + ex.Message);
Environment.Exit(1);
}
}

Console.WriteLine("[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "] [SYSTEM] 程序已退出");
}
}
}

成品 NetIdleShutdown.cs

3 分钟版 3minNetIdleShutdown.exe

30 分钟版 30minNetIdleShutdown.exe

GitHub地址 https://github.com/utgnim/NetIdleShutdown.cs.git