最近要帮公司系统增加一个类似于QQ机器人的功能,通过聊天人员发送相关指令(特殊字符),自动到数据库中取相关信息返回聊天人员,由于功能单一而且要长期执行,所以我想起用Windows服务的方式处理,同时也可以学习一下相关知识.在开发中遇到的问题和处理方法,我都记录在其中,希望能帮助有相同困难要处理的朋友.
一.Windows服务中的时钟问题.
要在服务中实现定时询问,一般有两种做法,其一是用时钟定时执行,其二是用线程,如果用时钟来处理的话就要注意了,从工具箱中取出的控件默认都是继承于类System.Windows.Forms.Timer,但这种控件在服务中是不会被执行的,如果要在服务中用,一定要用继承于类System.Timers.Timer的控件才行,
如果你想保持用可视化的方式来开发,你可以打开服务对应的设计文件(举例,服务文件名叫AlarmService,那么就打开AlarmService.Desinger.cs),将里面的System.Windows.Forms.Timer改为System.Timers.Timer(注意,有两个地方要改),保存后,你双击服务中的Timer控件就会发现创建的事件已经不同了,但其它地方大致还是一样的.
你也可以用代码同态创建,然后定义一个方法,再将方法绑到对象的Elapsed事件中,代码如下
Timer Timer1=new Timer()
protected override void OnStart(string[] args)
{
Timer1.Elapsed +=this.timer1_Elapsed; //动态绑定事件
}
private void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
//中间省略
}
即可
二.创建好服务好启动服务时出错,提示#1083
由于我同一个项目中创建了两个服务,但C#很笨,我装服务改名时,他不会为新的服务重新创建一个新的对象,举例:我创建一个Windows服务项目时,系统会自动创建一个叫Service1的服务,如果我装其它文件重命名为AlarmService后,打开Program.cs发现里面创建的服务对象还是Service1,要手工进行修改,不然注册服务后运行就会报1083,还有一点,同一项目如果创建了两个或以上服务时,新创建的服务系统也不会为其创建对象,也是要手工在Program.cs文件中添加,否则启动服务时也会报1083错
语法如下:
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new AMIService(),new AlarmService() //假设两个服务的名分别为AMIService和AlarmService
};
ServiceBase.Run(ServicesToRun);
Windows Service开发日志二(安装与调试)
要运行这个service我们还要做下边的几个步骤:
1.为我们的Service添加Installer,右键点击设计视图,选择Add Installer,VS将会为我们添加ProjectInstaller.cs,并在ProjectInstaller中添加组件serviceInstaller1和serviceProcessInstaller1,现在我们来修改他们的属性来控制Service的安装和启动选项。在ProjectInstaller得设计视图中选中serviceProcessInstaller1,将它得Account属性选为LocalSystem,这样以这个帐号服务启动。如果你希望系统启动时自动启动服务得话,将serviceInstaller1的StartType的属性选为Automatic,如果手动启动的话,选为manaul。
2.安装service,我们要用到IntallUtil.exe这个程序,这个程序位于C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727。点击开始菜单,选择“运行”,在运行对话框中输入cmd,进入到命令行窗口,输入cd :\WINDOWS\Microsoft.NET\Framework\v2.0.50727,进入到这个目录,然后输入installutil F:\Programs\C#\TestService\TestService\bin\Debug\testserveice.exe, installutil后边的内容就是我们的工程生成的可执行程序的路径,情根据需要修改。
如果你给ServiceInstaller1的StartType设为Automatic的话,安装完服务,服务已经运行起来了,如果StartType是Manual的话,你需要手动启动。现在我们进入“服务”,要打开“服务”,请单击“开始”,指向“设置”,然后单击“控制面板”。依次单击“性能和维护”、“管理工具”,然后双击“服务”。在里边你应该能够看到我们制作的Service MyFirstService。在这里边,我们可以启动,关闭服务,还可以设置服务的启动类型。然后,我们看看服务有没有正确的写入日志,我们需要进入到事件查看器,要打开“事件查看器”,请单击“开始”,指向“设置”,然后单击“控制面板”。单击“性能和维护”,单击“管理工具”,然后双击“事件查看器”。如下图所示,我们的消息已经成功的写到了系统日志里了。
3.如果你不需要这个Service了,仍然使用InstallUtil这个程序来卸载,不过在InstallUtil后跟参数 –u,比如installutil –u F:\Programs\C#\TestService\TestService\bin\Debug\testserveice.exe。
Service的调试方法与普通的程序调试方法是不一样的。我来介绍一下。
1. Build你的项目
2. 设置断点,因为我们的Service非常的简单,没有什么执行逻辑,所以设置断点没有任何意义,大家可以自己写一些代码来实践。一般来说,我们服务里需要用到一个另外的线程来执行任务,你需要在线程的执行代码中来设置断点。
3. 安装service,我们前边有介绍如何安装。
4. 如果你的Service启动类型是手动(Manual),你需要到“服务”里启动你的Service。一般来说,如果你的service在开发阶段,我推荐你将Service的启动类型设置为Manual,这样便于调试,因为如果service在运行过程中,你将无法build工程。
5. 在VS中,从菜单中选择Debug->Attach Process….,将会出现下图:
里边列出了正在运行的进程,如果你找不到自己的service,请选中Show processes from all users。在Available processes列表中选中我们的service所在的进程TestService,然后点击Attach按钮,如果你设置的断点合理的话,那么,程序就会停在断点处,接下来你就可以进行调试了。、
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
using System.Text;
namespace TestService
{
public partial class MyFirstService : ServiceBase
{
public MyFirstService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
// TODO: Add code here to start your service.
eventLog1.WriteEntry("Service start");
}
protected override void OnStop()
{
// TODO: Add code here to perform any tear-down necessary to stop your service.
eventLog1.WriteEntry("Service stop");
}
}
}
windows Service开发日志三(制作安装包)
windows service没有办法双击就运行.它需要一个安装类来辅助.接下来我们要做的,就是给这个服务添加一个安装辅助类.
在project名上右键,添加新项目,选择installer class.vs会自动给我们创建一个安装类.
实际上,你也可以添加一个新类,然后让这个类继承自System.Configuration.Install.Installer.所以,实际上,用c#写一个安装类,实际上就是要写一个继
承自Installer的类.
说到这里打断一下,虽然你可以自己创建windows service类和install类,但是还是建议让vs来给你创建,因为这样除了有清晰的层次关系,还会得到很多自
动生成的代码段.比如说重写的Dispose方法.
安装windows service类,首先需要一个service安装进程,然后在进程中有service的安装,所以,我们需要在这个安装类中创建这两个类.
this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller();
this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller();
你可以这样想:ServiceInstaller负责安装windows service,而ServiceProcessInstaller是包裹在外面的一层.
this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
this.serviceProcessInstaller1.Password = null;
this.serviceProcessInstaller1.Username = null;
这个来设置安装时的权限,一般选择本地系统账户的,话,就不需要用户名和密码了
this.serviceInstaller1.ServiceName = "notus";
this.serviceInstaller1.Description = "a sample";
serviceInstaller1.StartType = ServiceStartMode.Automatic;
而ServiceInstaller设定的都是和服务本身相关的一些参数,比如启动方式,名字,描述等.
这里的ServiceName要和前面你写的windows service的名字相同.否则会出麻烦.
如果你想在安装的前后做点什么,那就需要进入到事件的操作.ServiceInstaller提供了安装时的一些事件供你使用,比如下面这个:
serviceInstaller1.BeforeUninstall += new System.Configuration.Install.InstallEventHandler(serviceInstaller1_BeforeUninstall);
我们可以给这个事件加个代码,就是确保你在删除服务的时候,该服务是停止的.(如果服务正在运行,而你要删除它,那就会出问题)
void serviceInstaller1_BeforeUninstall(object sender, System.Configuration.Install.InstallEventArgs e)
{
ServiceController con = new ServiceController(serviceInstaller1.ServiceName);
if (con.Status == ServiceControllerStatus.Running || con.Status == ServiceControllerStatus.StartPending)
{
con.Stop();
}
}
还有一点要注意的是,如果要使用那些环境变量,需要按照下面的方法取得:
this.serviceProcessInstaller1.Context.Parameters["SURL"];
这个安装类麻烦了些,因为出现了三个带install的类,最后应该类似于这个样子:
[RunInstaller(true)]
public partial class ProjectInstaller : Installer
{
this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller();
this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller();
//......
}
如此这般,完成你的安装类.
这样,工作就基本完成了.如果你是用vs自动添加的这两个类,可能会有些小迷惑,因为点击view code,和到里面在点击,会有很多个名字一样的类出现,然后
有的继承了基类,有的没有继承,有的又引用什么的...其实安静下来看,这几个类都是partial的,也就是局部类.不要被vs弄晕.
2.widnows service的安装
vs命令提示符
一种是在vs命令提示符下(注意不是cmd敲出来的那个,而是在开始菜单的vs安装目录下那个)用命令操作
使用这个安装 installutil myservice1.exe
这样删除 installutil /u myservice1.exe
当然,在运行前,你得先定位到myservice1.exe所在的文件夹.
windows安装项目
也可以使用vs提供的制作安装程序的功能,把你的project添加到主输出,就可以安装.
新建peject,在其他那一类中选择setup project,vs会给你创建一个安装项目.
在项目名上右键,add,peojet output(输出),把你的服务project添加进来.然后再在项目名上右键,view,custom action,你会看到有四个类别,分别
是install,commit,rollback,uninstall,在上面右键,add custom action,然后在application folder中找到你的服务project,添加进来.
如此这般(...),完成.
编译,运行,看看效果 :)如果不出意外,你的服务就可以在控制面板的"服务"窗口中找到.
Windows Service开发日志四(用程序设置服务的运行状态及启动方式)
开发了服务,总要开发一个设置界面来开启或停止服务.
服务的启动方式有两种:
1.手动运行:手动运行,则每次都要手动去开启服务,开启了服务后,只要重启计算机,服务又会被停止.
2.自动运行:当服务被设置以自动运行方式安装,安装后服务是不会自动启动的(无论选择自动或手动,服务刚安装完时,状态都是停止的),但重启后服务就会自动启运,就算停止了服务,只要启动状态是自动,那么重启计算机后,服务又会自动运行的.
由于开发的需要,我的设置程序要实现几项功能:
1.可以开启或停止服务.
2.如果选择开启,那么启动类型就要设为自动.
3.如果选择停止,那么启动类型就要设为手动,以免重启后又自动执行.
这也是我写这篇目章的目的.
1.其实要实现这几个功能一点都不难,难就难在相关的资料少.
首先讲讲设置服务运行状态:
用C#的ServiceProcess. ServiceController就可以实现,以下是实现代码
view plaincopy to clipboardprint?
using System.ServiceProcess;
ServiceController AlarmCon=new ServiceController("AlarmService");
//获取要控制的服务对象
if (AlarmCon.Status==ServiceControllerStatus.Running)
{
butAlStart.Enabled = false;
butAlStop.Enabled = true;
}
else
{
butAlStart.Enabled = true;
butAlStop.Enabled = false;
}
using System.ServiceProcess;
ServiceController AlarmCon=new ServiceController("AlarmService");
//获取要控制的服务对象
if (AlarmCon.Status==ServiceControllerStatus.Running)
{
butAlStart.Enabled = false;
butAlStop.Enabled = true;
}
else
{
butAlStart.Enabled = true;
butAlStop.Enabled = false;
}
如果要设置运行状态只要如下即可
view plaincopy to clipboardprint?
AlarmCon.Start();
AlarmCon.WaitForStatus(ServiceControllerStatus.Running);
//等到服务的状态起效才往下执行
GetAlarmServiceState();
AlarmCon.Start();
AlarmCon.WaitForStatus(ServiceControllerStatus.Running);
//等到服务的状态起效才往下执行
GetAlarmServiceState();
2.如何改变服务的启动方式呢,原来是要通过修改注册表来实现
view plaincopy to clipboardprint?
using Microsoft.Win32;
string keyPath = @"SYSTEM\CurrentControlSet\Services\AlarmService";
RegistryKey key = Registry.LocalMachine.OpenSubKey(keyPath, true);
key.SetValue("Start", 2);
//2:自动启动,3:手动启动,4:禁用