背景
由于历史原因,存在一些旧服务程序,无法修改其源代码,但是某个客户更换新服务器后,可能由于兼容性问题导致服务经常会掉线或报错卡死。因此,开发一个程序对指定的服务进程进行监控,在服务掉线后自动重启服务,在服务报错卡死时自动关掉服务进程并重启。
程序设计
程序是使用C#开发的一个托盘程序,主要包含操作系统进程监测,操作系统应用程序日志监测和服务程序维护三部分功能。
![](https://img.haomeiwen.com/i26433151/f673a5b636e4c612.png)
-
操作系统进程监测:定时获取当前操作系统所有正在运行的进程进行循环判断。如果被监控服务程序存在且非标记为错误进程则无需重启;如果被监控服务程序不存在或被标记为错误进程则进行重启操作。流程如下:
进程监控流程
-
操作系统应用程序日志监测:捕获操作系统应用程序中的错误日志,并根据日志信息,判断是否为所监控的服务程序,是则将服务程序名称放入错误服务程序列表中。
-
服务程序维护:使用xml文件保存需要监控的服务程序名称和路径。
程序实现
1. C#托盘程序
C#的托盘程序是使用NotifyIcon
控件来实现。主要步骤如下:
- 窗体
Load
事件中关闭窗体
private void FormMain_Load(object sender, EventArgs e)
{
Close();
}
- 窗体
FormClosing
事件中取消窗体关闭和显示NotifyIcon
控件等。注意:一定要给NotifyIcon
控件设置图标,不然电脑右下角不会显示。
private void FormMain_FormClosing(object sender, EventArgs e)
{
if (e.CloseReason != CloseReason.WindowShutDown)
{
e.Cancel = true;
Hide();
ShowInTaskbar = false;
nIconMonitor.Visible = true; // show NotifyIcon
}
}
- 根据需要添加
ContextMenuStrip
控件及相应菜单项,在NotifyIcon
的MouseDown
事件中绑定。下面代码采用右键显示菜单:
// nIconMonitor为NotifyIcon控件,cmsMonitor为ContextMenuStrip控件
private void nIconMonitor_MouseDown(object sender, MouseEventArgs e)
{
if(e.Button == MouseButtons.Right)
{
nIconMonitor.ContextMenuStrip = cmsMonitor;
}
}
2. 进程监测实现
主要使用System.Diagnostics.Process
类获取当前正在运行的所有进程,判断被监控服务程序是否存在或被标记为错误。主要代码如下:
Process[] sysProcess = Process.GetProcesses();
List<int> killIndex = new List<int>();
for (int i = 0; i < sysProcess.Length; i++)
{
string processName = sysProcess[i].ProcessName;
if (mMonitorServices.Services.ContainsKey(processName))
{
string path = mMonitorServices.Services[processName].Path;
if (path == sysProcess[i].MainModule.FileName)
{
if (mErrorApp.Contains(processName))
{
killIndex.Add(i);
}
else
mMonitorServices.Services[processName].Reboot = false;
}
}
}
for (int i = 0; i < killIndex.Count; i++)
{
string processName = sysProcess[killIndex[i]].ProcessName;
sysProcess[killIndex[i]].Kill();
mMonitorServices.Services[processName].Reboot = true;
mErrorApp.Remove(processName);
}
程序重启直接调用Process.Start("path")
即可,其中path为程序绝对路径。
3. 日志监测实现
操作系统应用程序错误日志监测主要使用EventLogWatcher
和EventLogQuery
这两个类,其中EventLogQuery
设定日志的查询条件,而通过EventLogWatcher
的EventRecordWritten
事件捕获错误日志并进行服务程序错误标记。
private void StartEventLogWatcher()
{
EventLogQuery query = new EventLogQuery("Application", PathType.FilePath, "*[System/Level=2]");
mWatcher = new EventLogWatcher(query);
mWatcher.EventRecordWritten += Watcher_EventRecordWritten;
mWatcher.Enabled = true;
}
private void Watcher_EventRecordWritten(object sender, EventRecordWrittenEventArgs e)
{
if (e.EventRecord != null)
{
var properties = e.EventRecord.Properties;
foreach (var data in properties)
{
string info = data.Value.ToString();
if(info.Contains(".exe"))
{
string appName = info.Replace(".exe", "");
if (mMonitorServices.Services.ContainsKey(appName))
{
mErrorApp.Add(appName);
break;
}
}
}
}
}
-
EventLogQuery
类中的"Application"
指定监测应用程序日志,"*[System/Level=2]"
指定捕获级别为2的日志,即错误日志。 -
e.EventRecord.Properties
是日志记录保存自定义数据的属性,下图中的EventData。该属性是一个IList<EventProperty>
对象,我们主要是想获取其中表示程序名称的信息。
应用程序错误日志.png
-
mErrorApp
是一个保存错误服务程序名称的List<string>
对象。
4. 服务程序维护
因为只是一个小程序,所以监控的服务程序信息使用xml文件保存和维护。因此该模块功能主要是xml文件的一些操作。保存服务程序的xml文件格式如下:
<?xml version="1.0" encoding="utf-8" ?>
<services>
<service name="Service1">
<path>D:\Services\DicomService.exe</path>
</service>
</services>
其中每一个监控的服务程序使用一个<service>
节点进行保存,节点的name属性保存服务程序的名称(不含程序后缀.exe),子节点<path>
保存服务程序的绝对路径。维护类代码如下:
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
using System.Xml;
namespace WinformServiceMonitor
{
public class ServicesXml
{
private readonly string mFileName = $"{Application.StartupPath}\\Services.xml";
// 保存监控的服务程序字典
private Dictionary<string, MonitorService> mServicesDict = new Dictionary<string, MonitorService>();
public Dictionary<string, MonitorService> Services
{
get { return mServicesDict; }
}
public ServicesXml()
{
if (!File.Exists(mFileName))
CreateXmlFile();
ReadServices();
}
private void ReadServices()
{
XmlDocument document = new XmlDocument();
document.Load(mFileName);
XmlNode root = document.SelectSingleNode("services");
foreach(XmlNode node in root.ChildNodes)
{
string name = node.Attributes["name"].Value;
if (!mServicesDict.ContainsKey(name))
{
mServicesDict.Add(name, new MonitorService { Name = name, Path = node["path"].InnerText });
}
}
}
public void AddServiceNode(string name, string path)
{
XmlDocument document = new XmlDocument();
document.Load(mFileName);
XmlNode root = document.SelectSingleNode("services");
XmlElement serviceNode = document.CreateElement("service");
serviceNode.SetAttribute("name", name);
XmlElement pathNode = document.CreateElement("path");
pathNode.InnerText = path;
serviceNode.AppendChild(pathNode);
root.AppendChild(serviceNode);
mServicesDict.Add(name, new MonitorService { Name = name, Path = path, Reboot = false });
document.Save(mFileName);
}
public void RemoveServiceNode(string name)
{
XmlDocument document = new XmlDocument();
document.Load(mFileName);
XmlNode root = document.SelectSingleNode("services");
bool remove = false;
foreach (XmlNode node in root.ChildNodes)
{
if (node.Attributes["name"].Value == name)
{
root.RemoveChild(node);
remove = true;
break;
}
}
if (remove)
mServicesDict.Remove(name);
document.Save(mFileName);
}
private void CreateXmlFile()
{
XmlDocument document = new XmlDocument();
XmlDeclaration declaration = document.CreateXmlDeclaration("1.0", "UTF-8", "");
document.AppendChild(declaration);
XmlElement services = document.CreateElement("services");
document.AppendChild(services);
document.Save(mFileName);
}
}
}
namespace WinformServiceMonitor
{
// 监控服务程序实体类
public class MonitorService
{
/// <summary>
/// 服务程序名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 服务绝对路径
/// </summary>
public string Path { get; set; }
/// <summary>
/// 是否需要重启
/// </summary>
public bool Reboot { get; set; }
public MonitorService()
{
Reboot = false;
}
}
}
- 注意:其中为了方便,添加和移除功能都是直接打开xml文件进行操作,如果不是这种小程序不建议这样操作。
参考文章
- 操作系统日志订阅:.NET拾忆:EventLog(Windows事件日志监控)
- EventLogWatcher: EventLogWatcher 类 (System.Diagnostics.Eventing.Reader) | Microsoft Docs
- EventLogQuery: EventLogQuery 类 (System.Diagnostics.Eventing.Reader) | Microsoft Docs
网友评论