使用Topshelf组件 一步一步创建 Windows 服务

我们先来介绍一下使用它的好处,以下论述参考自其他大神。

topshelf是创建windows服务的一种方式,相比原生实现ServiceBase、Install.Installer更为简单方便, 我们只需要几行代码即可实现windows服务的开发。

topshelf本身支持windows及linux下mono上部署安装,同样也是开源的。

topshelf相对原生来说,调试起来比较方便,可以在开发时以控制台的形式直接f5调试,发布时用命令以服务的形式部署。

还一个比较有用的特性是支持多实例的部署,这样可以在一台机器上部署多个相对的服务。类似的工具有instsrv和srvany。

多实例有一个好处就是容灾,当一个服务部署多份时,这样其中任何一个服务实例挂了,剩余的可以继续执行。

多实例可以是主备的方式,主挂了备服务才会执行。也可以以负载均衡的方式实现,多实例抢占进程锁或分布式锁,谁拿到谁执行。

 

先写出具体步骤:

// 新建控制台应用程序
// 使用Nuget安装Topshelf,选择能用的最新版本
// 使用Nuget安装NLog和NLog.config,选择能用的最新版本,用于打印日志 Nlog需要配置文件,详见NLog.config
// 初始化配置文件,创建AppConfigHelper类,继承 ConfigurationSection (需要引用System.Configuration程序集)
// 完善App.Config配置文件,读取App.Config配置文件,具体查看AppConfigHelper类
// 创建一个注册服务类TopshelfRegistService,初始化Topshelf注册
// 我们的目标很简单,就是让服务打印一个日志文件
// 编译并生成项目,进入 bin\Debug 目录下,找到xxx.exe 执行 install 命令,Windows 服务就诞生了
// 注意:如果出现需要以管理员身份启动的提示,重新以管理员身份启动 cmd

//接下来直接上代码与截图

使用Topshelf组件  一步一步创建 Windows 服务

 

 使用Topshelf组件  一步一步创建 Windows 服务

 使用Topshelf组件  一步一步创建 Windows 服务

 卸载服务:

使用Topshelf组件  一步一步创建 Windows 服务

当我们启动服务的时候,成功打印出了日志,表示一切成功

使用Topshelf组件  一步一步创建 Windows 服务

程序结构很简单,如下图所示:

使用Topshelf组件  一步一步创建 Windows 服务

 接下来,我们直接上实现代码,我会按照步骤依次给出:

1,Program主程序代码

 1 namespace ProcessPrintLogService
 2 {
 3     class Program
 4     {
 5         public static readonly Logger log = LogManager.GetCurrentClassLogger();
 6         private static readonly AppConfigHelper config = AppConfigHelper.Initity();
 7         static void Main(string[] args)
 8         {
 9             TopshelfRegistService.Regist(config, true);
10         }
11     }
12 }

2.AppConfigHelper类,用于读取配置文件,使用配置文件的方式可以使你后期将该服务应用于多个应用程序

namespace ProcessPrintLogService
{
    public class AppConfigHelper : ConfigurationSection
    {
        private static AppConfigHelper _AppConfig = null;
        private static readonly object LockThis = new object();

        /// <summary>
        /// 获取当前配置 获取section节点的内容
        /// 使用单例模式
        /// </summary>
        /// <returns></returns>
        public static AppConfigHelper Initity()
        {
            if (_AppConfig == null)
            {
                lock (LockThis)
                {
                    if (_AppConfig == null)
                    {
                        //获取app.config文件中的section配置节点
                        _AppConfig = (AppConfigHelper)ConfigurationManager.GetSection("AppConfigHelper");
                    }
                }
            }
            return _AppConfig;
        }


        //创建一个AppConfigHelper节点
        //属性分别为:ServiceName、Desc 等....
        //这里介绍一下属性标签:ConfigurationProperty 它可以在配置文件中根据属性名获取Value值
        //可以参考文章https://www.cnblogs.com/liunlls/p/configuration.html


        /// <summary>
        /// 服务名称
        /// </summary>
        [ConfigurationProperty("ServiceName", IsRequired = true)]
        public string ServiceName
        {
            get { return base["ServiceName"].ToString(); }
            internal set { base["ServiceName"] = value; }
        }

        /// <summary>
        /// 描述
        /// </summary>
        [ConfigurationProperty("Desc", IsRequired = true)]
        public string Description
        {
            get { return base["Desc"].ToString(); }
            internal set { base["Desc"] = value; }
        }

    }
}

3.Topshelf组件注册服务

namespace ProcessPrintLogService
{
    /// <summary>
    /// Topshelf组件注册服务
    /// </summary>
    internal class TopshelfRegistService
    {
        /// <summary>
        /// 注册入口
        /// </summary>
        /// <param name="config">配置文件</param>
        /// <param name="isreg">是否注册</param>
        public static void Regist(AppConfigHelper config, bool isreg = false)
        {
            //这里也可以使用HostFactory.Run()代替HostFactory.New()
            var host = HostFactory.New(x =>
            {
                x.Service<QuartzHost>(s =>
                {
                    //通过 new QuartzHost() 构建一个服务实例 
                    s.ConstructUsing(name => new QuartzHost());
                    //当服务启动后执行什么
                    s.WhenStarted(tc => tc.Start());
                    //当服务停止后执行什么
                    s.WhenStopped(tc => tc.Stop());
                    //当服务暂停后执行什么
                    s.WhenPaused(w => w.Stop());
                    //当服务继续后执行什么
                    s.WhenContinued(w => w.Start());
                });
                if (!isreg) return; //默认不注册

                //服务用本地系统账号来运行
                x.RunAsLocalSystem();
                //服务的描述信息
                x.SetDescription(config.Description);
                //服务的显示名称
                x.SetDisplayName(config.ServiceName);
                //服务的名称(最好不要包含空格或者有空格属性的字符)Windows 服务名称不能重复。
                x.SetServiceName(config.ServiceName);
            });
            host.Run();  //启动服务  如果使用HostFactory.Run()则不需要该方法
        }
    }

    /// <summary>
    /// 自定义服务
    /// </summary>
    internal class QuartzHost
    {
        public readonly Logger log = LogManager.GetLogger("QuartzHost");

        public QuartzHost()
        {
            var service = AppConfigHelper.Initity();
        }

        //服务开始
        public void Start()
        {
            try
            {
                Task.Run(() =>
                {
                    log.Info($"服务开始成功!");
                });
            }
            catch (Exception ex)
            {
                Task.Run(() =>
                {
                    log.Fatal(ex, $"服务开始失败!错误信息:{0}", ex);
                });
                throw;
            }
        }

        //服务停止
        public void Stop()
        {
            Task.Run(() =>
            {
                log.Trace("服务结束工作");
            });
        }
    }

}

4.App.config配置文件

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <!--该节点一定要放在最上边-->
  <configSections>
    <section name="AppConfigHelper" type="ProcessPrintLogService.AppConfigHelper,ProcessPrintLogService"/>
  </configSections>

  <!--TopSelf服务配置文件 -->
  <AppConfigHelper
    ServiceName="Process_PrintLogService"
    Desc="日志打印服务"
    />

  <!--数据库连接字符串 -->
  <connectionStrings>
    <add name="ConnectionString" connectionString="123123123"/>
  </connectionStrings>

  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
  </startup>
</configuration>

5.Nlog.config日志配置文件

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <targets>
    <!--type="File|Console" 属性是设置日志输出目标是"File"(文件)或者"Console"(控制台)-->
    <!--fileName="${basedir}/logs/${shortdate}/${level}/${callsite}.log" 设置日记记录文件的路径和名称-->
    <!--layout="${longdate} ${level} ${callsite}:${message}" 设置日志输出格式-->
    <target name="t1"
            type="File"
            fileName="${basedir}/logs/${shortdate}/${level} ${callsite}.log"
            layout="${longdate} ${level} ${callsite}:${message}"
          archiveAboveSize="3145728"
          archiveNumbering="Rolling"
          concurrentWrites="false"
          keepFileOpen="true"
          maxArchiveFiles ="20"
    />

    <!--输出至控制台-->
    <target name="t2" type="Console" layout="${longdate} ${level} ${callsite}:${message}" />
  </targets>

  <rules>
    <!--如果填*,则表示所有的Logger都运用这个规则,将所有级别的日志信息都写入到“t1”和“t2”这两个目标里-->
    <logger name="*" writeTo="t1,t2"/>
  </rules>
</nlog>

以上就是此次示例的全部代码,到此你也许会有一个问题,就是我想定时执行我的任务?比如每天几点执行,或者每几分钟执行一次等等,那我们该怎么做呢?

答案是使用:Quartz.net ,接下来我将会使用 Quartz.net 实现上述的定时任务。

参考文献:

https://www.jianshu.com/p/f2365e7b439c

 

使用Topshelf组件 一步一步创建 Windows 服务

上一篇:用JS识别各版本浏览器


下一篇:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(32)-swfupload多文件上传[附源码]