所谓进程守护,就是想让我们的主程序能够长期、稳定的运行下去。单独一个主程序的话,很难做到这个事情。所以我们需要第二个程序来守护它,让它能够不被轻易停掉或者停掉后能自动再启动起来。
守护进程实现的方式有很多种,简单说下:
- 做两个程序,在主程序启动的时候去启动守护程序,守护程序需要是一个无交互、无页面的后台程序,守护程序用来不断检测主程序是否存在,如果不存在,则去启动它。同时也可以让则两个程序相互守护;
- 用Windows计划任务来守护程序,在主程序启动的时候,创建一个Windows计划任务来判断主程序的状态以及重启它,这种方式直接委托到系统中,其实也很稳妥。
- 用Windows服务来定时检测主程序。Windows服务一般用于后台处理,不需要界面操作,可以永远驻留系统,可以设定为自动启动和手动启动。所以这个我认为更能胜任这个工作。
下面介绍下Windows服务的创建以及基本使用
- 用Visual Studio创建项目的时候选择windows服务
- 创建后系统会默认创建一个Service1.cs文件,双击打开这个文件,然后在该页面上右键->添加安装程序
- 接下来会看到项目下多了个ProjectInstaller.cs文件,打开这个文件会看到里面有两个控件;
serviceProcessInstaller1
和serviceInstaller1
; serviceProcessInstaller1
是服务流程安装程序,这里我们只需要设置下服务的账户类型;即:serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
serviceInstaller1
就是我们的服务安装程序了,在这里可以设置服务的名称、描述以及启动方式等信息,同时还可以对一些安装事件进行处理- 我们再回到Service1文件看下代码文件,会发现里面有OnStart和OnStop两个方法,分别表示服务启动和停止时需要执行的事件,这里可以写我们需要处理的逻辑代码
介绍完上面的基本情况之后,我们再随便创建一个控制台程序,这是主程序,我们要做的就是守护这个程序,这里随便写个代码好了。
为了方便复用,还要新增一个公共类库,封装一些操作类等。所以最终程序的结构如下:
因为最后生成之后,我们的服务与主程序会是两个不同的程序,但是我们又需要通信,所以这里增加了配置文件,即Config.xml
,这个文件记录了一些服务的基本信息;同时为了更加人性化,也可以说是为了扩展点小知识吧。使用了注册表来存储程序的路径(因为我们不知道客户会把程序安装到哪里,所以在重启的时候找路径就比较困难。在启动主程序的时候将路径写入到注册表不失为一个很好的办法);
写了这么多,下面放下代码;
实现功能:
- 守护进程,当主程序被停掉的时候重新启动
开发环境:
开发工具: Visual Studio 2013
.NET Framework版本:4.5
实现代码:
- 配置文件
<?xml version="1.0" encoding="utf-8" ?>
<Service>
<!--服务名称-->
<ServiceName>ProtectApp</ServiceName>
<!--服务描述-->
<ServiceDesc>守护进程服务</ServiceDesc>
<!--主程序进程名称-->
<ProcessName>WindowsService.App</ProcessName>
<!--注册表路径-->
<RegeditPath>SOFTWARE\\ProtectApp</RegeditPath>
<!--注册表键名-->
<RegeditKey>Path</RegeditKey>
</Service>
- WindowsService的代码
//Service1
public partial class Service1 : ServiceBase
{
System.Timers.Timer timer1 = new System.Timers.Timer(5000);
private string appPath, processName;
public Service1()
{
InitializeComponent();
timer1.Elapsed += timer1_Tick;
}
protected override void OnStart(string[] args)
{
XElement xele = XElement.Load(Path.GetDirectoryName(typeof(WindowsService.Program).Assembly.Location) + "\\Config.xml");
processName = xele.Element("ProcessName").Value;
string subKey = xele.Element("RegeditPath").Value;
string keyName = xele.Element("RegeditKey").Value;
appPath = new RegeditUtil(RegistryKeyRoot.HKEY_LOCAL_MACHINE).GetValue(subKey, keyName).ToString();
timer1.Start();
}
protected override void OnStop()
{
}
private void timer1_Tick(object sender, EventArgs e)
{
Process[] process = Process.GetProcessesByName(processName);
if (process.Length == 0)
{
if (System.IO.File.Exists(appPath))
{
ServiceUtil.RunApp(appPath);
}
}
}
}
//ProjectInstaller
public partial class ProjectInstaller : System.Configuration.Install.Installer
{
public ProjectInstaller()
{
InitializeComponent();
serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
XElement xele = XElement.Load(Path.GetDirectoryName(typeof(WindowsService.Program).Assembly.Location) + "\\Config.xml");
serviceInstaller1.ServiceName = xele.Element("ServiceName").Value;
serviceInstaller1.Description = xele.Element("ServiceDesc").Value;
}
}
- 注册表操作工具类
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WindowsService.Common
{
public class RegeditUtil
{
private readonly RegistryKey registryKey;
public RegeditUtil(RegistryKeyRoot registryKeyRoot)
{
switch (registryKeyRoot)
{
case RegistryKeyRoot.HKEY_CLASSES_ROOT:
registryKey = Registry.ClassesRoot;
break;
case RegistryKeyRoot.HKEY_CURRENT_USER:
registryKey = Registry.CurrentUser;
break;
case RegistryKeyRoot.HKEY_LOCAL_MACHINE:
registryKey = Registry.LocalMachine;
break;
case RegistryKeyRoot.HKEY_USERS:
registryKey = Registry.Users;
break;
case RegistryKeyRoot.HKEY_CURRENT_CONFIG:
registryKey = Registry.CurrentConfig;
break;
default:
registryKey = Registry.LocalMachine;
break;
}
}
public object GetValue(string SubKey, string keyName)
{
RegistryKey key = registryKey.OpenSubKey(SubKey);
if (key != null)
{
return key.GetValue(keyName);
}
else
{
return null;
}
}
public Dictionary<string, object> GetAllValue(string SubKey)
{
Dictionary<string, object> dic = new Dictionary<string, object>();
RegistryKey key = registryKey.OpenSubKey(SubKey);
if (key != null)
{
string[] arr = key.GetValueNames();
for (int i = 0; i < arr.Length; i++)
{
dic.Add(arr[i], key.GetValue(arr[i]));
}
}
return dic;
}
public void SetValue(string SubKey, Dictionary<string, object> keyValue)
{
RegistryKey key = registryKey.OpenSubKey(SubKey, true) ?? registryKey.CreateSubKey(SubKey, RegistryKeyPermissionCheck.ReadWriteSubTree, RegistryOptions.Volatile);
foreach (var d in keyValue)
{
key.SetValue(d.Key, d.Value);
}
}
public void SetValue(string SubKey,string keyName,object value)
{
Dictionary<string, object> dic = new Dictionary<string, object>();
dic.Add(keyName, value);
SetValue(SubKey, dic);
}
public void RemoveKey(string SubKey, string keyName)
{
RegistryKey key = registryKey.OpenSubKey(SubKey);
if (key != null)
{
key.DeleteSubKeyTree(keyName);
}
}
}
public enum RegistryKeyRoot
{
HKEY_CLASSES_ROOT,
HKEY_CURRENT_USER,
HKEY_LOCAL_MACHINE,
HKEY_USERS,
HKEY_CURRENT_CONFIG
}
}
- Windows服务工具类
using Cjwdev.WindowsApi;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
namespace WindowsService.Common
{
public class ServiceUtil
{
private readonly string ServiceName;
public ServiceUtil(string serviceName)
{
ServiceName = serviceName;
}
/// <summary>
/// 安装服务
/// </summary>
/// <param name="ServiceAppPath">安装路径</param>
public void Install(string ServiceAppPath)
{
string basePath = AppDomain.CurrentDomain.BaseDirectory;
List<string> cmds = new List<string>();
cmds.Add(string.Format("{0}InstallUtil.exe {1}", basePath, ServiceAppPath));
cmds.Add(string.Format("sc stop {0}", ServiceName));
cmds.Add(string.Format("sc config {0} type= interact type= own", ServiceName));
cmds.Add(string.Format("sc start {0}", ServiceName));
RunCmd(cmds);
}
/// <summary>
/// 卸载服务
/// </summary>
/// <param name="ServiceAppPath">安装路径</param>
public void UnInstall(string ServiceAppPath)
{
string basePath = AppDomain.CurrentDomain.BaseDirectory;
List<string> cmds = new List<string>();
cmds.Add(string.Format("{0}InstallUtil.exe /u {1}", basePath, ServiceAppPath));
RunCmd(cmds);
}
/// <summary>
/// 开启服务
/// </summary>
/// <param name="Server"></param>
public void Start()
{
ServiceController control = new ServiceController(ServiceName);
if (control.Status == ServiceControllerStatus.Stopped)
{
control.Start();
}
}
/// <summary>
/// 关闭服务
/// </summary>
/// <param name="Server"></param>
public void Stop()
{
ServiceController control = new ServiceController(ServiceName);
if (control.Status == ServiceControllerStatus.Running)
{
control.Stop();
}
}
/// <summary>
/// 删除服务
/// </summary>
public void Remove()
{
string basePath = AppDomain.CurrentDomain.BaseDirectory;
List<string> cmds = new List<string>();
cmds.Add(string.Format("sc delete {0}", ServiceName));
RunCmd(cmds);
}
/// <summary>
/// 查看服务是否存在
/// </summary>
/// <param name="NameService"></param>
/// <returns></returns>
public bool Exists()
{
ServiceController[] services = ServiceController.GetServices();
foreach (ServiceController s in services)
{
if (s.ServiceName.ToUpper() == ServiceName.ToUpper())
{
return true;
}
}
return false;
}
/// <summary>
/// 设置允许服务与桌面交互 ,修改了注册表,要重启系统才能生效
/// </summary>
public void SetInteraction()
{
RegeditUtil regeditUtil = new RegeditUtil(RegistryKeyRoot.HKEY_LOCAL_MACHINE);
string subKey = @"SYSTEM/CurrentControlSet/Services/" + ServiceName;
string keyName = "Type";
int value = (int)regeditUtil.GetValue(subKey, keyName);
regeditUtil.SetValue(subKey, keyName, value | 256);
}
/// <summary>
/// 禁用任务管理器
/// </summary>
/// <param name="arg">0.启用;1.禁用 </param>
public static void ManageTaskManager(bool isEnable)
{
RegeditUtil regeditUtil = new RegeditUtil(RegistryKeyRoot.HKEY_CURRENT_USER);
string subKey = @"Software\Microsoft\Windows\CurrentVersion\Policies\System";
string keyName = "DisableTaskmgr";
int value = isEnable ? 0 : 1;
regeditUtil.SetValue(subKey, keyName, value);
}
/// <summary>
///winservice打开可执行程序
///win7以上系统不允许服务与桌面交互,所以此处借用Cjwdev.WindowsApi.dll来实现
/// </summary>
/// <param name="AppPath"></param>
/// <returns></returns>
public static int RunApp(string AppPath)
{
IntPtr userTokenHandle = IntPtr.Zero;
ApiDefinitions.WTSQueryUserToken(ApiDefinitions.WTSGetActiveConsoleSessionId(), ref userTokenHandle);
ApiDefinitions.PROCESS_INFORMATION procInfo = new ApiDefinitions.PROCESS_INFORMATION();
ApiDefinitions.STARTUPINFO startInfo = new ApiDefinitions.STARTUPINFO();
startInfo.cb = (uint)Marshal.SizeOf(startInfo);
ApiDefinitions.CreateProcessAsUser(
userTokenHandle,
AppPath,
"",
IntPtr.Zero,
IntPtr.Zero,
false,
0,
IntPtr.Zero,
null,
ref startInfo,
out procInfo);
if (userTokenHandle != IntPtr.Zero)
ApiDefinitions.CloseHandle(userTokenHandle);
return (int)procInfo.dwProcessId;
}
public List<string> RunCmd(List<string> cmds)
{
List<string> list = new List<string>();
try
{
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.RedirectStandardError = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.Start();
foreach (string cmd in cmds)
{
p.StandardInput.WriteLine(cmd);
}
p.StandardInput.WriteLine("exit");
p.StandardInput.AutoFlush = true;
StreamReader readerout = p.StandardOutput;
while (!readerout.EndOfStream)
{
list.Add(readerout.ReadLine());
}
p.WaitForExit();
p.Close();
return list;
}
catch (Exception ex)
{
list.Add(ex.Message);
return list;
}
}
}
}
- 控制台(主程序代码)
using System.Xml.Linq;
namespace WindowsService.App
{
class Program
{
static void Main(string[] args)
{
Console.Title="服务测试";
RegeditUtil regeditUtil = new RegeditUtil(RegistryKeyRoot.HKEY_LOCAL_MACHINE);
XElement xele = XElement.Load(Path.GetDirectoryName(typeof(WindowsService.App.Program).Assembly.Location)+"\\Config.xml");
string serviceName = xele.Element("ServiceName").Value;
string subKey = xele.Element("RegeditPath").Value;
string keyName = xele.Element("RegeditKey").Value;
string value = Path.GetFullPath(typeof(WindowsService.App.Program).Assembly.Location);
if (Convert.ToString(regeditUtil.GetValue(subKey, keyName)) != value)
{
regeditUtil.SetValue(subKey, keyName, value);
Console.WriteLine("写入注册表成功");
}
ServiceUtil serviceUtil = new ServiceUtil(serviceName);
if (!serviceUtil.Exists())
{
string ServicePath = Path.GetFullPath(typeof(WindowsService.Service1).Assembly.Location);
serviceUtil.Install(ServicePath);
Console.WriteLine("服务安装成功");
}
while (true)
{
Console.WriteLine(DateTime.Now);
Thread.Sleep(3000);
}
}
}
}
以下几点需要注意:
- 程序需要以管理员身份运行
- .复制Cjwdev.WindowsApi.dll到输出目录
- 服务的安装需要用到InstallUtil.exe,也需要复制到输出目录
实现效果:
效果这里就不放了,可以自行测试下,服务不停,程序不止。。。。
由简入繁,拿来即用
更多精彩,请持续关注公众号: