VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > temp > C#教程 >
  • c#发现之旅—c#开发windows service程序上

制作者:剑锋冷月 单位:无忧统计网,www.51stat.net
 

  本文示例源代码或素材下载

  Windows Service概念介绍

  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资源管理器,在左边的树状列表中选中“桌面-控制面板-管理工具”。

C#发现之旅:C#开发Windows Service程序(上)

  图片看不清楚?请点击这里查看原图(大图)。

  在右边的列表中打开“服务”项目即可打开Windows服务管理器。

C#发现之旅:C#开发Windows Service程序(上)

  图片看不清楚?请点击这里查看原图(大图)。在这些服务中,有我们最熟悉的IIS Admin和World Wide Web Publishing服务了。我们双击一个服务项目即可打开服务属性对话框。

 

C#发现之旅:C#开发Windows Service程序(上)

  Windows服务有一个服务名称属性,该属性是服务的惟一的不可重复的名称,我们可以在命令行中使用命令“net start 服务名称”来启动服务,使用“net stop 服务名称”来停止服务。

  Windows服务的启动类型有自动,手动和已禁用。当启动类型为自动时,Windows启动后不等用户登录就自动启动服务,当启动类型为手动时,需要某个操作员登录后点击这里的“启动”按钮来启动服务,而当启动类型为已禁用时,Windows服务不能启动。

  该页面中的“启动”按钮用于启动尚未启动的Windows服务,运行提供服务的进程;“停止”按钮用于停止已经启动的服务,杀死服务进程;而“暂停”按钮用于通知服务进程暂时停止提供服务,但服务进程依然存在;而“恢复”按钮用于通知处于暂停模式的服务进程重新提供服务。

   我们可以查看服务属性对话框的“登录”页面。

C#发现之旅:C#开发Windows Service程序(上)

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

  我们切换到“依存关系”页面,可以看到本服务和其他服务的依存关系。

 

C#发现之旅:C#开发Windows Service程序(上)

  各个Windows服务之间可能存在依赖关系,比如IISADMIN服务就依赖另外一个名为RPC的Windows服务,当启动一个Windows服务时,系统会启动该服务所依赖的其他Windows服务。例如我们设置IISADMIN服务为自动启动,而RPC服务为手动启动,则Windows启动后会试图自动启动IISADMIN服务,结果会首先启动RPC服务,即使RPC服务不是自动启动。若RPC服务为禁止,无论如何也不能启动,则IISADMIN服务就无法自动启动了。

  C#编写Windows服务的基本过程

  编写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#发现之旅:C#开发Windows Service程序(上)

  图片看不清楚?请点击这里查看原图(大图)。

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

C#发现之旅:C#开发Windows Service程序(上)

  软件设计

  命令行参数设计

  一般而言,我们将服务和客户端分成两个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中保存的系统配置信息。

  软件说明

  根据上述的软件设计,我已经把软件编写完毕,现对该软件结构进行说明

  客户端主界面 frmClient

  本软件为一个C#编写的EXE,主要包含服务端软件模块和客户端软件模块。首先对比较好理解的具有图形化用户界面的客户端模块进行说明,客户端的主界面为

C#发现之旅:C#开发Windows Service程序(上)

  图片看不清楚?请点击这里查看原图(大图)。

  这个界面主要功能是数据库信息管理,最上面为一个工具条,中间大部分的一个ListView控件,最下面为状态栏。

  对于ListView控件其内容是分组的,因此需要设计其分组信息,在VS.NET的窗体设计器中我们点中ListView控件,在旁边的属性列表中选择Groups属性,点击旁边的小按钮可以弹出分组设计器。

C#发现之旅:C#开发Windows Service程序(上)

 

  图片看不清楚?请点击这里查看原图(大图)。

  使用这个分组编辑器我们可以很容易的设计该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();
}

 

C#发现之旅:C#开发Windows Service程序(上)

  Windows服务有一个服务名称属性,该属性是服务的惟一的不可重复的名称,我们可以在命令行中使用命令“net start 服务名称”来启动服务,使用“net stop 服务名称”来停止服务。

  Windows服务的启动类型有自动,手动和已禁用。当启动类型为自动时,Windows启动后不等用户登录就自动启动服务,当启动类型为手动时,需要某个操作员登录后点击这里的“启动”按钮来启动服务,而当启动类型为已禁用时,Windows服务不能启动。

  该页面中的“启动”按钮用于启动尚未启动的Windows服务,运行提供服务的进程;“停止”按钮用于停止已经启动的服务,杀死服务进程;而“暂停”按钮用于通知服务进程暂时停止提供服务,但服务进程依然存在;而“恢复”按钮用于通知处于暂停模式的服务进程重新提供服务。

   我们可以查看服务属性对话框的“登录”页面。

C#发现之旅:C#开发Windows Service程序(上)

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

  我们切换到“依存关系”页面,可以看到本服务和其他服务的依存关系。



相关教程