关于c#:把-Console-部署成-Windows-服务四种方式总有一款适合你

45次阅读

共计 6645 个字符,预计需要花费 17 分钟才能阅读完成。

一:背景

1. 讲故事

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

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

怎么说呢,这种形式太老旧了,这篇就来聊聊除了这种还有其余三种很有意思的服务部署形式,罗唆拿在一起比拟比拟吧!

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

为了验证程序是否运行失常,能够去服务面板以及装置门路查看启动日志。

接下来说说优缺点吧:

  • 毛病:须要批改代码,而且一旦代码改完后,就不能再双击 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 是不是更加相熟了哈~~~

最初能够用 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

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

正文完
 0