zoukankan      html  css  js  c++  java
  • 关于使用Topshelf创建服务

    志铭-2021年12月13日 21:21:36

    0. 背景说明

    我为什么要创建windows服务?因为有一个操作需要我定时执行,这个操作可以是定时的数据同步或是周期性的某种操作

    之前都是按照以下方式实现的:

    • 若是数据库层面定时任务,则创建一个定时作业

    • 其他操作,则创建一个控制台程序,使用windows服务自带的任务计划程序,定时周期性执行该控制台程序

    而目前出现了问题:

    • 数据库作业,因为我使用的数据库账号没有看不到“SQL Server代理”,就是没有权限创建作业

    • 控制台程序因为引用其他的dll文件所以偶尔出现被杀毒软件删除,需要加入白名单。但也意味着其不够健壮。



    1. 使用Topshelf组件创建Windows服务

    使用Topshelf可以便捷的创建一个Windows服务

    该组件可以通过创建一个控制台程序,实现一个windows服务

    该组件可以配合Log4net和Quartz.net实现记录日志和定时执行

    Nuget:
     Install-Package Topshelf -Version 4.3.0
     Install-Package Topshelf.Log4Net -Version 4.3.0(注:依赖于Log4net)
     Install-Package Topshelf.Quartz -Version 0.4.0.1(注:依赖于Quartz)
    

    1.1 依赖Quartz.net实现定时任务

    定义任务类,实现Ijob接口

    //using log4net;
    //using Quartz;
    /// <summary>
    /// 任务类,定义我们需要调度的任务
    /// </summary>
    public class MyJob : IJob
    {
        private readonly ILog _logger = LogManager.GetLogger(typeof(MyJob));
        public void Execute(IJobExecutionContext context)
        {
            //todo:你所期望实现定时执行的操作
            //计入日志
            _logger.InfoFormat("任务执行成功");
        }
    }
    

    简单的安装单例模式封装一个调度器类,
    这里调度规则简单示例为每隔5s执行一次

    //using Quartz;
    //using Quartz.Impl;
    /// <summary>
    /// 任务调度器类,用于创建调度器并配置调度计划
    /// </summary>
    public class MyScheduler
    {
        private static readonly MyScheduler myScheduler = new MyScheduler();
        //调度器
        public IScheduler scheduler { private set; get; }
        //任务
        public IJobDetail job { private set; get; }
        //触发器
        public ISimpleTrigger trigger { private set; get; }
        private MyScheduler()
        {
            scheduler = StdSchedulerFactory.GetDefaultScheduler();
            job = JobBuilder.Create<MyJob>().Build();
            trigger = TriggerBuilder.Create()
                .StartNow()
                .WithSimpleSchedule(x => x.WithIntervalInSeconds(5).RepeatForever())//配置每隔5s执行一次,永久执行下去
                .WithIdentity("trigger", "group").Build() as ISimpleTrigger;
            scheduler.ScheduleJob(job, trigger);
        }
        //单例模式,用于返回单例对象
        public static MyScheduler GetMyScheduler()
        {
            return myScheduler;
        }
    }
    

    1.2 依赖于Topshelf创建服务类

    //using Quartz;
    //using Topshelf;
    /// <summary>
    /// 服务类,实现相应接口,定义服务启动和停止执行的操作
    /// </summary>
    public class MyServiceRunner : ServiceControl, ServiceSuspend
    {
        private readonly IScheduler scheduler = MyScheduler.GetMyScheduler().scheduler;
        #region ServiceControl接口需要实现的方法
        public bool Start(HostControl hostControl)
        {
            scheduler.Start();
            return true;
        }
        public bool Stop(HostControl hostControl)
        {
            scheduler.Shutdown(false);
            return true;
        }
        #endregion
        #region ServiceSuspen接口需要实现的方法
        public bool Continue(HostControl hostControl)
        {
            scheduler.ResumeAll();
            return true;
        }
        public bool Pause(HostControl hostControl)
        {
            scheduler.PauseAll();
            return true;
        }
        #endregion
    }
    

    1.3 log4net的配置文件log4net.config

    这里通过配置将日志信息写入到数据库中

    • 日志表建表语句
      CREATE TABLE [dbo].[Log4net] (
      [Id] [int] IDENTITY (1, 1) NOT NULL,
      [Date] [datetime] NOT NULL,
      [Thread] [varchar] (255) NOT NULL,
      [Level] [varchar] (50) NOT NULL,
      [Logger] [varchar] (255) NOT NULL,
      [Message] [varchar] (4000) NOT NULL,
      [Exception] [varchar] (2000) NULL
      )
      
    • 注意数据库的连接字符串需要单独写在项目的配置文件中
    • 右键log4net.config-->属性-->复制的输出目录:始终复制
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    <!-- ...................................为Log4Net添加的配置.....开始.................................-->
        <configSections>
        	<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
        </configSections>
        <log4net>
        	<root>
        		<level value="ALL"></level>
        		<appender-ref ref="AdoNetAppender"></appender-ref>
        	</root>
        	<appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
        		<bufferSize value="1" />
        		<connectionType value="System.Data.SqlClient.SqlConnection,System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
        		<connectionStringName value="ConnectionStringLogging" />
        		<commandText value="INSERT INTO Log4net ([Date],[Thread],[Level],[Logger],[Message],[Exception]) 
        						    VALUES (@log_date, @thread, @log_level, @logger, @message, @exception)" />
        		<parameter>
        			<parameterName value="@log_date" />
        			<dbType value="DateTime" />
        			<layout type="log4net.Layout.RawTimeStampLayout" />
        		</parameter>
        		<parameter>
        			<parameterName value="@thread" />
        			<dbType value="String" />
        			<size value="255" />
        			<layout type="log4net.Layout.PatternLayout">
        				<conversionPattern value="%thread" />
        			</layout>
        		</parameter>
        		<parameter>
        			<parameterName value="@log_level" />
        			<dbType value="String" />
        			<size value="50" />
        			<layout type="log4net.Layout.PatternLayout">
        				<conversionPattern value="%level" />
        			</layout>
        		</parameter>
        		<parameter>
        			<parameterName value="@logger" />
        			<dbType value="String" />
        			<size value="255" />
        			<layout type="log4net.Layout.PatternLayout">
        				<conversionPattern value="%logger" />
        			</layout>
        		</parameter>
        		<parameter>
        			<parameterName value="@message" />
        			<dbType value="String" />
        			<size value="4000" />
        			<layout type="log4net.Layout.PatternLayout">
        				<conversionPattern value="%message" />
        			</layout>
        		</parameter>
        		<parameter>
        			<parameterName value="@exception" />
        			<dbType value="String" />
        			<size value="2000" />
        			<layout type="log4net.Layout.ExceptionLayout" />
        		</parameter>
        	</appender>
        </log4net>
    <!-- ...................................为Log4Net添加的配置.....结束.................................-->
    </configuration>
    

    1.4 主函数中配置服务信息

    static void Main(string[] args)
    {
        //这里读取log4net.config
        log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo(AppDomain.CurrentDomainBaseDirectory + "log4net.config"));
        HostFactory.Run(x =>
        {
            x.UseLog4Net();
            x.Service<MyServiceRunner>();
            x.SetDescription("服务描述");
            x.SetDisplayName("服务显示名称");
            x.SetServiceName("服务名称");
            x.EnablePauseAndContinue();
        });
    }
    

    1.5 安装服务

    编译后得到的控制台程序 xxx.exe,

    以管理员的身份运行命令行程序,跳转到程序所在路径,执行该命令

    xxx.exe install
    

    win+R:services.msc 打开服务管理器,找到安装的服务并右键启动

    找到自己安装的服务,可以右键属性设置其他一些功能,如服务失败后的操作等

    卸载该服务,则先在停止,在按照安装的方法,管理员身份打开命令行,跳转程序路径,执行

    xxx.exe uninstall
    


    3. Demo源码下载及参考文档

    若是简单的周期执行,也可以不使用Quartz .net ,而是使用System.Timers.Timer对象实现间隔一定的时间执行一次任务
    而且可以抛弃Log4net,只是简单的使用 System.IO.StreamWriter在服务本地简单的记录一个日志文本
    该功能就是Topshelf官方文档中的最简的入门示例

    作者:shanzm
    欢迎交流,欢迎指教!
  • 相关阅读:
    Leetcode 127 **
    Leetcode 145
    Leetcode 144
    Leetcode 137
    Leetcode 136
    重写nyoj2——括号匹配
    堆排序
    Leetcode 150
    【转】个人最常用的Eclipse快捷键
    Ajax编程中,经常要能动态的改变界面元素的样式
  • 原文地址:https://www.cnblogs.com/shanzhiming/p/15682889.html
Copyright © 2011-2022 走看看