翻译
Mark Strawmyer 著 Creating a Windows Service in .NET (developer.com)
flanker 翻译 MSProject
简介
在本文中我们将探讨如何创建一个Windows服务的应用程序。我将说明什么是Windows服务,以及如何创建、安装和调试它。这需要使用System.ServiceProcess.ServiceBase命名空间中的类。
什么是Windows服务?
Windows服务应用程序是在服务器环境中长期运行的应用程序,它没有界面或者可视化的输出,信息一般都写入事件日志中。服务可以在操作系统启动时自动的开始运行,而不需要用户登录来运行它,并且服务可以运行在任何用户的账户中包括system账户。Windows服务通过Service Control Manager(SCM服务控制程序)来管理,控制服务的停止、暂停和重启等。
Windows服务,之前称为NT服务,是在Windows NT操作系统引入的,所以它不适用于Windows 9X或Windows ME系统。要运行Windows服务,你需要NT系列的操作系统,比如Windows NT、Windows 2000等。举几个例子,Microsoft Exchange、SQL Server就是服务器程序的Windows服务,而设置计算机时间的Windows Time是应用程序的Windows服务。
创建一个Windows服务
下面我们创建一个示例,它并没有什么实际用途。当服务启动后,它将在数据库中记录一个表示启动的条目。在它运行期间,每隔特定时长就在数据库中记录一个条目。当它停止时,它会记录一个终止的条目。这个服务也会在启动或者停止成功时,记录在Windows事件日志中。
在Visual Studio .NET中建立Windows服务相对的很简单。下面列出了创建这个示例的步骤:
1. 新建一个项目
2. 在模板中选择Windows服务
3. 设计器将会打开设计模式
4. 从工具箱的组件项里拖拉一个Timer对象到设计器上(注意:使用组件项里的Timer,而不是Windows窗体里的)
(flanker注:这里是指需要System.Timers.Timer,而不能使用System.Windows.Forms.Timer。在我的VS2005中,System.Timers.Timer并没有默认出现在工具箱里,需要自己手工添加。)
5. 在Timer属性里将Enabled设置为false,将Interval设置为30000毫秒
6. 切换到代码模式(F7),给服务增加功能
Windows服务的组成
通过代码你会发现Windows服务是继承于System.ServiceProcess.Service类,所有.NET建立的Windows服务都必须继承自这个类。你需要重写以下几个Visual Studio默认提供的方法:
OnStart - 控制服务启动
OnStop - 控制服务终止
Dispose - 清理所有托管和非托管的资源
示例数据库的脚本
下面的T-SQL脚本用来在示例中创建数据库的表。我使用的是SQL Server,你也可以使用Access或者别的选择。
CREATE TABLE [dbo].[MyServiceLog] ( [in_LogId] [int] IDENTITY (1, 1) NOT NULL, [vc_Status] [nvarchar] (40) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, [dt_Created] [datetime] NOT NULL ) ON [PRIMARY]
示例windows服务
下面是名为MyService的Windows服务示例源代码,代码的大部分都是Visual Studio自动生成的。
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.ServiceProcess;
namespace CodeGuru.MyWindowsService
{
public class MyService : System.ServiceProcess.ServiceBase
{
private System.Timers.Timer timer1;
///
/// Required designer variable.
///
private System.ComponentModel.Container components = null;
public MyService()
{
// This call is required by the Windows.Forms
// Component Designer.
InitializeComponent();
}
// The main entry point for the process
static void Main()
{
System.ServiceProcess.ServiceBase[] ServicesToRun;
ServicesToRun = new System.ServiceProcess.ServiceBase[]
{ new MyService() };
System.ServiceProcess.ServiceBase.Run(ServicesToRun);
}
///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
private void InitializeComponent()
{
this.timer1 = new System.Timers.Timer();
((System.ComponentModel.ISupportInitialize)
(this.timer1)).BeginInit();
//
// timer1
//
this.timer1.Interval = 30000;
this.timer1.Elapsed +=
new System.Timers.ElapsedEventHandler(this.timer1_Elapsed);
//
// MyService
//
this.ServiceName = "My Sample Service";
((System.ComponentModel.ISupportInitialize)
(this.timer1)).EndInit();
}
///
/// Clean up any resources being used.
///
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
///
/// Set things in motion so your service can do its work.
///
protected override void OnStart(string[] args)
{
this.timer1.Enabled = true;
this.LogMessage("Service Started");
}
///
/// Stop this service.
///
protected override void OnStop()
{
this.timer1.Enabled = false;
this.LogMessage("Service Stopped");
}
/*
* Respond to the Elapsed event of the timer control
*/
private void timer1_Elapsed(object sender,
System.Timers.ElapsedEventArgs e)
{
this.LogMessage("Service Running");
}
/*
* Log specified message to database
*/
private void LogMessage(string Message)
{
SqlConnection connection = null;
SqlCommand command = null;
try
{
connection = new SqlConnection(
"Server=localhost;Database=SampleDatabase;Integrated
Security=false;User Id=sa;Password=;");
command = new SqlCommand(
"INSERT INTO MyServiceLog (vc_Status, dt_Created)
VALUES ('" + Message + "',getdate())", connection);
connection.Open();
int numrows = command.ExecuteNonQuery();
}
catch( Exception ex )
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
finally
{
command.Dispose();
connection.Dispose();
}
}
}
}
安装Windows服务
Windows服务有别于其他普通Windows应用程序,不能简单的运行一个EXE文件来启动它。Windows服务应该使用.NET Framework提供的InstallUtil.exe程序来安装,或者通过一个部署项目,比如Microsoft Installer(MSI)文件。
添加安装程序
仅仅创建了Windows服务还不能用InstallUtil程序来安装它。你必须给你的Windows服务添加一个安装程序,这样InstallUtil或者其他的工具才能知道服务的具体配置。
1. 切换到服务的设计模式
2. 右键单击,选择添加安装程序
3. 切换到新增的ProjectInstaller的设计模式
4. 设置serviceInstaller1组件的属性:
ServiceName = My Sample Service
StartType = Automatic
5. 设置serviceProcessInstaller1组件的属性:
Account = LocalSystem
6. 生成解决方案
以上几步完成后,Visual Studio会在ProjectInstalle.cs源文件中自动生成如下的代码。
using System;
using System.Collections;
using System.ComponentModel;
using System.Configuration.Install;
namespace CodeGuru.MyWindowsService
{
///
/// Summary description for ProjectInstaller.
///
[RunInstaller(true)]
public class ProjectInstaller :
System.Configuration.Install.Installer
{
private System.ServiceProcess.ServiceProcessInstaller
serviceProcessInstaller1;
private System.ServiceProcess.ServiceInstaller serviceInstaller1;
///
/// Required designer variable.
///
private System.ComponentModel.Container components = null;
public ProjectInstaller()
{
// This call is required by the Designer.
InitializeComponent();
// TODO: Add any initialization after the InitComponent call
}
#region Component Designer generated code
///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
private void InitializeComponent()
{
this.serviceProcessInstaller1 = new
System.ServiceProcess.ServiceProcessInstaller();
this.serviceInstaller1 = new
System.ServiceProcess.ServiceInstaller();
//
// serviceProcessInstaller1
//
this.serviceProcessInstaller1.Account =
System.ServiceProcess.ServiceAccount.LocalSystem;
this.serviceProcessInstaller1.Password = null;
this.serviceProcessInstaller1.Username = null;
//
// serviceInstaller1
//
this.serviceInstaller1.ServiceName = "My Sample Service";
this.serviceInstaller1.StartType =
System.ServiceProcess.ServiceStartMode.Automatic;
//
// ProjectInstaller
//
this.Installers.AddRange(new
System.Configuration.Install.Installer[]
{this.serviceProcessInstaller1, this.serviceInstaller1});
}
#endregion
}
}
使用InstallUtil安装Windows服务
现在你需要安装你的服务了。按照如下步骤提示,安装你的服务。
1. 打开Visual Studio命令提示
2. 切换到你的项目路径中的bin/Debug目录(如果你用Release模式编译,则进入bin/Release目录)
3. 输入 InstallUtil MyService.exe 来注册你的服务,它将添加所需的注册表信息。
4. 在桌面我的电脑右键选择管理,打开计算机管理
5. 在服务与应用程序中选择服务,你就可以在列表中看到你的服务
6. 右键点击你的服务,选择启动
任何时候你如果需要改变你的Windows服务,你都需要卸载并重新安装它。在卸载服务前,你最后先关闭了服务管理控制台,否则可能在卸载中遇到问题。要卸载服务,只需输入和注册时一样的命令,只不过在后面加上一个/u开关。
调试Windows服务
调试Windows服务比普通应用程序要困难些,需要很多步骤。服务不能像普通应用程序那样在开发环境中直接执行来调试,它必须先安装并启动。一旦启动了它,你可以用Visual Studio附加到进程来调试代码。记住,对Windows服务做的任何改动,你都需要卸载并重新安装它。
附加到运行的Windows服务
要调试服务,你需要按如下步骤附加到Windows服务。这里假设你已经安装了服务并成功启动了它。
1. 在Visual Studio中打开项目
2. 选择调试
3. 选择附加到进程
4. 打开选择所有用户的进程
5. 在可用进程中选择你的进程
6. 点击附加
7. 在timer1_Elapsed方法里设置一个断点,等待它的执行
总结
现在你已经大概了解了什么是Windows服务,以及如何创建、安装和调试它。Windows服务还有许多功能需要你去学习,比如暂停(OnPause)和恢复(OnContinue)等。这几个属性在默认中没有给出,但你可以在属性面板里面设置。
关于作者
Mark Strawmyer, MCSD, MCSE (NT4/W2K), MCDBA 是.NET应用程序高级架构师,服务于大中型组织。Mark是印第安纳波利斯Crowe Chizek公司的技术领导,他关注于架构、设计和开发基于微软技术的解决方案。
关于附件代码
flanker注:原文并没有附带源代码,因为代码比较简单。这是我自己按照文章做的项目文件。有一点不同:原文输出到数据库,比较麻烦,我这里改用了一个Log类,输出到了文本文件。大家可以看看。