把 Console 部署成 Windows 服务,四种方式总有一款适合你!
一:背景
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 扣出来给我的服务用,那真的是
把 Console 部署成 Windows 服务,四种方式总有一款适合你!的更多相关文章
- prerender.io 搜索引擎优化 部署成windows服务 实现开机自动开启服务
		
一 prerender.io服务端部署 参考官方网站的部署步骤: $ git clone https://github.com/prerender/prerender.git $ cd preren ...
 - Tomcat和Mysql部署成Windows服务
		
如题: Tomcat部署进入到Tomcat的bin目录,执行命令:service.bat install [service_name]安装完毕后服务中能看见Apache Tomcat 7.0 [se ...
 - PHP/HTML混写的四种方式
		
[整理]PHP/HTML混写的四种方式 PHP作为一款后端语言,为了输出给浏览器让浏览器呈现出来,无可避免的要输出HTML代码,下文介绍下我用过的三种PHP/HTML混编方法 1.单/双引号包围法 ...
 - Postgresql数据库部署之:Postgresql本机启动和Postgresql注册成windows 服务
		
1.初始化并创建数据库(一次即可) initdb \data --locale=chs -U postgres -W You can now start the database server u ...
 - 部署Redis 成windows服务
		
Redis是可以安装成windows服务的,开机自启动,命令如下: redis-server --service-install redis.windows.conf 安装完之后,就可看到Redis已 ...
 - Java Service Wrapper将jar包安装成Windows服务
		
刚接触java,第一次使用Java开发windows服务,也是刚不久看了SSM框架 简直也是一头雾水,不过只要用心理解,其实很简单,下面有详细的步骤,包学包会 在windows上运行jar包,需要在工 ...
 - Windows Server 2008R2配置MySQL Cluster并将管理节点和数据节点配置成windows服务
		
说明:将mysql的管理节点和数据节点配置成windows服务是为了防止有人手误关闭管理节点或数据节点的dos命令窗口,管理节点或数据节点的命令窗口误关闭可能会造成mysql某台或某几台mysql不能 ...
 - 使用NSSM把.Net Core部署至 Windows 服务
		
为什么部署至Windows Services 在很多情况下,很少会把.Net Core项目部署至Windows服务中,特别是Asp.net Core就更少了.一般情况下,Asp.net Core会部署 ...
 - 使用srvany.exe将程序安装成windows服务的详细教程
		
srvany.exe介绍 srvany.exe是Microsoft Windows Resource Kits工具集的一个实用的小工具,用于将任何EXE程序作为Windows服务运行.也就是说srva ...
 
随机推荐
- python3-day1
			
一.python的优缺点: 先看优点 Python的定位是"优雅"."明确"."简单",所以Python程序看上去总是简单易懂,初学者学Py ...
 - Spring Boot(二) :Redis 使用
			
Redis 介绍 Redis 是目前业界使用最广泛的内存数据存储.相比 Memcached,Redis 支持更丰富的数据结构,例如 hashes, lists, sets 等,同时支持数据持久化.除此 ...
 - C#Messenger分析和使用方法
			
目录 源码分析 使用方法 没有返回值没有传参的消息写法 没有返回值有参数的消息写法 有返回值的消息写法 有参数有返回值的委托 C#Messenger是UntiyCommunity里的一个工具类,其效果 ...
 - Linux NSF网络共享盘
			
服务器安装: yum -y install nfs-utils rpcbind 服务器配置 :vi /etc/exports 例: /root/docs 192.168.1.*(rw,sync,no ...
 - springboot整合Mangodb实现crud,高级查询,分页,排序,简单聚合
			
//linux安装mangodb教程:https://www.cnblogs.com/yangxiaohui227/p/11347832.html 1.引入maven 依赖 <dependenc ...
 - Linux软件管理常用命令和选项
			
rpm /var/lib/rpm:数据库目录 -ivh x:安装软件包x -Uvh x:升级或安装软件包x,如果没有安装x的旧版本,则安装x,否则删除x的旧版本后再安装x. -Fvh x:升级软件包x ...
 - linux内核 idr机制
			
idr机制解决了什么问题?为什么需要idr机制(或者说,idr机制这种解决方案,相对已有的其他方案,有什么优势所在) ? idr在linux内核中指的就是整数ID管理机制,从本质上来说,这就是一种将整 ...
 - 题目:写出一条SQL语句,查询工资高于10000,且与他所在部门的经理年龄相同的职工姓名。
			
create table Emp( eid char(20) primary key, ename char(20), age integer check (age > 0), did char ...
 - 【题解】Computer Network
			
Description 给你一棵N(N<=10000)个节点的树,求每个点到其他点的最大距离. Input 第一行一个数N.接下来若干行每行两个数k,t描述一条点k到点t的边(输入数据保证无重复 ...
 - k8s的namespace一直Terminating的完美解决方案
			
k8s的namespace一直Terminating的完美解决方案 在k8s集群中进行测试删除namespace是经常的事件,而为了方便操作,一般都是直接对整个名称空间进行删除操作. 相信道友们在进行 ...