用
.NET
创建
Windows
服务
译者说明:我是通过翻译来学习C#的,文中涉及到的有Visual Studio.NET有关操作,我都根据中文版的VS.NET显示信息来处理的,可以让大家不致有误解。
作者: Mark Strawmyer
我们将研究如何创建一个作为 Windows 服务的应用程序。内容包含什么是 Windows 服务,如何创建、安装和调试它们。会用到 System.ServiceProcess.ServiceBase 命名空间的类。
作者: Mark Strawmyer
我们将研究如何创建一个作为 Windows 服务的应用程序。内容包含什么是 Windows 服务,如何创建、安装和调试它们。会用到 System.ServiceProcess.ServiceBase 命名空间的类。
什么是 Windows 服务?
Windows 服务应用程序是一种需要长期运行的应用程序,它对于服务器环境特别适合。它没有用户界面,并且也不会产生任何可视输出。任何用户消息都会被写进 Windows 事件日志。计算机启动时,服务会自动开始运行。它们不要用户一定登录才运行,它们能在包括这个系统内的任何用户环境下运行。通过服务控制管理器, Windows 服务是可控的,可以终止、暂停及当需要时启动。
Windows
服务,以前的
NT
服务,都是被作为
Windows NT
操作系统的一部分引进来的。它们在
Windows 9x
及
Windows Me
下没有。你需要使用
NT
级别的操作系统来运行
Windows
服务,诸如:
Windows NT
、
Windows 2000 Professional
或
Windows 2000 Server
。举例而言,以
Windows
服务形式的产品有:
Microsoft Exchange
、
SQL Server
,还有别的如设置计算机时钟的
Windows Time
服务。
创建一个 Windows 服务
我们即将创建的这个服务除了演示什么也不做。服务被启动时会把一个条目信息登记到一个数据库当中来指明这个服务已经启动了。在服务运行期间,它会在指定的时间间隔内定期创建一个数据库项目记录。服务停止时会创建最后一条数据库记录。这个服务会自动向
Windows
应用程序日志当中登记下它成功启动或停止时的记录。
Visual Studio .NET
能够使创建一个
Windows
服务变成相当简单的一件事情。启动我们的演示服务程序的说明概述如下。
1.
新建一个项目
2. 从一个可用的项目模板列表当中选择 Windows 服务
3. 设计器会以设计模式打开
4. 从工具箱的组件表当中拖动一个 Timer 对象到这个设计表面上 ( 注意 : 要确保是从组件列表而不是从 Windows 窗体列表当中使用 Timer)
5. 设置 Timer 属性, Enabled 属性为 False , Interval 属性 30000 毫秒
6. 切换到代码视图页 ( 按 F7 或在视图菜单当中选择代码 ) ,然后为这个服务填加功能
2. 从一个可用的项目模板列表当中选择 Windows 服务
3. 设计器会以设计模式打开
4. 从工具箱的组件表当中拖动一个 Timer 对象到这个设计表面上 ( 注意 : 要确保是从组件列表而不是从 Windows 窗体列表当中使用 Timer)
5. 设置 Timer 属性, Enabled 属性为 False , Interval 属性 30000 毫秒
6. 切换到代码视图页 ( 按 F7 或在视图菜单当中选择代码 ) ,然后为这个服务填加功能
Windows 服务的构成
在你类后面所包含的代码里,你会注意到你所创建的
Windows
服务扩充了
System.ServiceProcess.Service
类。所有以
.NET
方式建立的
Windows
服务必须扩充这个类。它会要求你的服务重载下面的方法,
Visual Studio
默认时包括了这些方法。
• Dispose –
清除任何受控和不受控资源
(managed and unmanaged resources)
• OnStart – 控制服务启动
• OnStop – 控制服务停止
• OnStart – 控制服务启动
• OnStop – 控制服务停止
数据库表脚本样例
在这个例子中使用的数据库表是使用下面的
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]
[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;
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;
/// <remarks>
/// Required designer variable.
/// </remarks>
private System.ComponentModel.Container components = null;
{
public class MyService : System.ServiceProcess.ServiceBase
{
private System.Timers.Timer timer1;
/// <remarks>
/// Required designer variable.
/// </remarks>
private System.ComponentModel.Container components = null;
public MyService()
{
// This call is required by the Windows.Forms
// Component Designer.
InitializeComponent();
}
{
// 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() };
static void Main()
{
System.ServiceProcess.ServiceBase[] ServicesToRun;
ServicesToRun = new System.ServiceProcess.ServiceBase[]
{ new MyService() };
System.ServiceProcess.ServiceBase.Run(ServicesToRun);
}
}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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();
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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();
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
/// <summary>
/// Set things in motion so your service can do its work.
/// </summary>
protected override void OnStart(string[] args)
{
this.timer1.Enabled = true;
this.LogMessage("Service Started");
}
/// <summary>
/// Stop this service.
/// </summary>
protected override void OnStop()
{
this.timer1.Enabled = false;
this.LogMessage("Service Stopped");
}
/// Set things in motion so your service can do its work.
/// </summary>
protected override void OnStart(string[] args)
{
this.timer1.Enabled = true;
this.LogMessage("Service Started");
}
/// <summary>
/// Stop this service.
/// </summary>
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");
}
* 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();
}
}
}
}
* 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
服务了。安装一个
Windows
服务应该通过使用
.NET Framework
提供的
InstallUtil.exe
来完成,或者通过诸如一个
Microsoft Installer (MSI)
这样的文件部署项目完成。
添加服务安装程序
创建一个
Windows
服务,仅用
InstallUtil
程序去安装这个服务是不够的。你必须还要把一个服务安装程序添加到你的
Windows
服务当中,这样便于
InstallUtil
或是任何别的安装程序知道应用你服务的是怎样的配置设置。
1.
将这个服务程序切换到设计视图
2. 右击设计视图选择 “ 添加安装程序 ”
3. 切换到刚被添加的 ProjectInstaller 的设计视图
4. 设置 serviceInstaller1 组件的属性:
1) ServiceName = My Sample Service
2) StartType = Automatic
5. 设置 serviceProcessInstaller1 组件的属性
1) Account = LocalSystem
6. 生成解决方案
2. 右击设计视图选择 “ 添加安装程序 ”
3. 切换到刚被添加的 ProjectInstaller 的设计视图
4. 设置 serviceInstaller1 组件的属性:
1) ServiceName = My Sample Service
2) StartType = Automatic
5. 设置 serviceProcessInstaller1 组件的属性
1) Account = LocalSystem
6. 生成解决方案
在完成上面的几个步骤之后,会自动由
Visual Studio
产生下面的源代码,它包含于
ProjectInstaller.cs
这个源文件内。
using System;
using System.Collections;
using System.ComponentModel;
using System.Configuration.Install;
using System.Collections;
using System.ComponentModel;
using System.Configuration.Install;
namespace CodeGuru.MyWindowsService
{
/// <summary>
/// Summary description for ProjectInstaller.
/// </summary>
[RunInstaller(true)]
public class ProjectInstaller :
System.Configuration.Install.Installer
{
private System.ServiceProcess.ServiceProcessInstaller
serviceProcessInstaller1;
private System.ServiceProcess.ServiceInstaller serviceInstaller1;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
{
/// <summary>
/// Summary description for ProjectInstaller.
/// </summary>
[RunInstaller(true)]
public class ProjectInstaller :
System.Configuration.Install.Installer
{
private System.ServiceProcess.ServiceProcessInstaller
serviceProcessInstaller1;
private System.ServiceProcess.ServiceInstaller serviceInstaller1;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public ProjectInstaller()
{
// This call is required by the Designer.
InitializeComponent();
{
// This call is required by the Designer.
InitializeComponent();
// TODO: Add any initialization after the InitComponent call
}
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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
}
}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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 .NET
命令提示
2. 改变路径到你项目所在的 bin/Debug 文件夹位置 ( 如果你以 Release 模式编译则在 bin/Release 文件夹 )
3. 执行命令 “InstallUtil.exe MyWindowsService.exe” 注册这个服务,使它建立一个合适的注册项。
4. 右击桌面上 “ 我的电脑 ” ,选择 “ 管理 ” 就可以打计算机管理控制台
5. 在 “ 服务和应用程序 ” 里面的 “ 服务 ” 部分里,你可以发现你的 Windows 服务已经包含在服务列表当中了
6. 右击你的服务选择启动就可以启动你的服务了
2. 改变路径到你项目所在的 bin/Debug 文件夹位置 ( 如果你以 Release 模式编译则在 bin/Release 文件夹 )
3. 执行命令 “InstallUtil.exe MyWindowsService.exe” 注册这个服务,使它建立一个合适的注册项。
4. 右击桌面上 “ 我的电脑 ” ,选择 “ 管理 ” 就可以打计算机管理控制台
5. 在 “ 服务和应用程序 ” 里面的 “ 服务 ” 部分里,你可以发现你的 Windows 服务已经包含在服务列表当中了
6. 右击你的服务选择启动就可以启动你的服务了
在每次需要修改
Windows
服务时,这就会要求你卸载和重新安装这个服务。不过要注意在卸载这个服务前,最好确保服务管理控制台已经关闭,这会是一个很好的习惯。如果没有这样操作的话,你可能在卸载和重安装
Windows
服务时会遇到麻烦。仅卸载服务的话,可以执行相的
InstallUtil
命令用于注销服务,不过要在后面加一个
/u
命令开关。
调试 Windows 服务
从另外的角度度看,调试
Windows
服务绝不同于一个普通的应用程序。调试
Windows
服务要求的步骤更多。服务不能象你对普通应用程序做的那样,只要简单地在开发环境下执行就可以调试了。服务必须首先被安装和启动,这一点在前面部分我们已经做到了。为了便于跟踪调试代码,一旦服务被启动,你就要用
Visual Studio
把运行的进程附加进来
(attach)
。记住,对你的
Windows
服务做的任何修改都要对这个服务进行卸载和重安装。
附加正在运行的 Windows 服务
为了调试程序,有些附加
Windows
服务的操作说明。这些操作假定你已经安装了这个
Windows
服务并且它正在运行。
1.
用
Visual Studio
装载这个项目
2. 点击 “ 调试 ” 菜单
3. 点击 “ 进程 ” 菜单
4. 确保 显示系统进程 被选
5. 在 可用进程 列表中,把进程定位于你的可执行文件名称上点击选中它
6. 点击 附加 按钮
7. 点击 确定
8. 点击 关闭
9. 在 timer1_Elapsed 方法里设置一个断点,然后等它执行
2. 点击 “ 调试 ” 菜单
3. 点击 “ 进程 ” 菜单
4. 确保 显示系统进程 被选
5. 在 可用进程 列表中,把进程定位于你的可执行文件名称上点击选中它
6. 点击 附加 按钮
7. 点击 确定
8. 点击 关闭
9. 在 timer1_Elapsed 方法里设置一个断点,然后等它执行
总结
现在你应该对
Windows
服务是什么,以及如何创建、安装和调试它们有一个粗略的认识了。
Windows
服务的额处的功能你可以自行研究。这些功能包括暂停
(OnPause)
和恢复
(OnContinue)
的能力。暂停和恢复的能力在默认情况下没有被启用,要通过
Windows
服务属性来设置。
About the Author
Mark Strawmyer, MCSD, MCSE (NT4/W2K), MCDBA is a Senior Architect of .NET applications for large and mid-size organizations. Mark is a technology leader with Crowe Chizek in Indianapolis, Indiana. He specializes in architecture, design and development of Microsoft-based solutions. You can reach Mark at mstrawmyer@crowechizek.com.
作者
Blog
:
http://blog.youkuaiyun.com/ego/