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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM