编写Windows服务疑问1:操作过程

作者: 虞磨

更新时间:2022-03-26 13:11:46

2142 阅读

 

Windows 服务开发平时不太受人关注,毕竟那是高大上的项目类型,平常需求也用不上,很多老掉牙的家伙也只知有WinForm,仍不知有WPF,更别说Windows 服务了,正如陶渊明所写的,“不知有汉,无论魏晋”。

通常,就算要让程序开机启动,多数也只考虑设置一个启动项,也很少去想到开发Windows服务。如果程序需要自动启动,并且希望在后台完成一些东东,其实使用Windows服务也不错的。

正因为用的人少,那么说的人更少了,使得不了解它,想学又找不到资料的人也多。没事,老周没什么资本,唯一值得骄傲就是颜值低,尤其是脸皮长得稍厚一些,所以,就让老周写几篇烂博客,看看能不能给大家解惑,传道授业愧不敢当,解几个简单的惑还是可以的。

 

其实,Windows服务也是一个Windows标准程序,也是编译为.exe的,运行时在任务管理器中也是有进程的,但它与一般的可执行文件也有不同,至少你不能直接双击运行服务。服务隶属于系统对象,因此在你运行服务前,需要向系统注册它。就好比你坐公交要先买票一样(包括刷卡,无人售票等),当然,个别另类人种是不买票的,后门上再从后门下。

 

一个服务项目可以包含多个服务,就是说你建立一个应用程序项目,你可以在里面根据你的需要,声明多个服务。实现服务的方法是从ServiceBase派生出一个自定义的类,然后重写相关的虚方法,来定义你的代码,当然,不是全部虚方法都要重写,你只写你觉得需要的方法。不要问我ServiceBase类在哪个命名空间,自己打开“对象浏览器”去找,“对象浏览器”是个好用的东西,老周每天都会看它一眼,特别好看,比你到网上下载的那些文笔拙劣的垃圾小说好看,不信你去看看。

 

举个地球人都知道的例子,假设你需要在服务启动时打开某个端口,去监听客户端的连接请求,那么,你可以重写OnStart方法;当服务被停止时关闭监听,重写OnStop方法;当服务被暂停时向所有客户端发送一条服务器放假的消息,请重写OnPause方法;如果你希望在关机的时候做一些处理,比如清空犯罪痕迹,可以重写OnShutdown方法,当系统即将关机,并且你的服务还在运行的时候,这个方法就会被调用。

 

扯的太空洞了,是吧,那就来点年货,反正也快过年了。咱们就从头到尾做一遍,看看你能不能掌握。

要上厕所的赶紧上,下面开始干活。

 

1、在VS中新项一个空项目,我知道VS中就有Windows服务的项目模板,但,为了让大伙伴能够更好地理解,咱们从空白开始。

2、因为项目是空的,所以里面连毛都没有,为了让项目看起来比较像样,先来设置一下项目的基本属性,估计这个很少人会注意,因为平时我们建项目时,连属性文件都由VS帮我们建好了。在项目节点上右击,选择属性。然后在应用程序页上,填好需要的东西,你可以随便填,不用客气。

 

注意,在输出类型那里选择“Windows应用程序”,不要选控制台应用程序,因为服务是没有界面的,就是一个进程。而且,在Win 7以上的系统中(其实是Vista时代就是了),是禁止服务和桌面交互的。我也赞成这样做,老周特别讨厌那些服务动不动就弹出个窗口,相当恶心。服务进程就是做逻辑处理的,BS有UI的服务。

 

3、点击“程序集信息”按钮,填上相关信息,自己喜欢填什么就填什么。

 

填完后确定,就会自动生成AssemblyInfo.cs文件。然后保存并关闭项目属性窗口。

 

4、添加下面三个引用,不用我教了。先加这么几个,等不够用时再加。

 

5、好,现在可以开始了,我们来做第一个服务,服务名称叫HuiJi,显示名为“灰机”.

    public class HuiJiService : ServiceBase
    {
        public HuiJiService()
        {
            ServiceName = "HuiJi";
        }

        protected override void OnStart(string[] args)
        {
            Trace.WriteLine($"{this.ServiceName} - 正在起飞。");
        }

        protected override void OnStop()
        {
            Trace.WriteLine($"{this.ServiceName} - 正在降落。");
        }
    }

 

由于ServiceName属性是ServiceBase类的公共属性,所以,你可以像我这样,在类的构造函数中设置,也可以不设置,等到在Main入口点中实例化HuiJiService时再赋值,公共属性在类外部可以访问。

 

6、好,这么个简单的服务做好了,然后我们要在Main入口点中运行它。

        static void Main()
        {
            SV.HuiJiService hj = new SV.HuiJiService();
            // 调用静态方法运行指定服务
            ServiceBase.Run(hj);
        }

 

一定要记住,在Main中要调用Run静态方法来运行你想要运行的服务,这个很重要,不要忘了。如果只有一个服务,就只传一个服务实例给它,如果你要运行N个服务,就传一个服务数组进去。

 

那么,是不是这样就能运行了呢。非也,虽然我们写好了服务,但操作系统不知道你这是啥玩意儿,你得写安装程序类,这样在向系统注册时,安装工具才会去查找安装器,对服务进行安装。

 

7、服务需要两个(至少)安装器,一个是安装服务本身,另一个用来安装服务进程。要注意的是,服务和服务进程是不同的,服务就是我们刚刚写的从ServiceBase类派生的那个,而服务进程就是我们这个应用的.exe文件运行时的进程。

哪怕你只有一个服务,至少也有两个安装器——服务和服务进程。如果有三个服务要安装,就要定义四个安装器(三个服务安装,一个进程安装),每个服务安装器只能对应一个服务。

正因为要>=2安装器,所以要从Installer类派生出来一个类,在里面实例化好各种安装器,然后统一加入到Installers集合中,这样,在安装服务时,安装工具就会自动扫描这个集合里面的安装器,逐个进行安装。

    public class MyInstaller : Installer
    {
        ServiceInstaller svinstaller = null; //服务安装器
        ServiceProcessInstaller processinstaller = null; //进程安装器
        public MyInstaller()
        {
            // 实例化安装器
            svinstaller = new ServiceInstaller();
            // 设置的服务名称必须和自定义服务的名字相同
            svinstaller.ServiceName = "HuiJi";
            // 服务描述
            svinstaller.Description = "23世纪触发式新型飞机。";
            // 设置显示名称
            svinstaller.DisplayName = "飞机";
            // 启动方式为自动
            svinstaller.StartType = ServiceStartMode.Automatic;
            // 实例化进程安装器
            processinstaller = new ServiceProcessInstaller();
            // 设置帐户类型为本地系统,通常用这个
            // 如果是远程服务,应指定登录的用户名和密码
            processinstaller.Account = ServiceAccount.LocalSystem;

            // 把安装器添加到集合中
            this.Installers.Add(svinstaller);
            this.Installers.Add(processinstaller);
        }
    }

安装服务用ServiceInstaller,而且,必须必须注意,安装器上ServiceName指定的服务名字,必须要和刚才在Main中运行的服务的名字相同,不然,无法正确安装,也无法启动服务。

刚才我们定义服务的名字叫 HuiJi,所以这里也要指明为 HuiJi。

服务进程要用 ServiceProcessInstaller 来安装,实例化后,要指定它的帐户类型,对于在本机运行的服务,用LocalSystem就可以了(本地系统帐户)。一个应用程序用一个ServiceProcessInstaller就可以了,你用多个也没用,不信你可以试试在 Installers 集合中添加N个 ServiceProcessInstaller 实例,最后你发现,只有第一个进去的才会有效,其他的靠边站。而且,一个应用程序安装多个服务进程也没什么用。

最后,不要忘了把两个安装器添加进 Installers 集合中。

 

8、最关键一步,在刚铡定义的自定义安装类上应用RunInstaller特性,并且把参数设为true,这样做表示这个类是用于给安装工具发现,并且扫描安装的,这一步不能少,少了就无法安装。

    [RunInstaller(true)]
    public class MyInstaller : Installer
    {

 

 

 

好了,包含一个服务的应用程序做好了,下面看看如何注册和运行它了。

首先,生成应用项目,不要直接运行,因为它不是普通的.exe。

然后,要知道,安装服务用到一个叫 InstallUtil.exe 的工具,这个工具在 C: \ Windows \ Microsoft.NET \ Framework 下面,自己去找。找到了随便你怎么弄,最把这个exe所在目录加入到系统变量path中,这样,你就很轻松地用了。

到项目生成目录 \ bin \ Debug 下面,确认已成功生成了.exe 文件,然后在文件管理器左上角的菜单中 文件 ,“打开命令提示符” -- 以管理员身份打开命令提示符。

 

然后输入installUtil xxxx.exe ,回车。如图。

 

看到提示安装成功,说明服务已经装上了。现在打开系统的本地服务管理器窗口,可以看到刚才的服务了。

 

现在,你可以启动和停止服务了。

 

我们刚刚是通过Trace来输出跟踪信息的,那如何调试呢。

首先,关掉VS,再以管理员身份运行VS,然后再打开你的项目。

接着,到服务管理器中,启动服务。

回到VS,执行菜单 调试 -- 附加到进程。选择我们服务应用程序的进程。

 

然后附加调试器,接着再把服务停止,再启动,你就会看到 输出 窗口中的跟踪信息了。

 

测试完后,可以把刚刚的服务卸载掉,你可以用 sc delete 服务名 命令来删掉。不过,最好还是用刚才的 installUtil 工具来卸载。很简单,只要加一个 /u 参数就可以了。比如:installUtil /u xxxx.exe。

 

然后,你再到服务管理器窗口刷新一下,刚刚的服务就不见了,被卸载了。

 

做完这个练习后,你应该知道 Windows 服务怎么耍了,至少基本的步骤你懂了。

 

 

示例源代码  :https://files.cnblogs.com/files/tcjiaan/sampleServiceapp.zip

 

版权声明:本文著作权归作者【虞磨 】所有,不代表本网站立场。

侵权请联系:root_email@163.com

相关推荐