C#实现Windows服务与IIS应用程序池自动监测重启工具 简介这是一套面向运维人员与C# Winform开发者的Windows服务与IIS网站实时监测源码项目针对业务网站或服务因不确定因素停止运行、影响业务却一时难以根治的痛点提供自动重启的临时救场方案。项目基于.NET Framework 4与Visual Studio 2022开发包含可直接运行的监测程序与完整工程源码支持二次开发。压缩包共70个文件约373KB以23个cs源码文件为核心辅以exe、dll、pdb等可执行与调试文件以及config、xml、resx、txt等配置与资源文件并附README说明。内容涵盖Winform窗体程序使用、IIS网站与应用程序池操作帮助类、Windows服务操作帮助类、文件操作类与日志操作类既能用于日常项目运维也可作为Winform入门学习范例。目前已有114人学习下载适合需要快速搭建服务与网站守护机制、并希望理解其实现思路的开发者参考借鉴。1. 凌晨三点被告警叫醒后我决定拆一个 Windows 服务与 IIS 监测的源码凌晨三点手机告警响了某台业务服务器上的 Windows 服务停了网站 502。爬起来连上去手动net start一下两分钟恢复但人已经清醒了。这种场景做运维的都懂——根因一时半会儿查不出来业务不能停你需要一个「临时救场」的机制服务或应用程序池一挂自动拉起来先保住业务再慢慢排查。servicecheck.zip就是干这个的一个基于 C# / .NET Framework 4 的 Winform 程序能实时监测 Windows 服务和 IIS 网站含应用程序池停止后自动重启同时附带了服务操作、IIS 操作、文件操作、日志操作等帮助类源码工程完整可以直接编译使用也能二次开发。适合做运维工具、做 Winform 入门练手或者需要一个轻量级守护进程但不想引入 Zabbix、Prometheus 那套重方案的场景。2. 拆开 servicecheck.zip工程结构、技术栈与监测对象2.1 从目录树看这个工程的组织方式拿到servicecheck.zip解压后目录结构大致是这样servicecheck/ ├── ServiceCheck.sln ├── ServiceCheck/ │ ├── ServiceCheck.csproj │ ├── app.config │ ├── Program.cs │ ├── Model/ │ ├── Kernal/ │ ├── Properties/ │ ├── Image/ │ ├── AllForms/ │ └── Global/ ├── README.md └── .vs/.vs是 Visual Studio 的本地缓存目录可以忽略。核心在ServiceCheck/下Program.cs是入口AllForms/放窗体Model/放数据模型Kernal/原文拼写如此实际就是 Kernel放核心逻辑Global/放全局变量或公共方法。这种分层不算复杂但胜在清晰——窗体归窗体模型归模型核心逻辑单独抽出来二次开发时改哪块很清楚。技术栈是 C# .NET Framework 4 Winform用 Visual Studio 2022 打开ServiceCheck.sln就能编译。注意是 .NET Framework 4不是 .NET Core / .NET 6所以别指望跨平台跑在 Linux 上它就是给 Windows Server 用的。2.2 监测对象Windows 服务和 IIS 应用程序池的区别这个项目监测两类东西机制完全不同得分开说。Windows 服务通过System.ServiceProcess.ServiceController类操作。判断服务是否运行看Status属性等于ServiceControllerStatus.Running就是正常等于Stopped就是挂了。重启就是先Stop()再Start()或者直接Start()如果已经是 Stopped 状态。IIS 网站和应用程序池这个不能直接用 .NET 自带的类得走Microsoft.Web.Administration这个库IIS 7 及以上提供。应用程序池的状态通过ApplicationPool.State判断等于ObjectState.Started是正常。网站本身的状态则要看它绑定的应用程序池是否在跑以及网站是否Started。为什么要区分因为很多人以为「网站停了」就是网站的问题实际上大部分时候是它背后的应用程序池挂了网站跟着 503。所以监测逻辑里应用程序池的状态判断比网站本身更关键。2.3 编译前必须确认的两件事在 VS 2022 里打开工程后别急着 F5先确认两件事第一目标框架。右键项目 → 属性 → 应用程序 → 目标框架确认是.NET Framework 4或更高4.5、4.6 都行但别降到 3.5Microsoft.Web.Administration用不了。第二引用。检查ServiceCheck.csproj里的引用是否完整尤其是System.ServiceProcess和Microsoft.Web.Administration。后者如果缺失需要手动添加# 在 VS 的 Package Manager Console 里执行 Install-Package Microsoft.Web.Administration或者直接在「引用」上右键 → 添加引用 → 程序集 → 扩展找到Microsoft.Web.Administration勾上。提示Microsoft.Web.Administration需要目标机器安装了 IIS 管理工具IIS Management Console。如果服务器上只装了 IIS 但没装管理工具这个库会报FileNotFoundException。装 IIS 时勾上「IIS 管理控制台」即可。编译通过后bin/Debug/或bin/Release/下会生成ServiceCheck.exe可以直接拷到目标服务器上跑。但注意操作 Windows 服务和 IIS 都需要管理员权限所以运行时必须右键「以管理员身份运行」否则会抛InvalidOperationException或UnauthorizedAccessException。3. 核心监测逻辑怎么判断服务挂了、怎么自动重启3.1 服务状态轮询Timer 还是后台线程这个项目用的是 Winform所以最自然的做法是System.Windows.Forms.Timer或者System.Timers.Timer。两者区别Forms.Timer跑在 UI 线程上Tick 事件里可以直接更新界面控件但如果监测逻辑耗时长了会卡界面Timers.Timer跑在线程池线程上不卡 UI但更新界面得Invoke。我一般会选System.Timers.Timer间隔设 10 到 30 秒。太短了没必要服务不会秒挂秒起太长了恢复不及时。10 秒是个比较稳的值。核心逻辑大概长这样// 监测单个 Windows 服务 private void CheckService(string serviceName) { try { using (var sc new ServiceController(serviceName)) { // 判断服务是否已停止 if (sc.Status ServiceControllerStatus.Stopped) { // 尝试启动服务 sc.Start(); // 等待服务进入 Running 状态最多等 30 秒 sc.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(30)); WriteLog($服务 {serviceName} 已自动重启); } } } catch (InvalidOperationException ex) { // 服务不存在或权限不足 WriteLog($服务 {serviceName} 操作失败{ex.Message}); } catch (System.ServiceProcess.TimeoutException ex) { // WaitForStatus 超时 WriteLog($服务 {serviceName} 启动超时{ex.Message}); } }逻辑说明ServiceController构造时传入服务名不是显示名是服务名在服务属性里能看到。Status属性是实时查询的不是缓存值。Start()之后用WaitForStatus等待状态变更避免刚 Start 完就判断还是 Stopped 导致重复启动。超时设 30 秒是因为有些服务启动确实慢尤其是依赖数据库或网络的服务。参数说明serviceName从配置文件或界面上读取建议做成可配置的列表而不是硬编码。WaitForStatus的超时时间根据实际服务调整数据库类服务可以放到 60 秒。3.2 IIS 应用程序池的自动重启应用程序池的重启比 Windows 服务简单因为 IIS 提供了Recycle()方法// 监测并重启 IIS 应用程序池 private void CheckAppPool(string poolName) { try { using (var manager new ServerManager()) { var pool manager.ApplicationPools[poolName]; if (pool null) { WriteLog($应用程序池 {poolName} 不存在); return; } // 判断应用程序池是否已停止 if (pool.State ObjectState.Stopped) { pool.Start(); WriteLog($应用程序池 {poolName} 已自动启动); } else if (pool.State ObjectState.Started) { // 可选如果进程假死但状态还是 Started可以强制回收 // pool.Recycle(); } } } catch (Exception ex) { WriteLog($应用程序池 {poolName} 操作失败{ex.Message}); } }逻辑说明ServerManager是Microsoft.Web.Administration的入口类每次操作完要Dispose所以用using。ApplicationPools[poolName]按名称索引名称区分大小写。State是ObjectState枚举常见值有Started、Stopped、Starting、Stopping。参数说明poolName要和 IIS 管理器里看到的应用程序池名称完全一致。如果应用程序池状态是Started但网站还是 503那可能是工作进程假死这时候可以考虑调Recycle()强制回收但别频繁调回收会导致当前请求中断。3.3 日志记录别等出事了才后悔没记日志这个项目带了日志操作类这是很关键的一环。自动重启这种机制最怕的就是「默默重启了但没人知道」结果问题被掩盖了。所以每次重启操作都必须记日志包括时间、对象名称、操作类型启动/重启、操作结果成功/失败、失败原因。日志格式建议用简单的文本追加别搞太复杂// 简单的日志写入方法 private static readonly object LogLock new object(); public static void WriteLog(string message) { lock (LogLock) { string logPath Path.Combine(AppDomain.CurrentDomain.BaseDirectory, logs); if (!Directory.Exists(logPath)) Directory.CreateDirectory(logPath); string fileName $servicecheck_{DateTime.Now:yyyyMMdd}.log; string fullPath Path.Combine(logPath, fileName); string line ${DateTime.Now:yyyy-MM-dd HH:mm:ss} | {message}{Environment.NewLine}; File.AppendAllText(fullPath, line, Encoding.UTF8); } }逻辑说明lock是为了防止多线程同时写文件导致内容错乱。按天分文件方便排查。AppDomain.CurrentDomain.BaseDirectory取的是 exe 所在目录这样不管从哪启动日志都在程序目录下。参数说明日志路径可以做成可配置的但默认放程序目录下最省事。编码用 UTF8避免中文乱码。4. 避坑与排查那些我踩过的坑4.1 服务名和显示名搞混导致找不到服务现象代码里写的是「SQL Server (MSSQLSERVER)」运行时报「服务不存在」。原因ServiceController构造函数接收的是服务名Service Name不是显示名Display Name。在服务管理器里看到的那一列是显示名服务名要右键属性才能看到。解决用sc query命令或者 PowerShell 的Get-Service | Select-Object Name, DisplayName确认服务名。代码里统一用服务名。4.2 权限不足操作被拒绝现象程序在开发机上跑得好好的拷到服务器上就报UnauthorizedAccessException或InvalidOperationException。原因操作 Windows 服务和 IIS 都需要管理员权限。开发机上 VS 是以管理员身份运行的所以没感觉服务器上双击运行就是普通权限。解决右键 exe → 以管理员身份运行。或者修改 exe 的兼容性设置勾选「以管理员身份运行此程序」。更彻底的做法是在app.manifest里声明requireAdministrator。4.3 应用程序池状态是 Started 但网站还是 503现象监测程序显示应用程序池正常但网站访问返回 503。原因应用程序池的State是Started但工作进程w3wp.exe可能已经假死或者被回收了但没起来。这种情况State不会变。解决这种情况光靠状态判断不够需要加一层「健康检查」——比如定期发一个 HTTP 请求到网站的健康检查接口如果连续失败 N 次就强制Recycle()应用程序池。这个项目本身没带 HTTP 检查但可以自己扩展。4.4 服务启动超时WaitForStatus 抛异常现象调用WaitForStatus时抛TimeoutException但服务实际上后来起来了。原因有些服务启动确实慢尤其是依赖数据库、网络或其它服务的。默认超时时间不够。解决把WaitForStatus的超时时间调大比如 60 秒甚至 120 秒。同时捕获TimeoutException记录日志但不要当成致命错误——服务可能只是慢不是起不来。4.5 日志文件被占用写入失败现象日志写不进去报IOException。原因多个线程同时写同一个文件或者日志文件被其它程序比如日志采集工具打开了。解决用lock保证单线程写入。如果还是被占用可以考虑用FileStream以FileShare.ReadWrite方式打开或者换用NLog、log4net这类成熟的日志库。5. 进阶用法把监测程序做成 Windows 服务以及验证它真的在工作5.1 让监测程序自己变成 Windows 服务现在这个程序是个 Winform得手动开着窗口才能跑。但运维场景下你不可能一直开着远程桌面。更合理的做法是把它改成一个 Windows 服务开机自启后台运行。改造方法用sc create命令把 exe 注册成服务# 以管理员身份运行 cmd sc create ServiceCheck binPath C:\path\to\ServiceCheck.exe start auto sc description ServiceCheck 监测 Windows 服务和 IIS 应用程序池停止后自动重启 sc start ServiceCheck但注意Winform 程序直接注册成服务会有问题——它没有实现ServiceBaseSCM服务控制管理器无法正确控制它。所以更稳妥的做法是新建一个 Windows Service 项目把监测逻辑放到OnStart里用Timer定期执行。或者用NSSMNon-Sucking Service Manager这类工具把普通 exe 包装成服务。我一般会选后者因为改动最小。NSSM 的用法# 下载 nssm.exe 后 nssm install ServiceCheck C:\path\to\ServiceCheck.exe nssm set ServiceCheck AppDirectory C:\path\to nssm set ServiceCheck Start SERVICE_AUTO_START nssm start ServiceCheck这样程序就在后台跑了开机自启崩溃了 NSSM 还会帮你拉起来。5.2 验证监测是否真的生效部署完了别就不管了得验证。验证方法很简单手动把被监测的服务停掉等一个监测周期比如 10 秒看它是不是自动起来了。# 停掉服务 net stop YourServiceName # 等 15 秒 timeout /t 15 # 查看服务状态 sc query YourServiceName如果STATE显示RUNNING说明监测生效了。同时去看日志文件应该有对应的重启记录。对于 IIS 应用程序池可以在 IIS 管理器里手动停止应用程序池然后观察是否自动启动。或者用 PowerShell# 停止应用程序池 Stop-WebAppPool -Name YourAppPoolName # 等 15 秒 Start-Sleep -Seconds 15 # 查看状态 Get-WebAppPoolState -Name YourAppPoolName如果返回Started说明生效了。5.3 一个容易忽略的细节监测频率和重启次数的平衡监测频率设太短比如 1 秒服务刚停就重启可能服务本身还在停止过程中导致Start()失败。设太长比如 5 分钟业务中断时间太久。10 到 30 秒是个比较稳的区间。另外如果某个服务反复挂监测程序会反复重启它。这时候需要加一个「重启次数限制」——比如 10 分钟内重启超过 5 次就停止自动重启并告警因为这说明问题不是「临时救场」能解决的了得人工介入。这个逻辑可以在监测代码里加一个计数器// 简单的重启频率控制 private Dictionarystring, QueueDateTime _restartHistory new Dictionarystring, QueueDateTime(); private bool CanRestart(string serviceName) { if (!_restartHistory.ContainsKey(serviceName)) _restartHistory[serviceName] new QueueDateTime(); var history _restartHistory[serviceName]; var now DateTime.Now; // 移除 10 分钟前的记录 while (history.Count 0 (now - history.Peek()).TotalMinutes 10) history.Dequeue(); // 10 分钟内重启超过 5 次不再自动重启 if (history.Count 5) return false; history.Enqueue(now); return true; }逻辑说明用Queue记录每次重启的时间每次判断前先清理超过 10 分钟的旧记录然后看剩余数量是否达到阈值。达到阈值就返回false调用方记录告警日志并跳过本次重启。参数说明10 分钟和 5 次是经验值可以根据实际服务的稳定性调整。对于特别稳定的服务可以放宽到 3 次对于本身就不太稳定的服务可以放到 10 次。从那以后我每次部署这类监测程序都会先手动停一次服务验证它真的能拉起来再去看日志确认记录完整最后才放心离开。希望帮到你。本文还有配套的精品资源点击获取