以Windows服務方式運行.NET Core程序

原文:以Windows服務方式運行.NET Core程序

在之前一篇博客《以Windows服務方式運行ASP.NET Core程序》中我講述了如何把ASP.NET Core程序作為Windows服務運行的方法,而今,我們又遇到了新的問題,那就是:我們的控制臺程序,也就是普通的.NET Core程序(而不是ASP.NET Core程序)如何以服務的方式運行呢?

這個問題我們在.NET Core之前早就遇到過,那是是.NET Framework的時代(其實距今也沒多遠啦),我們是用一個第三方的組件——Topshelf,來解決這個問題的,Topshelf的官網是:http://topshelf-project.com/,它的使用很簡單,官網上有具體的描述,對于一個普通的控制臺程序而言(通常是一個不需要圖形界面的服務),開發和調試的時候,把它當做一個普通的控制臺程序來使用,十分方便;而實際部署的時候,通過傳入不同的命令行參數,可以使它有了新的行為:安裝Windows服務、運行Windows服務、停止/重啟Windows服務或者卸載Windows服務。進入跨平臺的.NET Core時代之后,Topshelf自然有了支持.NET Core的版本,使用方法與之前的類似,具體在此不表了,因為接下來我們根本不打算使用它!

現在我想要的是:不要引入任何組件,不要對現在控制臺程序進行任何修改(ASP.NET Core程序也是控制臺程序),開發調試時候不要進行任何復雜的參數配置,一切照舊,僅僅是在部署階段,把程序當做Windows服務去運行。——你嘚講吼不吼?

要達到這個目標,就要借助一個神器了,此神器為NSSM,Non-Sucking Service Manager,名字有點拗口,翻譯成中文就是:不嗝屁服務管理器。

NSSM的官網是:https://nssm.cc/,十分簡陋,但程序功能可是非常強大和全面的,下面我來一步步演示它如何使用。

1,先構建一個簡單的服務程序

構建一個簡單的服務程序,程序功能描述:程序沒有圖形界面,僅僅是定時記錄一些日志(5秒鐘寫一下日志),在用戶按下<Ctrl>+<C>的時候,程序退出。功能明確,Okay,let's get down to work.

1. 創建一個.NET Core Application,叫MyService

2. Nuget引入Quartz和NLog.Extensions.Logging,一個用來做定時任務,另一個用來log

3. 另外,程序使用了依賴注入,還需要用Nuget引入Microsoft.Extensions.DependencyInjection

4. 給項目增加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"autoReload="true"throwExceptions="false"internalLogLevel="Off"><variable name="theLayout" value="${date:format=HH\:mm\:ss.fff} [${level}][${logger}] ${callsite:className=False:fileName=True:methodName=False} ${message} ${onexception:${newline}}${exception:format=Message,ShortType,StackTrace:innerFormat=Message,ShortType,StackTrace:separator=\r\n:innerExceptionSeparator=\r\n---Inner---\r\n:maxInnerExceptionLevel=5}"/><targets><target name="asyncFile" xsi:type="AsyncWrapper"><target name="logfile" xsi:type="File" fileName="${basedir}/log/${shortdate}.log" layout="${theLayout}" encoding="UTF-8" /></target><target name="debugger" xsi:type="Debugger" layout="${theLayout}" /><target name="console" xsi:type="Console" layout="${theLayout}" /><target name="void" xsi:type="Null" formatMessage="false" /></targets><rules><logger name="Quartz.*" minlevel="Trace" maxlevel="Info" writeTo="void" final="true" /><logger name="*" minlevel="Debug" writeTo="asyncFile" /><logger name="*" minlevel="Trace" writeTo="debugger"/><logger name="*" minlevel="Trace" writeTo="console"/></rules>
</nlog>

還要注意的是這個文件必須復制到生成目錄去以便程序運行時候能夠加載到。

5. 增加MyServiceJobFactory.cs

using Quartz;
using Quartz.Spi;
using System;
namespace MyService {class MyServiceJobFactory : IJobFactory {protected readonly IServiceProvider _container;public MyServiceJobFactory(IServiceProvider container) {_container = container;}public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) {return _container.GetService(bundle.JobDetail.JobType) as IJob;}public void ReturnJob(IJob job) {}}
}

6. 增加PeriodLoggingJob.cs

using Microsoft.Extensions.Logging;
using Quartz;
using System;
using System.Threading.Tasks;
namespace MyService {class PeriodLoggingJob  : IJob {private readonly ILogger<PeriodLoggingJob> _logger;public PeriodLoggingJob(ILogger<PeriodLoggingJob> logger, IServiceProvider serviceProvider) {_logger = logger;}private void DoLoggingJob() {_logger.LogInformation("logging...");}public Task Execute(IJobExecutionContext context) {try {DoLoggingJob();}catch (Exception ex) { //必須妥善處理好定時任務中發生的異常_logger.LogError(ex, "執行定時任務發生意外錯誤");}returnTask.CompletedTask;}}
}

7.?Program.cs的完整內容如下

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NLog.Extensions.Logging;
using Quartz;
using Quartz.Impl;
using Quartz.Spi;
using System;
using System.Collections.Specialized;
using System.IO;
using System.Threading;
namespace MyService {class Program {//注冊各種服務static void RegisterServices(IServiceCollection services) {//日志相關services.AddSingleton<ILoggerFactory, LoggerFactory>();services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));services.AddLogging(builder => builder.SetMinimumLevel(LogLevel.Trace));//定時任務相關services.AddSingleton<IJobFactory, MyServiceJobFactory>();services.AddSingleton<PeriodLoggingJob>();}static void Main(string[] args) {//注冊退出事件處理(響應<Ctrl>+<C>)ManualResetEvent exitEvent = new ManualResetEvent(false);Console.CancelKeyPress += delegate (object sender, ConsoleCancelEventArgs e) {e.Cancel = true;exitEvent.Set();};//處理其它程序關閉事件(如kill),使得程序可以優雅地關閉AppDomain.CurrentDomain.ProcessExit += (sender, e) => { exitEvent.Set(); };//容器生成ServiceCollection services = new ServiceCollection();RegisterServices(services);using (ServiceProvider container = services.BuildServiceProvider()) {//日志初始化var loggerFactory = container.GetRequiredService<ILoggerFactory>();loggerFactory.AddNLog(new NLogProviderOptions {CaptureMessageTemplates = true,CaptureMessageProperties = true});string nlogConfigFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "NLog.config");NLog.LogManager.LoadConfiguration(nlogConfigFile);//記錄啟動日志ILogger<Program> logger = container.GetService<ILogger<Program>>();logger.LogInformation("MyService啟動.");//定時任務配置NameValueCollection props = new NameValueCollection { { "quartz.serializer.type", "binary" } };StdSchedulerFactory schedulerFactory = new StdSchedulerFactory(props);IScheduler scheduler = schedulerFactory.GetScheduler().Result;scheduler.JobFactory = container.GetService<IJobFactory>();//每天1:00執行APP狀態更新任務ITrigger periodLoggingJobTrigger = TriggerBuilder.Create().WithIdentity("PeriodLoggingJobTrigger").StartNow().WithSimpleSchedule(x=>x.WithIntervalInSeconds(5).RepeatForever()).Build();IJobDetail checkPasswordOutOfDateJob = JobBuilder.Create<PeriodLoggingJob>().WithIdentity("PeriodLoggingJob").Build();scheduler.ScheduleJob(checkPasswordOutOfDateJob, periodLoggingJobTrigger);//開啟定時服務
                scheduler.Start();//----------------------------------------↑↑↑ 程序開始 ↑↑↑----------------------------------------
                exitEvent.WaitOne();//----------------------------------------↓↓↓ 程序結束 ↓↓↓----------------------------------------//定時任務結束
                scheduler.Shutdown();//記錄結束日志logger.LogInformation("MyService停止.");}}}
}

這就是整個服務程序的完整內容,本來我可以提供一個更簡單的程序,這里啰里啰嗦寫了這么一大堆,目的還是讓初學者更加清楚.NET Core的程序結構和運行方式。其中內容包括:NLog的使用、Quartz的使用、容器及依賴注入的入門例子、如何處理程序關閉事件等,也許你想問“為什么要引入Quartz,搞這么復雜,弄個Timer不行嗎?”當然行,但Quartz更強大,而且更適合給大家演示容器與依賴注入的使用。

8. 試運行程序

運行這個程序,輸出幾條日志信息后,以<Ctrl>+<C>來結束程序的運行,這樣會在程序目錄下產生log目錄及日志文件,文件的內容大致如下:

19:03:37.117 [Info][MyService.Program] (d:\work\MyService\MyService\Program.cs:55) MyService啟動.
19:03:37.637 [Info][MyService.PeriodLoggingJob] (d:\work\MyService\MyService\PeriodLoggingJob.cs:15) logging...
19:03:42.536 [Info][MyService.PeriodLoggingJob] (d:\work\MyService\MyService\PeriodLoggingJob.cs:15) logging...
19:03:47.535 [Info][MyService.PeriodLoggingJob] (d:\work\MyService\MyService\PeriodLoggingJob.cs:15) logging...
19:03:49.293 [Info][MyService.Program] (d:\work\MyService\MyService\Program.cs:80) MyService停止.

9. 發布程序

選擇publish,在publish的目標目錄下產生一堆文件,將這些文件復制到D:\Service\MyService目錄下,一會兒我們要用到這個目錄。

2,NSSM配置

首先要獲取NSSM程序,當然是要到官網下載,版本選擇最新版,盡管它聲稱是pre-release版,但功能杠杠的,沒有任何影響,而正式版(非pre-release)則是2014年的了,太舊了。下載下來后找到對應的exe文件,叫nssm.exe。(注意有32位版和64位版的分別)
它是個綠色軟件,不需要安裝,僅此一個exe文件,把這個文件復制到C:\Windows\System32目錄下,之后經常要用。
在Windows命令行中直接敲nssm,會出現它的幫助提示。

1. 安裝服務

>nssm install MyService
出現配置界面(注意,需要管理員權限)
配置選項比較多,這是我的配置,供參考:
點“Install service”即將服務安裝好了。我們打開Windows服務來查看所安裝的服務:
服務已經安裝完畢,一切準備就緒。
2. 啟動服務
>nssm start MyService

其它一些操作
其實不用我說大家也應該知道了:

  • nssm status MyService 查看服務狀態
  • nssm stop MyService 停止服務
  • nssm restart MyService 重啟服務
  • nssm edit MyService 重新配置服務的參數
  • nssm remove MyService 刪除服務

其余的請自行參考nssm的使用手冊。

注意事項:需要用管理員身份來執行上面這些命令,否則會出現訪問拒絕的錯誤。

3,分享一些想法

2018年快過去了,回顧這一年來,我覺得我在公司所做的最大且重要的一件事情就是推動了.NET Core的應用,將能遷移的.NET Framework的程序都遷移至.NET Core了,為什么要這么干?最最主要的原因當然是要跨平臺,原先ASP.NET開發的網站,只能運行于Windows平臺,它們得依賴于IIS!Windows(作為服務器)本身就是一個非常復雜的系統,有著各種令人眼花繚亂的配置,加上IIS,就更加令人感到困惑,我同意IIS是功能強大的服務器程序,但它真的過于復雜,設計不合理,很難用,讓我等菜鳥頻頻掉到它的坑里爬不出來。IIS并不是一個能夠自由選擇版本的軟件,它的版本通常認為與Windows操作系統綁定,微軟官方并不建議安裝與Windows操作系統原生版本不一致的IIS,所以現在甚至還有公司繼續在用IIS6,而各個版本的IIS的行為卻不盡相同,默認IIS并不帶安裝ASP.NET組件,所以在Windows系統和IIS剛部署好的時候,想直接運行ASP.NET網站居然還不行,要自己去安裝ASP.NET的支持,完成后還需要使用一條額外的命令來注冊ASP.NET組件,另外還可能遇到稀奇古怪的問題,大多數問題可以通過安裝若干個補丁解決(如ASP.NET MVC的路由不起作用導致網站無法訪問的問題),而有時則不會那么順利,你得仔細看看這些補丁是否符合當前操作系統及IIS版本,甚至操作系統的語言版本也會影響你所要安裝的補丁。IIS與ASP.NET程序之間的關系也是令人很懵逼,我想讓我的ASP.NET程序自始至終運行著就是做不到,盡管應用程序池里似乎有這個選項,我在StackOverflow上針對相關問題進行過討論,有不少人頂我,但也有人說不行(我猜跟IIS版本還有關系),ASP.NET程序空閑一段時間后便被IIS踢掉——即便你的主機不差內存,你無法肯定IIS一運行你的程序就跟著跑起來,也無法肯定你的程序什么時候在運行,什么時候被踢掉,這是個類似薛定諤的貓的問題,你的ASP.NET程序就通常處于這么一種“疊加態”,你得看一看才知道確切它是否在運行,這一看,才使得程序從“疊加態”坍縮為“生態”或“死態”,且從“死態”轉入“生態”還需要耗費好些時間,表現為第一次打開頁面時候的長時間卡頓,跟客戶演示系統,有時候會很尷尬。我曾經為了讓程序不被IIS踢掉,還手工寫了一個KeepAlive的小程序,定時去get我的網站的首頁,實在奇葩。微軟對此的解釋是:IIS并不是為long-term程序設計的,你想在IIS里做一個準時的定時服務,那是相當不妥,根本不是為這種事情設計的,所以不好用不能怪我。我承認這當然是一種設計,但ASP.NET網站除了提供網頁之外,跑一些后臺服務也應該是很正常的吧?沒辦法,于是我將服務和網站分開,中間用總線溝通,聽起來很cool?——其實這是一段悲傷的往事,不過說來話長,以后有機會再提了。.NET Core出現了,ASP.NET Core也和它一起到來,2.0版開始就是一個很完善的版本,我想是時候上了,這是工作量很大的差事,但為了將來更好的發展,我們必須經歷這個艱難的爬坡,所幸的是現在一切都已轉入正軌,我預想的目的達到了。

.NET Core的一大特點就是程序都可以獨立運行,包括ASP.NET Core程序,不再依賴于IIS,我可以根據業務的需要,將系統劃分為多個模塊,方便開發分工和測試,這些模塊甚至不需要部署在同一臺主機上,極大提高了靈活性。一般來說,我還是推薦將程序部署至Linux環境,理由依舊是Linux作為服務器操作系統的使用體驗遠遠好于Windows,Windows實在太過復雜了!但也有例外,如果遇到缺乏Linux支持技術的客戶的情況,那就把程序部署到他們的Windows主機上吧,無所謂,反正.NET Core是跨平臺的。

不知這是不是我2018年的最后一篇博客,如果是,上面這段文字就算是我對今年自己的主要工作總結吧。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/276705.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/276705.shtml
英文地址,請注明出處:http://en.pswp.cn/news/276705.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

SparkSession.read().csv()無法定位本地文件的問題

原因是spark有兩個文件頭 [file://]代表本地 [hdfs://]代表hdfs路徑 如果路徑沒有文件頭&#xff0c;spark會將該路徑默認添加上"hdfs://" 所以如果要訪問本地csv文件&#xff0c;需要確保路徑前面有"file://" //java代碼&#xff0c;告訴spark這是本地文件…

JavaScript的10種跨域共享的方法

在客戶端編程語言中&#xff0c;如javascript和ActionScript&#xff0c;同源策略是一個很重要的安全理念&#xff0c;它在保證數據的安全性方面有著重要的意義。同源策略規定跨域之間的腳本是隔離的&#xff0c;一個域的腳本不能訪問和操作另外一個域的絕大部分屬性和方法。那…

好用的shell工具_精選5個酷斃的Python工具

來自&#xff1a;Python之禪工欲善其事必先利其器&#xff0c;一個好的工具能讓起到事半功倍的效果&#xff0c;Python社區提供了足夠多的優秀工具來幫助開發者更方便的實現某些想法&#xff0c;下面這幾個工具給我的工作也帶來了很多便利&#xff0c;推薦給追求美好事物的你。…

承載輝煌歷史 暢想無線未來

看了JustDI的文章“手機也能當電腦用&#xff1f;&#xff0d;&#xff0d;談談未來智能手機操作系統的走向”&#xff0c;作為嵌入式愛好者&#xff0c;我也想談談自己的看法。首先&#xff0c;從網絡發展的角度看&#xff0c;移動互聯網的寬帶化&#xff0c;寬帶互聯網的移動…

接口碼釋義

1xx&#xff1a;信息&#xff0c;請求收到&#xff0c;繼續處理 2xx&#xff1a;成功&#xff0c;行為被成功地接受、理解和采納 3xx&#xff1a;重定向&#xff0c;為了完成請求&#xff0c;必須進一步執行的動作 4xx&#xff1a;客戶端錯誤&#xff0c;請求包含語法錯誤或…

java讀取文件內容,文件頭有\ufeff

"\ufeff"是UTF-8 BOM編碼的文件頭&#xff0c;代表該文件按照什么字節順序排序 調用java的工具類[ UnicodeInputStream ]即可解決這個問題 //第二個參數targetEncoding為null時在getDetectedEncoding方法中會自動檢測編碼類型 UnicodeInputStream unicodeInputStrea…

6款國內外SNS開源軟件 搭建社交網站利器

SNS(Social Network Service)&#xff0c;有時稱為社交網絡&#xff0c;有時稱為社會化網絡&#xff0c;專指旨在幫助人們建立社會性網絡的互聯網應用服務。如果對SNS概念還很模糊&#xff0c;說到人人網、開心網你就明白了。 去年360圈、螞蟻網接連關站給SNS前景蒙上一層陰影&…

aop實現原理_從宏觀的實現原理和設計本質入手,帶你理解 AOP 框架的原理

點擊上方“Java知音”&#xff0c;選擇“置頂公眾號”技術文章第一時間送達&#xff01;作者&#xff1a;FeelsChaoticjuejin.im/post/5c57b2d5e51d457ffd56ffbb前言本文將從另一個角度講解 AOP&#xff0c;從宏觀的實現原理和設計本質入手。大部分講 AOP 的博文都是一上來就羅…

孟憲會老師推薦的一部C#圖解教程

Amazon五星級盛譽 C# 最新特性一覽無余 數十年開發與教學經驗的結晶 圖、表和文字完美結合&#xff0c;最易學的C# 教程 本書詳細信息&#xff1a;http://www.china-pub.com/43556 微軟4大名著評選結果揭曉&#xff1a;http://www.china-pub.com/static07/0812/jsj_micrmingz_0…

Python數據分析Numpy庫方法簡介(三)

補充&#xff1a; np.ceil()向上取整 3.1向上取整是4 np.floor()向下取整 數組名.resize((m,n)) 重置行列 基礎操作 np.random.randn()符合正態分布(鐘行/高斯)的數據 矩陣的水平拼接 np.vstack((a,b)) 矩陣的垂直拼接 np.hstack((a,b)) 點陣積&#xff1a; np.dot(a,b)/ ab…

xxl-job源碼分析

xxl-job源碼分析 xxl-job 系統說明 安裝 安裝部署參考文檔&#xff1a;分布式任務調度平臺xxl-job 功能 定時調度、服務解耦、靈活控制跑批時間&#xff08;停止、開啟、重新設定時間、手動觸發&#xff09; XXL-JOB是一個輕量級分布式任務調度平臺&#xff0c;其核心設計目標是…

定制jQuery File Upload為微博式單文件上傳

原文鏈接&#xff1a;http://avnpc.com/pages/single-file-upload-component-by-jquery-file-upload jQuery File Upload是一個非常優秀的上傳組件&#xff0c;主要使用了XHR作為上傳方式&#xff0c;并且利用了相當多的現代瀏覽器功能&#xff0c;所以可以實現諸如批量上傳、超…

vb趣味編程彈球小游戲_最好玩的微信小游戲集合,總有一款是你沒玩過的

大家好&#xff0c;這里是小雅龍生活趣味時間&#xff0c;自從17年微信推出小游戲程序以來&#xff0c;微信小游戲行業可謂是炙手可熱&#xff0c;知道2019年不斷有許許多多的微信小游戲如雨后春筍般的生根發芽。下面就由我帶大家來看看今年最好玩&#xff0c;最受歡迎的微信小…

開發MOSS2007 Masterpage的一些經驗

一直在做MOSS平臺的Masterpage開發,碰到很多的問題,總結了一些經驗,特此記錄: masterpage的所有的ContentPlaceholder詳細解釋見以下網址:http://www.cnblogs.com/WinYoung/archive/2007/06/25/791766.html 1.如果應用masterpage以后IE狀態欄出現""網頁指令碼錯誤訊息…

Golang——垃圾回收GC(2)

1 垃圾回收中的重要概念 1.1 定義 In computer science, garbage collection (GC) is a form of automatic memory management. The garbage collector, or just collector, attempts to reclaim garbage, or memory occupied by objects that are no longer in use by the pro…

java gui框架_推薦!程序員整理的Java資源大全

構建這里搜集了用來構建應用程序的工具。Apache Maven&#xff1a;Maven使用聲明進行構建并進行依賴管理&#xff0c;偏向于使用約定而不是配置進行構建。Maven優于Apache Ant。后者采用了一種過程化的方式進行配置&#xff0c;所以維護起來相當困難。Gradle&#xff1a;Gradle…

帆軟報表(finereport)控件背景色更改

setTimeout(function() {$(.fr-trigger-btn-up).css({"background-color": "#003399" });}, 100); 轉載于:https://www.cnblogs.com/Williamls/p/11571586.html

開心網分析,師從“中國緣”

作者&#xff1a;麥田   一&#xff0c;師從“中國緣” 開心網從08年“爆發”之后&#xff0c;網上出現很多評論文章。幾乎100%的評論文章都談到了開心網“不可思議”的爆發增長速度&#xff0c;比如幾個月就進入了alexa前500等等。但是&#xff0c;幾乎沒有一篇文章提到“開心…

HTML5+CSS3+JQuery1.9 輸入框切換和Div失焦模擬

Div失焦原理&#xff1a;判斷document點擊對象是否在Div容器以內&#xff0c;否則觸發事件 需要腳本&#xff1a;jquery-1.9.1.js 下載地址&#xff1a;http://download.csdn.net/detail/dmtnewtons/5807757 <!DOCTYPE> <html> <head> <meta http-equ…

資本冬天已至,開發者卻可以著眼未來

云&#xff0c;在國內外都已成為軟件開發者的首選服務。縱觀歷史&#xff0c;在云計算發展的這些年里&#xff0c;不管云上做了多少產品和服務&#xff0c;其實都離不開云最本質的價值體系&#xff1a;自服務、高彈性、按需提供、免運維&#xff0c;這些特性也讓云服務天然成為…