zoukankan      html  css  js  c++  java
  • 使用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注册
    // 我们的目标很简单,就是让服务打印一个日志文件
    // 编译并生成项目,进入 binDebug 目录下,找到xxx.exe 执行 install 命令,Windows 服务就诞生了
    // 注意:如果出现需要以管理员身份启动的提示,重新以管理员身份启动 cmd

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

     

     

     卸载服务:

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

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

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

    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

    http://www.80iter.com/blog/1451523192435464/ 
    https://www.itsvse.com/thread-7503-1-1.html?tdsourcetag=s_pctim_aiomsg

    https://www.cnblogs.com/yanglang/p/7199913.html

  • 相关阅读:
    【POJ 1655】Balancing Act
    【POJ 2631】 Roads in the North
    【洛谷 1002】 过河卒
    【洛谷 3178】树上操作
    【洛谷 1060】开心的金明
    【洛谷 2709】小B的询问
    【洛谷 1972】HH的项链
    JavaSpark-sparkSQL
    java8下spark-streaming结合kafka编程(spark 2.3 kafka 0.10)
    Kafka 使用Java实现数据的生产和消费demo
  • 原文地址:https://www.cnblogs.com/peterzhang123/p/11894837.html
Copyright © 2011-2022 走看看