C# Windows 服务程序的开发

目录

一、概述

二、新建服务程序

三、安装前的准备

四、启动、停止,安装、卸载

五、测试

1.安装服务

2.启动服务

3.停止服务

4.卸载服务

结束


一、概述

当需要在 Windows 操作系统以后台服务的形式运行某个应用程序时,可以使用 Windows 服务。Windows 服务是一种特殊类型的应用程序,它可以在操作系统启动时自动启动,并在后台持续运行,而无需用户交互。

Windows 服务通常用于执行一些重要的系统任务,例如定期备份数据、监控系统状态、处理消息队列等。它们在后台默默地运行,不会显示用户界面,但可以通过服务管理器进行配置和监视。

编写一个 Windows 服务程序可以使用 C# 编程语言和 .NET 框架。通过使用 .NET 提供的相关类和方法,可以轻松地创建、安装、启动和停止 Windows 服务。 一个典型的 Windows 服务程序包括以下几个关键部分:

1. 服务类:这是一个继承自 System.ServiceProcess.ServiceBase 类的自定义类。在这个类中,需要重写 OnStart 和 OnStop 方法,分别用于处理服务的启动和停止逻辑。在 OnStart 方法中,可以执行服务的初始化操作,并开始执行后台任务。在 OnStop 方法中,可以执行服务的清理操作,并停止后台任务。

2. 服务主程序:这是一个包含 Main 方法的类,用于启动和停止服务。在 Main 方法中,需要创建一个 ServiceHost 对象,并将服务类作为参数传递给它。然后调用 Start 方法启动服务,并通过调用 Stop 方法停止服务。

3. 安装程序:为了将服务安装到 Windows 系统中,需要编写一个安装程序。安装程序将服务的可执行文件注册到系统服务管理器中,并提供安装、卸载和配置服务的功能。 通过编写一个 Windows 服务程序,可以实现在后台运行的应用程序,以便自动执行某些任务或提供某种功能。这种方式可以确保应用程序在系统启动后始终可用,并且无需用户干预。 请注意,编写和部署 Windows 服务需要一些系统管理权限和操作。确保在进行这些操作之前,对系统有足够的了解,并按照最佳实践进行操作。

二、新建服务程序

在新建项目时,选择 Windows 服务,目前只有 .NET Framework 的服务程序,没有 .Net6 的

或者直接搜索“服务”二字

这里就直接用默认的名字吧

创建完成后的界面

选中 Service1.cs 右键选择查看代码,如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;

namespace WindowsService1
{
    public partial class Service1 : ServiceBase
    {
        public Service1()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
        }

        protected override void OnStop()
        {
        }
    }
}

这里重写了一个 OnStart 和 OnStop 方法,意思是程序启动的时候执行一次,和程序关闭的时候执行一次。

因为目前只是作为演示,就不用过多的功能,就随便输出一些日志,能理解 Windows 服务的用法就好了。

安装插件 log4net

新建一个类 Logger,用来输出日志

using System;

[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config", ConfigFileExtension = "config", Watch = true)]
public class Logger
{
    private static readonly log4net.ILog loginfo = log4net.LogManager.GetLogger("loginfo");
    private static readonly log4net.ILog logerror = log4net.LogManager.GetLogger("logerror");

    public static void WriteInfo(string info)
    {
        Console.WriteLine(info);
        if (loginfo.IsInfoEnabled)
        {
            loginfo.Info(info);
        }
    }

    public static void WriteError(string error)
    {
        Console.WriteLine(error);
        if (logerror.IsErrorEnabled)
        {
            logerror.Error(error);
        }
    }

    public static void WriteError(string info, Exception ex)
    {
        Console.WriteLine(info);
        if (logerror.IsErrorEnabled)
        {
            logerror.Error(info, ex);
        }
    }
}

log4net 要想输出日志,还需要另外添加一个配置文件 log4net.config

<?xml version="1.0"?>
<configuration>
	<configSections>
		<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
	</configSections>
	<log4net>
		<!-- 错误日志类-->
		<logger name="logerror">
			<level value="ALL" />
			<appender-ref ref="ErrorAppender" />
		</logger>
		<!-- 信息日志类 -->
		<logger name="loginfo">
			<level value="ALL" />
			<appender-ref ref="InfoAppender" />
		</logger>
		<!-- 错误日志附加介质-->
		<appender name="ErrorAppender" type="log4net.Appender.RollingFileAppender">
			<param name="File" value="Log\\LogError\\" />
			<param name="AppendToFile" value="true" />
			<param name="MaxSizeRollBackups" value="100" />
			<param name="MaxFileSize" value="10240" />
			<param name="StaticLogFileName" value="false" />
			<param name="DatePattern" value="yyyyMMdd&quot;.htm&quot;" />
			<param name="RollingStyle" value="Date" />
			<!--布局-->
			<layout type="log4net.Layout.PatternLayout">
				<param name="ConversionPattern" value="&lt;HR COLOR=red&gt;%n异常时间:%d [%t] &lt;BR&gt;%n异常级别:%-5p &lt;BR&gt;%n异 常 类:%c [%x] &lt;BR&gt;%n%m &lt;BR&gt;%n &lt;HR Size=1&gt;"  />
			</layout>
		</appender>
		<!-- 信息日志附加介质-->
		<appender name="InfoAppender" type="log4net.Appender.RollingFileAppender">
			<param name="File" value="Log\\LogInfo\\" />
			<param name="AppendToFile" value="true" />
			<param name="MaxFileSize" value="10240" />
			<param name="MaxSizeRollBackups" value="100" />
			<param name="StaticLogFileName" value="false" />
			<param name="DatePattern" value="yyyyMMdd&quot;.htm&quot;" />
			<param name="RollingStyle" value="Date" />
			<!-- 信息日志布局-->
			<layout type="log4net.Layout.PatternLayout">
				<param name="ConversionPattern" value="&lt;HR COLOR=blue&gt;%n日志时间:%d [%t] &lt;BR&gt;%n日志级别:%-5p &lt;BR&gt;%n日 志 类:%c [%x] &lt;BR&gt;%n%m &lt;BR&gt;%n &lt;HR Size=1&gt;"  />
			</layout>
		</appender>
	</log4net>
</configuration>

将 log4net.config 文件设置成:如果较新则复制

服务程序并不能直接启动,安装服务程序需要固定的流程,接下来是安装服务程序的相关内容。

三、安装前的准备

双击 Service1.cs 文件

就打开了 Service1 的设计界面,接着点击 添加安装界面

这时候会自动生成一个 ProjectInstaller 的文件

点击 serviceInstaller1 查看属性

Windows 服务程序主要用到的有几个属性:

Description                服务的说明

ServiceName             服务的名字

StartType                   服务的启动类型

这几个属性可以参考下图:

这里我就随便设置下:

再点击 serviceProcessInstaller1,查看属性

将 Account 设置为 LocalService

以上操作完成后,将项目保存,生成程序。

Windows 服务程序一般用控制台进行安装和卸载的,但控制台用起来不是特别方便,这里我就用 Winform 写一个对应的程序好了。

四、启动、停止,安装、卸载

新一个 Winform 项目,名字叫 ServiceControl, 主要用来操作服务程序的启动、停止,安装、卸载。

创建项目完成后,需要添加一个 dll,名字叫 System.ServiceProcess,一般默认情况下在创建项目时是不会主动添加这个 dll 的,在引用管理器中选择程序集,然后搜索,找到之后添加到项目中就好了。

Winform 界面如下:

Form1 代码

using System;
using System.Collections;
using System.Configuration.Install;
using System.IO;
using System.ServiceProcess;
using System.Windows.Forms;


namespace ServiceControl
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        //服务程序路径
        private string ServiceFilePath = string.Empty;
        //服务程序名
        private string ServiceName = string.Empty;

        private void Form1_Load(object sender, EventArgs e)
        {

        }


        /// <summary> 
        /// 选择服务程序路径 点击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_SelectPath_Click(object sender, EventArgs e)
        {
            string serviceName = TextBox_ServiceName.Text;
            if (string.IsNullOrEmpty(serviceName))
            {
                Console.WriteLine("请先输入服务程序名");
                return;
            }
            ServiceName = serviceName;

            OpenFileDialog openFileDialog = new OpenFileDialog();
            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                //文件路径
                string filePath = openFileDialog.FileName;
                TextBox_ServicePath.Text = filePath;
                ServiceFilePath = filePath;
            }
        }

        /// <summary>
        /// 启动服务 点击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_StartService_Click(object sender, EventArgs e)
        {
            try
            {
                if (!IsServiceExisted(ServiceName))
                {
                    Console.WriteLine("请先安装服务程序:{0}", ServiceName);
                    return;
                }
                ServiceStart(ServiceName);
                Console.WriteLine("启动服务 完成");
            }
            catch (Exception ex)
            {
                Console.WriteLine("启动服务程序错误:{0}", ex.Message);
            }
        }

        /// <summary>
        /// 停止服务 点击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_StopService_Click(object sender, EventArgs e)
        {
            try
            {
                if (!IsServiceExisted(ServiceName))
                {
                    Console.WriteLine("请先安装服务程序:{0}", ServiceName);
                    return;
                }
                ServiceStop(ServiceName);
                Console.WriteLine("停止服务 完成");
            }
            catch (Exception ex)
            {
                Console.WriteLine("停止服务程序错误:{0}", ex.Message);
            }
        }

        /// <summary>
        /// 安装服务 点击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_InstallerService_Click(object sender, EventArgs e)
        {
            try
            {
                if(IsServiceExisted(ServiceName))
                {
                    Console.WriteLine("当前服务程序已经安装,不能重复安装");
                    return;
                }
                InstallService(ServiceFilePath);
                Console.WriteLine("安装服务 完成");
            }
            catch (Exception ex)
            {
                Console.WriteLine("安装服务程序错误:{0}", ex.Message);
            }
        }

        /// <summary>
        /// 卸载服务 点击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_UnloadService_Click(object sender, EventArgs e)
        {
            try
            {
                if (!IsServiceExisted(ServiceName))
                {
                    Console.WriteLine("请先安装服务程序:{0}", ServiceName);
                    return;
                }

                ServiceStop(ServiceName);
                UninstallService(ServiceFilePath);
                Console.WriteLine("卸载服务 完成");
            }
            catch (Exception ex)
            {
                Console.WriteLine("卸载服务程序错误:{0}", ex.Message);
            }
        }

        /// <summary>
        /// 判断服务程序是否存在
        /// </summary>
        /// <param name="serviceName">服务程序的名字</param>
        /// <returns></returns>
        private bool IsServiceExisted(string serviceName)
        {
            if (string.IsNullOrEmpty(serviceName)) 
            {
                Console.WriteLine("参数 serviceName 不能为空");
                return false; 
            }

            System.ServiceProcess.ServiceController[] services =
                System.ServiceProcess.ServiceController.GetServices();
            foreach (var item in services)
            {
                if (item.ServiceName.ToLower() == serviceName.ToLower())
                    return true;
            }
            return false;
        }

        /// <summary>
        /// 安装服务
        /// </summary>
        /// <param name="servicePath">服务程序的路径</param>
        private void InstallService(string servicePath)
        {
            if (string.IsNullOrEmpty(servicePath))
            {
                Console.WriteLine("参数 servicePath 不能为空");
                return; 
            }

            using (AssemblyInstaller installer = new AssemblyInstaller())
            {
                installer.UseNewContext = true;
                installer.Path = servicePath;
                IDictionary savedState = new Hashtable();
                installer.Install(savedState);
                installer.Commit(savedState);
            }
        }

        /// <summary>
        /// 卸载服务
        /// </summary>
        /// <param name="servicePath">服务程序的路径</param>
        private void UninstallService(string servicePath)
        {
            if (string.IsNullOrEmpty(servicePath))
            {
                Console.WriteLine("参数 servicePath 不能为空");
                return;
            }

            using (AssemblyInstaller installer = new AssemblyInstaller())
            {
                installer.UseNewContext = true;
                installer.Path = servicePath;
                installer.Uninstall(null);
            }
        }

        /// <summary>
        /// 启动服务
        /// </summary>
        /// <param name="serviceName">服务程序的名字</param>
        private void ServiceStart(string serviceName)
        {
            if (string.IsNullOrEmpty(serviceName))
            {
                Console.WriteLine("参数 serviceName 不能为空");
                return;
            }

            using (System.ServiceProcess.ServiceController control =
                new System.ServiceProcess.ServiceController(serviceName))
            {
                if (control.Status == System.ServiceProcess.ServiceControllerStatus.Stopped)
                    control.Start();
            }
        }

        /// <summary>
        /// 停止服务
        /// </summary>
        /// <param name="serviceName">服务程序的名字</param>
        private void ServiceStop(string serviceName)
        {
            if (string.IsNullOrEmpty(serviceName))
            {
                Console.WriteLine("参数 serviceName 不能为空");
                return;
            }

            using (System.ServiceProcess.ServiceController control =
               new System.ServiceProcess.ServiceController(serviceName))
            {
                if (control.Status == System.ServiceProcess.ServiceControllerStatus.Running)
                    control.Stop();
            }
        }
    }
}

下面操作会对 VS 配置有影响,不用管理员的身份打开 VS 每次都会弹框,请慎重操作。

由于启动服务程序需要管理员权限,现在我们添加权限,右键点击项目,添加--> 新建项,选择 应用程序清单文件:

将原有的代码注释掉,下图中的代码复制出来:

点击保存,并将输出类型改为控制台输出

第一次运行,会提示你是否提升权限,点击 使用其他凭据重新启动

但是后面项目每次打开都提示这个弹框,请慎重操作

五、测试

1.安装服务

运行项目,选择服务程序的路径

点击 安装服务 按钮,控制台输出了一大堆文字,最后可以看到 “安装服务完成” 的打印。

接下来,我们打开服务程序,找到列表中 T 字开头的服务程序,可以看到我们刚刚安装的  TestService1,在说明这里写着 “测试服务程序”,就是上面操作中手动添加进去的。

打开属性看看:

找到服务程序的安装目录,会多两个文件出来

2.启动服务

 点击按钮 启动服务 按钮

控制台输出:

在服务程序中,可以看到 “正在运行” 的文字,那么启动服务就是成功了。

如果你之前已经打开了服务程序,点击一下刷新按钮就好了

在服务程序目录,会多一个文件夹,打开这个文件夹

在 LogInfo 文件夹中找到当天的日期的日志文件

在浏览器中打开,就能看到代码中输出的日志

3.停止服务

点击 停止服务 按钮

控制台输出:

在服务程序列表中,“正在运行” 的状态已经变成了一片空白,那么就是成功了。

我们打开日志文件:

4.卸载服务

点击 卸载服务 按钮

控制台同样输出了一大堆文字,这里执行会稍微慢点,界面会卡死,执行完成后,在最后一行可以看到 “卸载服务完成” 的打印,那么就是成功了,这时界面的卡死也恢复了正常。(单线程的缘故)

打开服务程序,可以看到,我们安装的  TestService1 服务程序已经没有了

那么这就是服务程序大致的操作了。

源码:点击跳转

结束

如果这个帖子对你有所帮助,欢迎 关注 + 点赞 + 留言

end

猜你喜欢

转载自blog.csdn.net/qq_38693757/article/details/134710362
今日推荐