7

把 Console 部署成 Windows 服务,四种方式总有一款适合你!

 4 years ago
source link: https://www.cnblogs.com/huangxincheng/p/13913004.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

把 Console 部署成 Windows 服务,四种方式总有一款适合你!

1. 讲故事

上周有一个项目交付,因为是医院级项目需要在客户的局域网独立部署。 程序: netcore 2.0,操作系统: windows server 2012,坑爹的事情就来了, netcore sdk 一直装不上,网上找了资料说需要先安装 Visual C++ Redistributable for Visual Studio 2015, 开开心心下载下来又是安装失败,再次找资料说要打一堆 系统补丁,搞了一天!!!???

环境总算是装好了,因为是 Console 服务程序,还得给它做成 windows service,看公司以前的部署方式都是采用 vs 的 windows service 模板,如下图:

214741-20201102095550243-148175327.png

怎么说呢,这种方式太老旧了,这篇就来聊聊除了这种还有其他三种很有意思的服务部署方式,干脆拿在一起比较比较吧!

2. 测试代码

为了能更加正规化一些,我在 Console 中监听 Ctrl + C 事件,代码如下:


    public class Program
    {
        public static void Main(string[] args)
        {
            var dir = AppDomain.CurrentDomain.BaseDirectory;


            var cts = new CancellationTokenSource();

            var bgtask = Task.Factory.StartNew(() => { TestService.Run(cts.Token); });

            Console.CancelKeyPress += (s, e) =>
            {
                TestService.Log($"{DateTime.Now} 后台测试服务,准备进行资源清理!");

                cts.Cancel();
                bgtask.Wait();

                TestService.Log($"{DateTime.Now} 恭喜,Test服务程序已正常退出!");
            };

            TestService.Log($"{DateTime.Now} 后端服务程序正常启动!");

            bgtask.Wait();
        }
    }

有了这个模板,再定义一个 TestService,用于不断的执行后台任务,代码如下:


    public class TestService
    {
        public static void Run(CancellationToken token)
        {
            while (!token.IsCancellationRequested)
            {
                Console.WriteLine($"{DateTime.Now}: 1. 获取mysql");
                System.Threading.Thread.Sleep(2000);
                Console.WriteLine($"{DateTime.Now}: 2. 获取redis");
                System.Threading.Thread.Sleep(2000);
                Console.WriteLine($"{DateTime.Now}: 3. 更新monogdb");
                System.Threading.Thread.Sleep(2000);
                Console.WriteLine($"{DateTime.Now}: 4. 通知kafka");
                System.Threading.Thread.Sleep(2000);
                Console.WriteLine($"{DateTime.Now}: 5. 所有业务处理完毕");
                System.Threading.Thread.Sleep(2000);
            }
        }

        public static void Log(string msg)
        {
            Console.WriteLine(msg);
            File.AppendAllText(AppDomain.CurrentDomain.BaseDirectory + "//1.log", $"{msg}\r\n");
        }
    }

二:四种服务部署方式

1. 传统的 Windows Service 模板

相信做过 windowsservice 部署的朋友都知道这种方式,需要在 vs 中新建模板,然后定义一个子类 MySerivce 继承于 ServiceBase ,重写父类的 OnStart 和 OnStop 方法,代码如下:


    partial class MyService : ServiceBase
    {
        CancellationTokenSource cts = new CancellationTokenSource();

        Task bgtask;

        public MyService()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            // TODO: Add code here to start your service.
            bgtask = Task.Factory.StartNew(() => { TestService.Run(cts.Token); });
        }

        protected override void OnStop()
        {
            // TODO: Add code here to perform any tear-down necessary to stop your service.
            cts.Cancel();
            bgtask.Wait();
        }
    }

再重构一下 Main 方法:


    public class Program
    {
        public static void Main(string[] args)
        {
            ServiceBase.Run(new MyService());
        }
    }

最后执行 publish 发布,用 windows 自带的 sc 安装服务。


sc create MyService BinPath=E:\net5\ConsoleApp1\ConsoleApp2\bin\Release\netcoreapp3.1\publish\ConsoleApp2.exe
sc start MyService

为了验证程序是否运行正常,可以去服务面板以及安装路径查看启动日志。

214741-20201102095550600-1467657149.png

接下来说说优缺点吧:

  • 缺点:需要修改代码,而且一旦代码改完后,就不能再双击 exe 执行,导致无法调试。
  • 优点:不需要额外依赖,全部采用内建技术。

2. 使用开源的 Topshelf

大家有兴趣可以看一下它的官网: http://topshelf-project.com 比较轻便简洁,使用 nuget Install-Package Topshelf 接入项目,按照官方demo我需要在 TestService 中实现 Start 和 Stop 方法,修改如下:


public class TestService
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        CancellationToken token;
        Task bgtask;

        public TestService()
        {
            token = cts.Token;
        }

        public void Start()
        {
            bgtask = Task.Run(() =>
            {
                while (!token.IsCancellationRequested)
                {
                    Log($"{DateTime.Now}: 1. 获取mysql");
                    System.Threading.Thread.Sleep(2000);
                    Log($"{DateTime.Now}: 2. 获取redis");
                    System.Threading.Thread.Sleep(2000);
                    Log($"{DateTime.Now}: 3. 更新monogdb");
                    System.Threading.Thread.Sleep(2000);
                    Log($"{DateTime.Now}: 4. 通知kafka");
                    System.Threading.Thread.Sleep(2000);
                    Log($"{DateTime.Now}: 5. 所有业务处理完毕");
                    System.Threading.Thread.Sleep(2000);
                }
            });
        }

        public void Stop()
        {
            cts.Cancel();
            bgtask.Wait();
        }

        public static void Log(string msg)
        {
            Console.WriteLine(msg);
            File.AppendAllText(AppDomain.CurrentDomain.BaseDirectory + "1.log", $"{msg}\r\n");
        }
    }

接下来再改造一下 Main 方法,使用它的 HostFactory 类,代码如下:


        public static void Main(string[] args)
        {
            var rc = HostFactory.Run(x =>                                   //1
            {
                x.Service<TestService>(s =>                                   //2
                {
                    s.ConstructUsing(name => new TestService());            //3
                    s.WhenStarted(tc => tc.Start());                         //4
                    s.WhenStopped(tc => tc.Stop());                          //5
                });
                x.RunAsLocalSystem();                                       //6
                x.StartAutomatically();

                x.SetDescription("TestService2 Topshelf Host");                   //7
                x.SetDisplayName("MyService2");                                  //8
                x.SetServiceName("MyService2");                                  //9
            });                                                             //10

            var exitCode = (int)Convert.ChangeType(rc, rc.GetTypeCode());  //11

            Environment.ExitCode = exitCode;
        }

从上面代码可以看出,主要还是做一些服务的信息配置,然后就可以发布项目,使用 xxx.exe install 进行服务安装,如下图:


E:\net5\ConsoleApp1\ConsoleApp5\bin\Release\netcoreapp3.1\publish2>ConsoleApp5.exe install
Configuration Result:
[Success] Name MyService2
[Success] Description TestService2 Topshelf Host
[Success] ServiceName MyService2
Topshelf v4.2.1.215, .NET Framework v3.1.9

Running a transacted installation.

Beginning the Install phase of the installation.
Installing MyService2 service
Installing service MyService2...
Service MyService2 has been successfully installed.

The Install phase completed successfully, and the Commit phase is beginning.

The Commit phase completed successfully.

The transacted install has completed.

从输出信息来看已经安装成功,大家感觉这种方式优缺点如何?

  • 缺点:需要安装第三方工具包,需要修改代码,而且还挺大的。。。
  • 优点:双击也可调试,实现了系统的一些内建监听,比如 Ctrl + C

3. 使用微软新内置的 Hosting

说到这个 Hosting 相信大家不会陌生,在 netcore 中不管是 Console, MVC,WebApi 都是 Console 模式,比如我新建一个如下 WebApi。

这里我就有想法了,能不能把 Main 中的 Hosting 扣出来给我的服务用,那真的是??了,还别说,真的可以,安装一个 hosting + for windowsservice 即可。


nuget Install-Package Microsoft.Extensions.Hosting
nuget Install-Package Microsoft.Extensions.Hosting.WindowsServices

值得庆幸的是,包的最小依赖是 .NETStandard 2.0 ,意味着 .NET Framework 4.6.1 + 和 .NetCore 2.0 + 都可以用的上,??

接下来就是改造,让 TestService 重写的父类 BackgroundService 中的 ExecuteAsync 方法,如下代码:


    public class TestService : BackgroundService
    {
        protected override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            return Task.Run(() =>
            {
                while (!stoppingToken.IsCancellationRequested)
                {
                    Log($"{DateTime.Now}: 1. 获取mysql");
                    System.Threading.Thread.Sleep(2000);
                    Log($"{DateTime.Now}: 2. 获取redis");
                    System.Threading.Thread.Sleep(2000);
                    Log($"{DateTime.Now}: 3. 更新monogdb");
                    System.Threading.Thread.Sleep(2000);
                    Log($"{DateTime.Now}: 4. 通知kafka");
                    System.Threading.Thread.Sleep(2000);
                    Log($"{DateTime.Now}: 5. 所有业务处理完毕");
                    System.Threading.Thread.Sleep(2000);
                }
            });
        }

        public static void Log(string msg)
        {
            Console.WriteLine(msg);
            File.AppendAllText(AppDomain.CurrentDomain.BaseDirectory + "1.log", $"{msg}\r\n");
        }
    }

然后再改造 Main 方法。


    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
            .UseWindowsService()
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<TestService>();
            });
    }

哇! 是不是熟悉的代码映入眼前,双击 Console 是不是更加熟悉了哈~~~

214741-20201102095551830-296982.png

最后可以用 sc 命令做成服务。

  • 缺点:有少量的代码侵入性,引入的依赖稍多
  • 优点:微软正派血统,功能强大,内建日志支持

4. nssm 第三方工具

前面三种要么是内建模板,要么是安装 dll 的方式,那有没有一种真的可以对代码 零侵入 呢? 大千世界无奇不有,可以看一下这款工具:http://www.nssm.cc ,你无需修改任何代码, 直接发布代码后用下面命令安装即可:


C:\Windows\system32>cd C:\xcode\soft\nssm-2.24\win64

C:\xcode\soft\nssm-2.24\win64>nssm  install TestService3 E:\net5\ConsoleApp1\ConsoleApp6\bin\Release\netcoreapp3.1\publish\ConsoleApp6.exe && nssm start TestService3
Service "TestService3" installed successfully!
TestService3: START: 操作成功完成。

看到没有,我真的没有动任何代码,服务就安装完成了。

  • 缺点:需要安装第三方工具
  • 优点:对代码零侵入

如果让我选择的话,我喜欢 3+4 的组合,代码层面我更愿意使用 微软新的 Hosting 承载,服务部署上更喜欢 nssm,毕竟它比 sc 灵活强大的多,不知道大家更喜欢哪一种部署方式呢? 欢迎留言补充! ???

更多高质量干货:参见我的 GitHub: dotnetfly

图片名称

Recommend

  • 52
    • blockchain.51cto.com 6 years ago
    • Cache

    这四种安全方式为区块链保驾护航

    区块链自诞生之日起,就伴随着不少反驳的声音。新鲜事物便是如此,在有人的眼里它价值千金,仓箱可期,有人则认为它分文不值,将其视如草芥。但在越来越多的案例看来,区块链确实能成功运用于譬如农业科技、电商、医疗、甚至是艺术品等收...

  • 44

  • 50
    • www.ruanyifeng.com 6 years ago
    • Cache

    OAuth 2.0 的四种方式

    上一篇文章介绍了 OAuth 2.0 是一种授权机制,主要用来颁发令牌(token)。本文接着介绍颁发令牌的实务操作。 下面我假定,你已经...

  • 27
    • www.cnblogs.com 5 years ago
    • Cache

    多线程的四种实现方式

    JDK5.0之后Java多线程的实现方式变成了四种,下面来简单的列举一下,如果需要更深入的了解,强烈建议阅读一下源码。 一、继承Thread类重写run()方法: 1. 创建一个继承于Thread类的子类 2. 重写Thread类的run()...

  • 12

    使用 for 遍历 List可以使用 for 来遍历 List,代码如下: thislist = ["apple", "banana", "cherry"] for x in thislist: print(x) PS E:\dream\markdown\python> & "C:/Program Files (x86)/Python/python.exe" e:/dream/m...

  • 26

    Microsoft在最新的C#版本中引入了Source Generator。这是一项新功能,可以让我们在代码编译时生成源代码。在本文中,我将介绍四种C#中的代码生成方式,以简化我们的日常工作。然后,您可以视情况选择正确的方法。 在 .NET 中,我们有以下几种方法来...

  • 6
    • www.androidchina.net 4 years ago
    • Cache

    Android多线程的四种方式

    Android多线程的四种方式 – Android开发中文站当我们启动一个App的时候,Android系统会启动一个Linux Process,该Process包含一个Thread,称为UI Thread或Main Thread。通常一个应用的所有组件都运行在这一个Process中,当然,你可以通过修改四大组件在Manifest...

  • 10

    有数据显示,有约98%的网站曾经遭受黑客攻击。也就是说,几乎所有的网站,都被黑客送过“温暖”,它们无时无刻都在关注着我们的网站,有时候他们对网站的关注程度,甚至超过了我们。 在这里,给广大的黑客朋友们说一句:“你们辛苦了。”...

  • 10

    比较 JavaScript 中的原始值非常简单。只需使用任何一种可用的相等运算符即可,例如严格相等运算符:'a' === 'c'; // => false1 === 1; // => true但是对象却有结构...

  • 7

    免费!Windows 11 正式版来了,这四种方式快速升级...老电脑也能用-极果 免费!Windows 11 正式版来了,这四种方式快速升...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK