
简介这是一套面向运维人员与C# Winform开发者的Windows服务与IIS网站实时监测源码项目针对业务网站或服务因不确定因素停止运行、影响业务功能的常见痛点提供停止后自动重启的临时救场方案为排查根因争取时间。项目基于C#语言与.NET Framework 4框架使用Visual Studio 2022开发包含可直接运行的监测程序与完整源码工程支持二次开发。压缩包共70个文件约373KB以23个cs源码文件为核心辅以exe可执行程序、dll类库、config配置、xml与txt说明文档、resx资源及sln解决方案等结构完整。内容涵盖Winform窗体程序使用以及IIS网站与应用程序池操作帮助类、Windows服务操作帮助类、文件操作类、日志操作类等模块既可用于日常项目运维也适合作为Winform入门学习案例。目前已有114人学习下载适合需要快速搭建服务与网站守护机制的开发者参考借鉴。1. Windows服务、IIS网站和应用程序池实时监测为什么你的运维告警总是慢半拍凌晨两点业务方电话打过来“网站打不开了。”你远程连上去一看IIS应用程序池已经停止响应超过二十分钟Windows服务里那个负责后台任务的OrderSyncService也早就挂了。可你的监控面板上一切正常——因为它只Ping了服务器IP端口通着但应用层早就凉了。这就是典型的“监测盲区”操作系统活着IIS进程活着但应用程序池的工作进程已经假死或者某个关键Windows服务悄悄停止了。市面上很多所谓“免费python源码大全”里的监控脚本大多只做端口探测或进程存活检查根本触及不到IIS应用程序池的请求队列、工作进程状态、以及Windows服务的具体运行态。今天要聊的这套实时监测源码项目核心就是解决三个层面的问题Windows服务的运行状态与启动类型、IIS网站的响应可用性、以及应用程序池的实时健康度。适合谁适合手里管着几台到几十台Windows Server、又不想为Zabbix或SCOM写复杂插件的运维和后端开发。读完你能拿到一套可落地的采集逻辑、参数配置和避坑清单。2. 监测对象拆解从服务状态到应用程序池的请求队列2.1 Windows服务监测别只看“正在运行”Windows服务的状态远不止“正在运行”和“已停止”两种。真正要抓的是四个维度StatusRunning/Stopped/Paused、StartTypeAuto/Manual/Disabled、ProcessId是否真的绑定了进程、以及ExitCode上次退出码。很多“windows无法启动windows installer服务”这类报错根源就是服务依赖项没起来或者启动账户密码过期。监测源码里通常用WMI或ServiceController类来采集但WMI查询在服务数量多的时候会有性能损耗我一般会走System.Management的ManagementObject异步查询或者直接用PowerShell的Get-Service配合Get-CimInstance。关键参数采集间隔建议30秒到60秒太短会加重WMI负担太长会漏掉瞬时崩溃。对于关键服务比如SQL Server Agent、Windows Update可以单独设置15秒的高频采集。注意StartType为Auto但状态为Stopped的服务这是最高优先级的告警项——它本该自启却没起来。2.2 IIS网站监测HTTP状态码之外的三个指标IIS网站监测如果只发一个HTTP请求看200那和普通拨测没区别。真正要抓的是CurrentConnections当前连接数、BytesSent/sec发送速率、以及ServiceUptime网站运行时长。如果CurrentConnections突然归零但网站进程还在说明应用程序池可能已经挂了。源码里一般通过IIS的Performance Counter或者Microsoft.Web.Administration命名空间来读取。// 使用 Microsoft.Web.Administration 读取网站状态 using (ServerManager serverManager new ServerManager()) { foreach (Site site in serverManager.Sites) { // 网站状态Started / Stopped / Unknown Console.WriteLine($站点: {site.Name}, 状态: {site.State}); // 应用程序池名称 string appPoolName site.Applications[/].ApplicationPoolName; Console.WriteLine($绑定应用程序池: {appPoolName}); // 绑定信息端口、主机头 foreach (Binding binding in site.Bindings) { Console.WriteLine($绑定: {binding.Protocol}://{binding.EndPoint}); } } }逻辑说明ServerManager是IIS 7及以上版本的管理入口需要引用Microsoft.Web.Administration程序集。site.State返回的是ObjectState枚举Started不代表应用程序池健康只代表网站配置已加载。参数上serverManager.Sites是只读集合遍历时不要做修改操作否则会抛InvalidOperationException。2.3 应用程序池监测请求队列是核心指标应用程序池是IIS最脆弱的环节。它的状态有Started、Stopped、Disabling、Disabled等但真正反映健康度的是RequestQueueLength请求队列长度和CurrentWorkerProcesses当前工作进程数。如果队列长度持续大于0且工作进程数为0说明应用程序池已经假死。源码里通常用System.Diagnostics.PerformanceCounter来读APP_POOL_WAS计数器组。// 读取应用程序池的请求队列长度 PerformanceCounter queueCounter new PerformanceCounter( APP_POOL_WAS, // 计数器类别 Current Requests Queued, // 计数器名称 DefaultAppPool, // 实例名称应用程序池名 true // 只读 ); // 读取工作进程数 PerformanceCounter workerCounter new PerformanceCounter( APP_POOL_WAS, Current Worker Processes, DefaultAppPool, true ); float queueLength queueCounter.NextValue(); float workerCount workerCounter.NextValue(); Console.WriteLine($队列长度: {queueLength}, 工作进程数: {workerCount});参数说明APP_POOL_WAS计数器组在IIS 7默认启用如果读不到值检查Windows Process Activation Service是否运行。NextValue()第一次调用返回0需要连续调用两次取第二次的值。实例名称就是应用程序池名称区分大小写。队列长度超过10就值得关注超过50基本可以判定应用层阻塞。3. 实时采集架构用C#还是Python以及数据怎么落盘3.1 技术选型为什么我最终选了C# WMI PerformanceCounter网上搜“免费python源码大全”能找到一堆用psutil和wmi模块写的监控脚本但Python在Windows上的WMI调用有几个硬伤一是wmi模块依赖pywin32在Server Core环境下安装麻烦二是GIL导致多线程采集时性能上不去三是长时间运行后内存泄漏比C#明显。我最后选的是C#控制台程序 定时器 异步采集编译成单个exe丢到服务器上就能跑不需要装运行时用.NET Framework 4.8Windows Server自带。核心采集循环用System.Threading.Timer每个采集项独立一个Timer避免一个慢查询拖垮整个采集周期。数据落盘用SQLite单文件、零配置、支持并发读。表结构三张ServiceStatus、SiteStatus、AppPoolStatus每张表按时间戳建索引。CREATE TABLE ServiceStatus ( Id INTEGER PRIMARY KEY AUTOINCREMENT, CollectTime DATETIME NOT NULL, ServiceName TEXT NOT NULL, DisplayName TEXT, Status TEXT, StartType TEXT, ProcessId INTEGER, ExitCode INTEGER ); CREATE INDEX idx_service_time ON ServiceStatus(CollectTime);3.2 采集频率与数据保留策略采集频率不是越密越好。Windows服务的状态变化通常以分钟级为单位30秒一次足够IIS网站和应用程序池的队列长度变化快建议15秒一次。但SQLite的写入频率要控制我一般会在内存里攒够10条再批量插入减少磁盘I/O。数据保留策略原始数据保留7天之后按小时聚合保留30天再之后按天聚合保留一年。聚合表用INSERT INTO ... SELECT配合GROUP BY做不要用触发器性能太差。如果服务器磁盘紧张可以把SQLite文件放到D:\MonitorData\下别放C盘。提示SQLite在并发写入时会锁库采集程序用单线程写入查询用只读连接加PRAGMA journal_modeWAL能显著减少锁冲突。3.3 告警触发逻辑阈值怎么设才不误报告警逻辑分三级Info记录但不通知、Warning邮件或钉钉、Critical短信或电话。Windows服务StartTypeAuto且StatusStopped直接CriticalStartTypeManual且StatusStopped记Info。IIS网站HTTP状态码非200连续3次Warning连续5次Critical。应用程序池RequestQueueLength 10持续2个采集周期Warning 50持续2个周期Critical。这里有个血泪经验不要用单次采集值触发告警。网络抖动或WMI偶发超时会导致误报。我一般会加一个“连续N次满足条件”的滑动窗口N默认3关键服务可以调到5。告警恢复也要做否则告警风暴能把人逼疯。4. 避坑与排查那些让你半夜爬起来的监测翻车现场4.1 现象WMI查询返回“拒绝访问”但账户明明是管理员原因UAC远程限制。即使你是本地管理员通过WMI远程查询时UAC会过滤掉令牌。解决在注册表HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System下把LocalAccountTokenFilterPolicy设为1DWORD然后重启WinRM服务。如果是域环境用域管理员账户跑采集程序并在防火墙里放行WMI的入站规则。4.2 现象PerformanceCounter读应用程序池时抛InvalidOperationException原因应用程序池名称拼写错误或者该应用程序池从未启动过。APP_POOL_WAS计数器组只为“曾经启动过”的应用程序池创建实例。解决先用Get-Counter -ListSet APP_POOL_WAS列出所有实例名确认名称完全一致。如果应用程序池是新建的且从未启动先手动启动一次再采集。4.3 现象采集程序运行几天后内存涨到几个G原因ManagementObjectSearcher和PerformanceCounter对象没有释放。这两个类都实现了IDisposable必须用using包裹或者在finally里调Dispose()。另外ManagementObjectCollection在遍历完后也要手动释放。我一般会在每次采集周期结束后强制GC.Collect()虽然不优雅但能压住内存。4.4 现象IIS网站监测返回200但页面实际是错误页原因只检查了HTTP状态码没检查响应内容。IIS的httpErrors配置可能把500错误重写成200的自定义错误页。解决在监测请求里加一个Keyword校验比如检查响应体里是否包含“登录”或“首页”等关键字。或者直接请求一个健康检查接口返回JSON格式的{status:ok}。4.5 现象告警邮件发不出去SMTP超时原因服务器上的SMTP端口被防火墙拦了或者用了错误的SMTP服务器。解决别用System.Net.Mail同步发送用SendMailAsync加超时。如果公司有内部邮件网关优先走内部网关。实在不行把告警写到本地日志文件用另一个独立的脚本读日志发通知解耦采集和通知。5. 进阶技巧用PowerShell做轻量级兜底监测与数据校验5.1 为什么还需要PowerShell兜底C#采集程序再稳也有挂掉的时候。我习惯在每台服务器上再放一个PowerShell脚本每5分钟跑一次做两件事一是检查采集程序进程是否存活二是直接查一遍关键服务和应用池状态和SQLite里的最新数据做比对。如果发现采集程序超过10分钟没写数据或者PowerShell查到的状态和数据库里不一致就发告警。这叫“监测的监测”。# 轻量级兜底检查脚本 $ErrorActionPreference Stop $dbPath D:\MonitorData\monitor.db $lastWriteTime (Get-Item $dbPath).LastWriteTime $minutesSinceWrite (New-TimeSpan -Start $lastWriteTime -End (Get-Date)).TotalMinutes if ($minutesSinceWrite -gt 10) { Write-EventLog -LogName Application -Source MonitorWatchdog -EntryType Error -EventId 1001 -Message 采集程序超过10分钟未写入数据最后写入时间$lastWriteTime } # 直接检查关键服务 $criticalServices (W3SVC, WAS, SQLSERVERAGENT) foreach ($svc in $criticalServices) { $service Get-Service -Name $svc -ErrorAction SilentlyContinue if ($service.Status -ne Running) { Write-EventLog -LogName Application -Source MonitorWatchdog -EntryType Error -EventId 1002 -Message 关键服务 $svc 状态异常$($service.Status) } }逻辑说明Get-Item取SQLite文件的最后写入时间如果超过10分钟没更新说明采集程序可能卡死或崩溃。Get-Service直接查服务状态不依赖WMI速度快且稳定。Write-EventLog写入Windows事件日志方便和现有运维体系集成。参数上$criticalServices数组按实际环境改W3SVC是IIS的核心服务WAS是应用程序池的支撑服务。5.2 数据校验用SQL做交叉验证采集到的数据不一定准。我一般会写几条SQL做交叉验证。比如检查同一时间点ServiceStatus表里StatusRunning的服务数量和PowerShell直接查出来的数量是否一致。如果不一致说明采集程序可能漏采或误采。-- 检查最近一次采集的服务数量是否异常 SELECT CollectTime, COUNT(*) AS TotalServices, SUM(CASE WHEN Status Running THEN 1 ELSE 0 END) AS RunningCount FROM ServiceStatus WHERE CollectTime (SELECT MAX(CollectTime) FROM ServiceStatus) GROUP BY CollectTime;如果RunningCount突然比平时少很多比如从80掉到60那要么是服务器真出问题了要么是采集程序翻车了。这时候结合PowerShell的兜底结果一起看就能快速定位。5.3 一个具体技巧用Get-Counter做实时采样对比Get-Counter是PowerShell里最被低估的命令。它可以直接读性能计数器和C#采集程序读的是同一套数据源。我习惯在排查问题时用Get-Counter手动采一次和数据库里的值对比。# 实时采样应用程序池的请求队列 Get-Counter -Counter \APP_POOL_WAS(DefaultAppPool)\Current Requests Queued -SampleInterval 1 -MaxSamples 5如果Get-Counter读出来是0但数据库里是50那说明采集程序读错了实例名或者计数器路径。如果两边都是50但网站确实打不开那说明应用程序池真的堵了得去查代码或数据库连接。这套监测方案我用了三年多从Windows Server 2012 R2到2022都跑过。最大的教训是别追求大而全先把Windows服务、IIS网站、应用程序池这三个核心指标采准告警阈值调稳比堆一堆花哨的图表有用得多。采集程序尽量用原生编译的语言写别在服务器上装一堆运行时。兜底脚本一定要有而且要和主程序走不同的技术栈避免同时翻车。希望帮到你。本文还有配套的精品资源点击获取