C# 进程守护之Windows服务

所谓进程守护,就是想让我们的主程序能够长期、稳定的运行下去。单独一个主程序的话,很难做到这个事情。所以我们需要第二个程序来守护它,让它能够不被轻易停掉或者停掉后能自动再启动起来。

守护进程实现的方式有很多种,简单说下:

  1. 做两个程序,在主程序启动的时候去启动守护程序,守护程序需要是一个无交互、无页面的后台程序,守护程序用来不断检测主程序是否存在,如果不存在,则去启动它。同时也可以让则两个程序相互守护;
  2. 用Windows计划任务来守护程序,在主程序启动的时候,创建一个Windows计划任务来判断主程序的状态以及重启它,这种方式直接委托到系统中,其实也很稳妥。
  3. 用Windows服务来定时检测主程序。Windows服务一般用于后台处理,不需要界面操作,可以永远驻留系统,可以设定为自动启动和手动启动。所以这个我认为更能胜任这个工作。

下面介绍下Windows服务的创建以及基本使用

  1. 用Visual Studio创建项目的时候选择windows服务

  1. 创建后系统会默认创建一个Service1.cs文件,双击打开这个文件,然后在该页面上右键->添加安装程序
  2. 接下来会看到项目下多了个ProjectInstaller.cs文件,打开这个文件会看到里面有两个控件;serviceProcessInstaller1serviceInstaller1
  3. serviceProcessInstaller1是服务流程安装程序,这里我们只需要设置下服务的账户类型;即:serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
  4. serviceInstaller1就是我们的服务安装程序了,在这里可以设置服务的名称、描述以及启动方式等信息,同时还可以对一些安装事件进行处理
  5. 我们再回到Service1文件看下代码文件,会发现里面有OnStart和OnStop两个方法,分别表示服务启动和停止时需要执行的事件,这里可以写我们需要处理的逻辑代码

介绍完上面的基本情况之后,我们再随便创建一个控制台程序,这是主程序,我们要做的就是守护这个程序,这里随便写个代码好了。

为了方便复用,还要新增一个公共类库,封装一些操作类等。所以最终程序的结构如下:

因为最后生成之后,我们的服务与主程序会是两个不同的程序,但是我们又需要通信,所以这里增加了配置文件,即Config.xml,这个文件记录了一些服务的基本信息;同时为了更加人性化,也可以说是为了扩展点小知识吧。使用了注册表来存储程序的路径(因为我们不知道客户会把程序安装到哪里,所以在重启的时候找路径就比较困难。在启动主程序的时候将路径写入到注册表不失为一个很好的办法);

写了这么多,下面放下代码;

实现功能:

- 守护进程,当主程序被停掉的时候重新启动

开发环境:

开发工具: Visual Studio 2013

.NET Framework版本:4.5

实现代码:

  1. 配置文件
<?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>
  1. 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;
        }

    }
  1. 注册表操作工具类
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
    }
}
  1. 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;
        }
        
    }
    
    
}
}

  1. 控制台(主程序代码)
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);
        }
        
    }
}
}

以下几点需要注意:

  1. 程序需要以管理员身份运行
  2. .复制Cjwdev.WindowsApi.dll到输出目录
  3. 服务的安装需要用到InstallUtil.exe,也需要复制到输出目录

实现效果:

效果这里就不放了,可以自行测试下,服务不停,程序不止。。。。

由简入繁,拿来即用

更多精彩,请持续关注公众号:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值