转帖|其它|编辑:郝浩|2009-04-07 09:35:35.000|阅读 2951 次
概述:本课程说明了Windows Service程序的概念,并演示如何使用C#开发一个简单的Windows Service程序。
#慧都22周年庆大促·界面/图表报表/文档/IDE/IOT/测试等千款热门软控件火热促销中>>
本课程说明了Windows Service程序的概念,并演示如何使用C#开发一个简单的Windows Service程序。C#工程名为MyWindowsService,编译生成的文件是MyWindowsService.exe。本课程的演示代码下载地址为。
Windows Service,也称Windows服务,是32位Windows操作系统中一种长期运行的后台程序。它们长期后台运行,没有用户界面,默默无闻,但它们却是支持Windows正常运行的幕后英雄,却永无出头之日。
Windows服务程序为其他系统模块提供了非常重要的服务,而且各个Windows服务分工明确,比如IISAdmin服务提供WEB内容的发布功能,若IISAdmin服务不启动,则静态HTML页面、ASP、ASP.NET或者WebService等等统统不行;有个名为“Print Spooler”的服务用于提供打印支持,若该服务不启动,则任何软件都不能进行打印,比如Word,记事本或者报表软件等等。
Windows启动后在没有用户登录时就会启动Windows服务。Windows NT和Windows2000,以及更新的版本操作系统能运行Windows服务,但Windows98及其前期版本是不能运行服务的。
我们打开Windows资源管理器,在左边的树状列表中选中“桌面-控制面板-管理工具”。

在右边的列表中打开“服务”项目即可打开Windows服务管理器。
在这些服务中,有我们最熟悉的IIS Admin和World Wide Web Publishing服务了。我们双击一个服务项目即可打开服务属性对话框。

Windows服务有一个服务名称属性,该属性是服务的惟一的不可重复的名称,我们可以在命令行中使用命令“net start 服务名称”来启动服务,使用“net stop 服务名称”来停止服务。
Windows服务的启动类型有自动,手动和已禁用。当启动类型为自动时,Windows启动后不等用户登录就自动启动服务,当启动类型为手动时,需要某个操作员登录后点击这里的“启动”按钮来启动服务,而当启动类型为已禁用时,Windows服务不能启动。
该页面中的“启动”按钮用于启动尚未启动的Windows服务,运行提供服务的进程;“停止”按钮用于停止已经启动的服务,杀死服务进程;而“暂停”按钮用于通知服务进程暂时停止提供服务,但服务进程依然存在;而“恢复”按钮用于通知处于暂停模式的服务进程重新提供服务。
我们可以查看服务属性对话框的“登录”页面。

可以指定服务使用本地系统帐户登录,也可另外指定其他的用户,这里有一个允许服务和桌面交互的选项,若选中此选项,则Windows服务可以显示图形化用户界面,比如显示自己的窗体,显示消息框等等。不过不建议使用该选项,而且Windows服务运行时不要显示图形化用户界面。
我们切换到“依存关系”页面,可以看到本服务和其他服务的依存关系。

各个Windows服务之间可能存在依赖关系,比如IISADMIN服务就依赖另外一个名为RPC的Windows服务,当启动一个Windows服务时,系统会启动该服务所依赖的其他Windows服务。例如我们设置IISADMIN服务为自动启动,而RPC服务为手动启动,则Windows启动后会试图自动启动IISADMIN服务,结果会首先启动RPC服务,即使RPC服务不是自动启动。若RPC服务为禁止,无论如何也不能启动,则IISADMIN服务就无法自动启动了。
编写Windows服务是一种比较高级的编程技术,内部使用了很多Windows操作系统的核心功能,但微软.NET框架已经很好的封装了这些技术细节,使得我们可以很方便的使用C#编写自己的Windows服务,其基本过程一般为
1. 创建C#工程。创建一个EXE工程,可以是WinForm或者命令行格式的。添加对System.ServiceProcess.dll和System.Configuration.Install.dll的引用。
2. 创建服务类。新增一个类,该类型继承System.ServiceProcess.ServiceBase类型,在构造函数中设置ServiceName属性来指明该服务的名称。然后重载它的OnStart方法来响应启动服务的事件,重载OnStop方法来响应停止服务的事件,重载OnPause方法来响应暂停服务的事件,重载OnContinue方法来响应恢复提供服务的事件。在重载这些方法时必须要立即返回,其中不能出现长时间的操作,若处理时间过长则Windows服务管理器会觉得该Windows服务停止响应而报错。为此我们可以使用一个线程来进行实际的工作,而OnStart方法创建线程,OnStop方法关闭线程,OnPause方法挂起线程,而OnContinue方法来恢复运行线程。
3. 启动服务。在main函数中调用“System.ServiceProcess.ServiceBase.Run( 自定义服务类的实例 )”来运行服务。比如“System.ServiceProcess.ServiceBase.Run( new MyService() )”,这里的MyService就是继承自ServiceBase。
4. 安装服务。新增一个类,该类型继承自System.Configuration.Install.Installer类型,该类型用于配合微软.NET框架自带的安装命令行工具InstallUtil.exe的。我们为该类型附加System.ComponentModel.RunInstallerAttribute特性,并在它的构造函数中使用System.ServiceProcess.ServiceInstaller对象和System.ServiceProcess.ServiceProcessInstaller对象向系统提供该服务的安装信息。程序编译后我们可以使用命令行“InstallUtil.exe EXE文件名”向Windows服务管理器注册服务,可以使用命令行“InstallUtil.exe /u EXE文件名”从Windows服务管理器中注销服务。
5. 编写服务客户端。这是一个根据实际情况而可选的过程,由于Windows服务是没有用户界面的,因此我们可以编写一个具有用户界面的程序来显示和控制Windows服务提供的数据,并进行一些系统设置等操作。比如对于MS SQL Server,数据库引擎是以服务的形式存在,而SQL Server企业管理器就是一个客户端软件。
现在我们要求使用C#和VS.NET2005开发一个软件,该软件功能为
1. 该软件能监视指定目录下的文件和子目录的新增,修改,删除和重命名操作,并将操作日志记录到一个数据库中。
2. 该软件以Windows服务的形式运行,能监视不同的用户帐户的操作记录。
3. 有一个客户端软件能控制服务,并能查看服务的保存的监视记录。其用户界面为

客户端软件还能设置服务监视的目录,系统设置对话框为

一般而言,我们将服务和客户端分成两个C#工程开发,但这里为了方便我们只在一个工程中实现服务器和客户端软件的开发。Windows服务是不能显示图形化用户界面的,但并不是说Windows服务的软件中不能包含显示图形化用户界面的软件模块。我们完全可以编写一个EXE,其中包含服务器和客户端两个相互独立的软件模块。直接执行EXE将以服务模式运行,若带有命令行参数将以客户端模式运行。为此我们设计了如下的命令行参数
|
命令行参数 |
功能 |
|
无任何参数 |
以服务模式运行,调用ServiceBase.Run函数来运行服务。 |
|
/install |
调用InstallUtil.exe安装服务,将EXE自己注册到Windows服务管理器中。 |
|
/uninstall |
调用InstallUtil.exe卸载服务。 |
|
/client |
以客户端模式运行,显示图形用户界面。 |
|
/debug |
以调试模式下运行,方便VS.NET对服务的操作过程提供调试。 |
使用VS.NET调试服务是一个比较麻烦的事,首先我们得安装并运行服务,然后使用VS.NET的菜单项目“工具-附加到进程”的操作来附加到服务程序,然后设置断点进行调试,其中OnStart函数是没有办法设置断点调试的。为此我们专门添加一个“/debug”命令行参数使得程序不进入服务模式,而是直接运行提供服务内容的功能性代码,然后主线程休眠,但功能性代码还在运行,可以调试。这样我们在VS.NET中设置断点后可以直接运行进行调试了。
这里我们设计的C#工程名称为MyWindowsService,编译生成的文件为MyWindowsService.exe。
在本软件中,数据将保存到应用程序目录下的一个名为FileSystemWatcher.mdb的Access2000格式的数据库。数据库中的表结构为
文件系统操作日志表 FileSystemLog,字段有
|
字段名 |
类型 |
说明 |
|
RecordID |
文本(50) |
记录编号 |
|
WatchTime |
文本(20) |
记录时间,为yyyy-MM-dd HH:mm:ss格式 |
|
ObjectName |
文本(250) |
对象名称 |
|
EventStyle |
文本(50) |
事件类型 |
该数据表中保存的数据范例为
|
RecordID |
WatchTime |
ObjectName |
EventStyle |
|
0d4e0d9a-6826-415b-bd47-c86fbb1449b0 |
2008-10-02 15:31:27 |
c:"aaaaaa.txt |
Renamed |
|
22c1df6d-4f94-488c-a705-e8024d875213 |
2008-10-02 20:37:03 |
d:"aa.png |
Renamed |
|
27632fe8-6cbf-4a41-95ad-6ab2e8222192 |
2008-10-02 20:40:56 |
c:"a.pdf |
Created |
|
48403266-0150-44c8-8efa-169f7a68bcb4 |
2008-10-03 11:02:04 |
c:"zzzzzz.bmp |
Renamed |
|
6c3b603a-f43b-415c-8122-4aa23376d575 |
2008-10-02 11:26:57 |
c:"SDC_2008_10_2.log |
Changed |
|
6fb9fad1-51f5-40b2-b05b-d0628f775a3c |
2008-10-02 15:31:52 |
c:"aaaaaa.txt |
Deleted |
|
735d74e6-1548-4d7d-9048-ab75dd1c5874 |
2008-10-02 20:31:27 |
c:"aa.bmp |
Renamed |
|
7b36a079-c56c-48f7-9c6e-cf0d77b9c6c1 |
2008-10-02 11:27:12 |
c:"SDC_2008_10_2.log |
Changed |
|
7c2672ac-b210-4eca-9277-2505030e72e5 |
2008-10-02 20:39:12 |
d:"aa.png |
Deleted |
|
9ab95c19-ccd0-43eb-89ec-3930ebec9a8d |
2008-10-02 21:55:57 |
c:"b.bmp |
Renamed |
|
9adb5696-fb6a-497e-b4ff-06f5da896434 |
2008-10-02 20:39:12 |
d:"1.png |
Deleted |
|
9f4d702f-57c1-46ec-a827-701c2a15ee81 |
2008-10-02 23:59:04 |
c:"新建文件夹 |
Created |
|
c163fa48-f5ea-49b1-95c9-b89f9ee622e5 |
2008-10-02 11:26:42 |
c:"新建 文本文档.txt |
Created |
对于新增文件或目录操作其EventStyle值为Created,对于修改为Changed,对于删除为Deleted,对于重命名为Renamed。
系统设置信息表 SystemConfig,字段有
|
字段名 |
类型 |
说明 |
|
ConfigName |
文本(50) |
系统配置名称 |
|
ConfigValue |
文本(250) |
配置数据 |
该数据表中保存的数据的范例为
|
ConfigName |
ConfigValue |
|
LogChanged |
True |
|
LogCreated |
True |
|
LogDeleted |
True |
|
LogRenamed |
False |
|
path0 |
c:"*.txt |
|
path1 |
d:" |
在这里配置项LogChanged表示是否监视文件内容是否被改变事件,配置项LogCreated表示是否监视新建文件或目录事件,配置项LogDeleted表示是否监视文件或目录删除事件,配置项LogRenamed表示是否监视文件或目录重命名事件。而path0,path1,path2等表示监视的路径,支持通配符。系统配置中可以有若干个path配置项。
我们可以使用System.IO.FileSystemWatcher来监视文件系统的对象的修改,我们可以使用它的Path属性来设置要监视的文件夹,使用Filter属性来设置文件名过滤器,然后响应它的Changed事件来处理文件内容修改操作,响应Created事件来处理新增文件或目录操作,响应Deleted事件来处理删除文件或目录操作,响应Renamed事件来处理文件和目录重命名操作。这这里我们简单是将这些事件信息保存到数据表FileSystemLog中。程序在监视文件系统前会读取系统配置信息表SystemConfig中读取配置信息,根据其中的path配置项目创建若干个FileSystemWatcher对象展开监视。
我们选定服务的名称为“MyFileSystemWatcher”。
本软件的客户端具有一个图形化用户界面,其界面设计如下
|
客户端主窗体 |
|
工具条:刷新 删除记录 系统配置 启动服务 停止服务 |
|
数据列表 显示从 FileSystemLog表查询所得的数据 |
|
状态栏 |
此外还有一个系统配置对话框,用于查看和修改数据表SystemConfig中保存的系统配置信息。
根据上述的软件设计,我已经把软件编写完毕,现对该软件结构进行说明
本软件为一个C#编写的EXE,主要包含服务端软件模块和客户端软件模块。首先对比较好理解的具有图形化用户界面的客户端模块进行说明,客户端的主界面为

这个界面主要功能是数据库信息管理,最上面为一个工具条,中间大部分的一个ListView控件,最下面为状态栏。
对于ListView控件其内容是分组的,因此需要设计其分组信息,在VS.NET的窗体设计器中我们点中ListView控件,在旁边的属性列表中选择Groups属性,点击旁边的小按钮可以弹出分组设计器。

使用这个分组编辑器我们可以很容易的设计该ListView控件的分组信息。
这个窗体的加载事件处理为
|
/// <summary> /// 服务已经安装标记 /// </summary> private bool bolServiceInstalled = false ;
private void frmViewLog_Load(object sender, EventArgs e) { try { System.ServiceProcess.ServiceController control = new System.ServiceProcess.ServiceController("MyFileSystemWatcher"); System.ServiceProcess.ServiceControllerStatus status = control.Status; control.Dispose(); bolServiceInstalled = true; } catch( Exception ext ) { lblServiceInstall.Text = "服务尚未安装" ; myTimer.Enabled = false ; btnStartService.Enabled = false ; btnStopService.Enabled = false ; bolServiceInstalled = false ; MessageBox.Show( this,"服务尚未安装:" + ext.Message , "系统错误"); } this.btnRefresh_Click(null, null); } |
在这里我们首先创建一个联系到文件监视服务的ServiceController,调用它的Status属性,若一切正常则表示服务已经安装,我们设置bolServiceInstalled的标志变量,若发生错误则服务尚未安装,则显示“服务尚未安装”的提示信息。
对于工具条的“刷新列表”按钮,其点击事件处理为
|
private void btnRefresh_Click(object sender, EventArgs e) { this.Cursor = System.Windows.Forms.Cursors.WaitCursor; lvwRecord.BeginUpdate(); try { lvwRecord.Items.Clear(); using (System.Data.IDbCommand cmd = Util.DBConnection.CreateCommand()) { cmd.CommandText = "Select RecordID , ObjectName , WatchTime , EventStyle From FileSystemLog order by WatchTime"; System.Data.IDataReader reader = cmd.ExecuteReader(); while (reader.Read()) { ListViewItem NewItem = new ListViewItem(); NewItem.Tag = Convert.ToString(reader.GetValue(0)); NewItem.Text = Convert.ToString(reader.GetValue(1)); NewItem.SubItems.Add(Convert.ToString(reader.GetValue(2))); string Style = Convert.ToString(reader.GetValue(3)); NewItem.SubItems.Add(Style); Style = Style.Trim().ToLower(); if (Style == "created") { NewItem.Group = lvwRecord.Groups[0]; NewItem.ImageIndex = 0; } else if (Style == "changed") { NewItem.Group = lvwRecord.Groups[1]; NewItem.ImageIndex = 1; } else if (Style == "deleted") { NewItem.Group = lvwRecord.Groups[2]; NewItem.ImageIndex = 2; } else if (Style == "renamed") { NewItem.Group = lvwRecord.Groups[3]; NewItem.ImageIndex = 3; } NewItem.StateImageIndex = NewItem.ImageIndex; lvwRecord.Items.Add(NewItem); } reader.Close(); } myStatus.Text = "共列出 " + lvwRecord.Items.Count + " 个记录"; } catch (Exception ext) { MessageBox.Show(ext.ToString(), "系统错误"); } this.Cursor = System.Windows.Forms.Cursors.Default; lvwRecord.EndUpdate(); } |
在该按钮事件处理中,我们查询数据表FileSystemLog,对每一条查询所得的数据创建一个ListViewItem项目,并根据记录的EventStyle值设置该列表项目的图标序号和分组状态。
工具条的“删除记录”按钮用于删除列表中选择的项目,其点击事件处理为
|
private void btnDelete_Click(object sender, EventArgs e) { if (lvwRecord.SelectedItems.Count > 0) { using (System.Data.IDbCommand cmd = Util.DBConnection.CreateCommand()) { for (int iCount = lvwRecord.Items.Count - 1; iCount >= 0; iCount--) { ListViewItem item = lvwRecord.Items[iCount]; if (item.Selected) { cmd.CommandText = "Delete From FileSystemLog Where RecordID = cmd.ExecuteNonQuery(); lvwRecord.Items.Remove(item); } }//for }//using } } |
在刷新列表中,我们将列表项目的Tag属性值设置为数据库记录的编号,在这里我们利用这个事先保存的数据库记录的编号拼凑出SQL语句然后删除指定的记录。
工具条的“启动服务”按钮用于启动后台的文件监视服务。其点击事件处理为
|
private void btnStartService_Click(object sender, EventArgs e) { if (bolServiceInstalled == false) return; using (System.ServiceProcess.ServiceController control = new System.ServiceProcess.ServiceController("MyFileSystemWatcher")) { if (control.Status == System.ServiceProcess.ServiceControllerStatus.Stopped) { control.Start(); } } } |
在这里我们创建一个ServiceController对象,若判断出服务的状态为停止,则调用控制器的Start方法来启动服务,在这里Start方法内部只是通知操作系统启动指定名称的服务,它发送通知后立即返回,并不会等待服务启动后返回。
类似的对于“停止服务”,其点击事件处理为
|
private void btnStopService_Click(object sender, EventArgs e) { if (bolServiceInstalled == false) return; using (System.ServiceProcess.ServiceController control = new System.ServiceProcess.ServiceController("MyFileSystemWatcher")) { if (control.Status == System.ServiceProcess.ServiceControllerStatus.Running) { control.Stop(); } } } |
在这个处理过程中,若判断出服务状态为运行中,则调用控制器的Stop方法来停止服务。在这里Stop方法内部只是通知操作系统停止指定的服务,它发送通知后立即返回,不会等待服务停止后返回。
我们还在窗体上放置一个定时器控件,定时间隔为2秒,用于根据服务的状态刷新工具条按钮状态,其定时事件处理为
|
private void myTimer_Tick(object sender, EventArgs e) { if (bolServiceInstalled == false) return; using (System.ServiceProcess.ServiceController control = new System.ServiceProcess.ServiceController("MyFileSystemWatcher")) { btnStartService.Enabled = (control.Status == System.ServiceProcess.ServiceControllerStatus.Stopped); btnStopService.Enabled = (control.Status == System.ServiceProcess.ServiceControllerStatus.Running); } } |
在这里我们创建了一个绑定到文件系统监控服务的ServiceController对象,然后根据它的Status状态来设置“启动服务”和“停止服务”按钮的可用状态。
在客户端主窗体中点击工具条的“系统配置”按钮就会弹出系统设置对话框,该对话框的用户界面为
该对话框比较简单,就是用于显示和修改系统配置信息对象MyConfig中的内容。由于文件系统监视服务只有在启动的时候读取系统配置信息,因此对系统配置的任何修改都需要重新启动服务才能生效。
本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@hmdbvip.cn
文章转载自:博客园