咱们先来介绍一下使用它的好处,如下论述参考自其余大神。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