1. 程式人生 > >使用Topshelf元件 一步一步建立 Windows 服務

使用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

//接下來直接上程式碼與截圖

 

 

 

 解除安裝服務:

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

程式結構很簡單,如下圖所示:

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

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

&n