使用Topshelf組件 一步一步建立 Windows 服務

咱們先來介紹一下使用它的好處,如下論述參考自其餘大神。html

topshelf是建立windows服務的一種方式,相比原生實現ServiceBase、Install.Installer更爲簡單方便, 咱們只須要幾行代碼便可實現windows服務的開發。linux

topshelf自己支持windows及linux下mono上部署安裝,一樣也是開源的。數據庫

topshelf相對原生來講,調試起來比較方便,能夠在開發時以控制檯的形式直接f5調試,發佈時用命令以服務的形式部署。windows

還一個比較有用的特性是支持多實例的部署,這樣能夠在一臺機器上部署多個相對的服務。相似的工具備instsrv和srvany。app

多實例有一個好處就是容災,當一個服務部署多份時,這樣其中任何一個服務實例掛了,剩餘的能夠繼續執行。負載均衡

多實例能夠是主備的方式,主掛了備服務纔會執行。也能夠以負載均衡的方式實現,多實例搶佔進程鎖或分佈式鎖,誰拿到誰執行。分佈式

 

先寫出具體步驟:工具

// 新建控制檯應用程序
// 使用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 ui

//接下來直接上代碼與截圖spa

 

 

 

 卸載服務:

當咱們啓動服務的時候,成功打印出了日誌,表示一切成功

程序結構很簡單,以下圖所示:

 接下來,咱們直接上實現代碼,我會按照步驟依次給出:

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

相關文章
相關標籤/搜索