郭静毅
摘要 Windows服务是指在系统启动时自动加载的程序。Windows系统为我们提供了众多服务,在系统服务管理器中可以实现对这些服务的监视与控制。本案例的实现目标就是应用.Net Framework提供的服务管理类(如ServiceController 类)来实现对系统服务的实时控制,开启或关闭相应服务以达到优化系统的目的。 关键词 Windows 服务,ServiceController,C# 一、 Windows 服务及ServiceController 类简介 Windows服务是微软从Windows2000才开始引入的一个概念,它指的是操作系统启动时可以自动打开的程序,并且其运行需要NT内核。Windows服务可以在没有交互式用户登录系统的情况下运行。在表1中列举了一些服务的示例及描述: 表1 部分Windows服务的示例及描述
服务 |
描述 |
Alerter |
当系统发生故障时向管理员发送错误警报 |
ASP.Net 状态服务 |
为 ASP.Net 提供进程外会话状态支持 |
Application Management |
提供软件安装服务,诸如分派、发行以及删除 |
DNS Client |
为此计算机解析和缓冲域名系统名称(DNS) |
FTP Publishing |
通过 Internet 信息服务管理单元提供 FTP 连接和管理 |
HTTP SSL |
此服务通过安全套接字层(SSL)实现 HTTP 服务 |
Telnet |
允许远程用户登录到此计算机并运行程序 |
单击运行 → 键入Services.msc或者单击开始 → 设置 → 控制面板 → 管理工具 → 服务便可以打开服务管理器,如图1所示。
图1 系统服务管理器图
Windows服务的体系结构主要由3种类型的程序组成,而在.Net Framework中的System.ServiceProcess 命名空间下提供了实现这3个部分的服务类。 1.服务程序 在.Net Framework中从ServiceBase类继承的类可以实现服务。其主要用于注册服务、响应开始和停止请求。 2.服务控制程序 在.Net Framework中ServiceController类可用于实现服务控制程序。通过它可以把诸如启动、停止、暂停、继续或执行自定义命令等请求发送给服务。 3.服务配置程序 在.Net Framework中,ServiceProcessInstaller类和ServiceInstaller类可用于安装和配置服务程序。 值得注意的是,之所以使用服务配置程序来安装程序是因为服务不但要复制到文件系统中去而且还需要向注册表中写入,并配置为一个服务。这点区别于一般的.Net组件,.Net组件是不需要写信息进注册表的,可以直接用xcopy命令进行安装。 本例将主要讨论的是ServiceController类,介绍如何实现对已有服务的控制。ServiceController表示Windows 服务并允许连接到正在运行或者已停止的服务、对其进行操作或获取有关它的信息。 首先,利用ServiceController类可以获取每一个服务的信息以实现对服务的监视。在表2中列出了ServiceController类的一些属性及其描述。 表2 一些ServiceController类的属性及其描述
属性 |
描述 |
CanPauseAndContinue |
获取一个用于指示是否可以暂停和继续服务的布尔值。 |
CanShutdown |
获取一个用于指示在系统关闭时是否应通知服务的布尔值。 |
CanStop |
获取一个用于指示服务在启动后是否可以停止的布尔值。 |
DependentServices |
获取依赖于与此 ServiceController 实例关联的服务的服务集。 |
DisplayName |
获取或设置服务的显示名称。比如ASP.Net 状态服务为服务的显示名称,服务的实际名称为aspnet_state。 |
MachineName |
获取或设置此服务所驻留的机器名称。 |
ServiceHandle |
获取服务的句柄。 |
ServiceName |
获取或设置对此实例引用的服务进行标识的名称。 |
ServicesDependedOn |
获取此服务所依赖的服务集。 |
ServiceType |
获取此对象引用的服务类型。 |
Status |
获取由此实例引用的服务的状态。状态可以是正在运行、停止、暂停或者某些中间状态。状态值在ServiceController枚举中定义。 |
ServiceController 类还能完成对服务的控制,该类的方法及描述如表3所示。
表3 ServiceController 类服务控制方法
方法 |
描述 |
Start |
用于启动服务。 |
Stop |
用于停止该服务以及任何依赖于该服务的服务。 |
Pause |
用于挂起服务。 |
Continue |
用于在服务暂停后继续该服务。 |
ExecuteCommand |
用于对服务执行自定义命令。 |
二、 集合类System.Collections概述 从概念上来讲,集合指的是一组组合在一起的类似的类型化对象。一个集合可以包括多个元素,即有一个集合类对象和N个元素对象。通过遍历可以访问到集合中的每个元素,特别是在C#中可使用foreach语句实现循环访问。例如,如果ArraySet是一个集合类型,那么可以编写如下代码对ArraySet中的每个元素进行访问: foreach(object element in ArraySet) { DoSomething(element); } 实际上,可以使用foreach语句也是集合类的主要特性。 在.Net中,因为任何的集合类必须实现System.Colloctions.IEnumerable接口。在IEnumerable接口中只定义了一个方法,代码如下。 interface IEnumerable { IEnumerator GetEnumerator(); } 所以任何集合类型的对象都有一个GetEnumerator()方法,该方法可以返回一个实现了IEnumerator接口的对象。IEnumerator接口的实现代码如下。 interface IEnumerator { object Current{get;} bool MoveNext(); void Reset(); } 注意,这个返回的IEnumerator对象既不是集合类对象,也不是集合的元素类对象,它是一个独立的类对象,并且通过这个对象可以遍历访问集合类型对象中的每一个元素。如果集合类是用户自定义的集合类,则用户必须实现它的GetEnumerator()方法,否则不能使用循环。当然,与这个自定义集合类对应的实现了IEnumerator接口的类也需要自定义。 MoveNext()可以实现从集合的当前元素移动到下一个元素。Current属性可以获取当前元素。Reset()可以IEnumerator对象返回到集合的初始化时指向的位置。注意,这里的初始化时指向的位置并不是指集合开头位置。实际上,可以把这个初始位置看成是返回了集合开头前面的位置,因为集合初始化时IEnumerator对象并没有指向集合的任何元素,必须调用MoveNext()才能使它指向集合中的第一个元素。 在.Net Framework 的平台中,System.Colloctions命名空间下提供了一系列实现集合功能的类,并且根据适用环境的不同为开发者提供灵活多样的选择。System.Collections 主要包括了以下通用的类和接口: ArrayList类: 数组列表,是Array类的优化版本。它实现了接口IList、ICollection以及IEnumerable。 ArrayList中的维数可以有一个或多个Array具有的固定的容量。如果有可变容量,则用Array.CreateInstance,其可以不从零开始存储。 ArrayList提供了Array所不具有的某些灵活性。比如,可以设置ArrayList的下限,但Array的下限始终为零;ArrayList可以具有多个维度,而Array始终是唯一的;Array是特定类型而不是Object类型,因此性能上会比ArrayList好。ArrayList在存储和检索时经常发生拆箱和装箱操作现象。 Hashtable类:一种数据结构,将数据作为一组键(Key)值(Value)来存储,哈希表中数据将会根据Key来建立索引,一般用来存储几万,几十万条数据,数据搜索性能高。它实现了接口IDictionary、ICollection以及IEnumerable。Hashtable类有以下特点: 首先,Hashtable类基于IDictionary接口,因此该集合中的每一元素都是键和对应的值。 然后,Object.GetHashCode方法为其自身生成哈希代码。还可以通过使用Hashtable构造函数,为所有元素指定一个哈希函数。 SortedList类:一种排序的数据列表,兼顾了ArrayList和Hashtable的优点。它也是将数据作为一组键(Key)值(Value)来存储,也会根据Key来建立索引,一般用来存储几百,几千条数据,当存储几万条是数据的搜索性能就会降低,因此超过上万条记录时建议使用Hashtable。它实现了接口IDictionary、ICollection以及IEnumerable。SortedList类有以下特点: 首先,SortedList类类似于Hashtable和ArrayList间的混合; 其次,SortedList的每一元素都是键以及其对应的值,提供只返回键列表或只返回值列表的方法; 最后,如果想要一个保留键和值的集合,并且还需要索引的灵活性,则使用SortList。 Queque类:队列类,它使用的是以先进先出的方式访问各个元素,元素进入集合的顺序与其弹出的顺序相同。可以调用Queque对象的GetEnumerator()方法,得到IEnumerator对象,来遍历队列中的各个元素。它本身实现了接口ICollection以及IEnumerable。 Stack类:堆栈类,它使用的是后进先出的方式访问各个元素,元素进入集合的顺序与其弹出的顺序相反。可以调用Stack对象的GetEnumerator()方法,得到IEnumerator对象,来遍历堆栈中的各个元素。它本身实现了接口ICollection以及IEnumerable。 ICollection接口:定义所有集合的大小、枚举数和同步方法。它派生于IEnumerable,定义了集合类最基本的行为,基本所有的集合类都实现了这个接口(基接口)。 IEnumerable接口:用于支持在集合上进行简单迭代。它只有一个方法 GetEnumerator(),该方法可以返回一个IEnumerator接口,通过它可以遍历集合。所有的集合类都实现了这个接口。 IList接口:IList实现是可排序且可按照索引访问其成员的值的集合,它本身实现了ICollection和IEnumerable接口。它也是所有列表的抽象基类。 三、详细设计 本例主要实现了获取每一个服务的信息以实现对服务的监视,利用ServiceController 类提供的方法完成对服务的控制,这些控制命令包括诸如启动、停止、暂停、继续或执行自定义命令等请求。根据要求设计出程序界面(如图2所示)。
图2 系统服务控制程序界面设计图 界面主要由五个部分组成。从上之下,依次是菜单栏、服务列表、服务控制命令按钮、是否自动更新服务复选框以及状态栏组成,在Visual Studio 2005的工具箱中分别可以找到MenuStrip、ListView、Button、CheckBox以及ToolStrip控件并添加相应代码逻辑来实现。菜单栏的主要功能是实现工具栏菜单项中对服务的刷新。ListView控件用于列出当前系统服务的一些信息。服务控制命令按钮用于对服务执行启动、停止、暂停以及继续这些操作。复选框结合Timer控件实现定时对服务的自动刷新功能。 由于要实现服务的自动刷新,程序要使用到Timer控件。Timer控件提供了一种以指定的时间间隔执行方法的机制。时间间隔的长度由它的Interval 属性定义,其值以毫秒作为单位。当启用了该组件后,每个时间间隔将引发一个 Tick 事件。如图3、4所示,Interval 属性被设置成了5000毫秒,当启用后每隔5000毫秒将调用一次tmrStatus_Tick方法。
图3 设置Timer控件Interval 属性
图4 设置Timer控件Tick事件绑定的委托方法
在服务列表需要以列表的形式显示和刷新当前系统服务,因此这里就要用到之前介绍过的集合类知识了。尤其是当刷新服务的时候,需要根据用户当前选中的ListView中Item的名称来找到所代表的ServiceController类型的系统服务实例。这两者之间就存在着一一对应的关系,在程序中可以通过构建一个HashTable来对这种关系进行记录,图5说明了这种关系。
图5 ServiceController类型服务与服务显示名称一一对应关系图 在以下的服务列表显示中,首先构建了这种一一对应关系。 private Hashtable mcolSvcs = new Hashtable(); …… ServiceController[] svcs = ServiceController.GetServices(); foreach(ServiceController svc in svcs) { //将服务的显示名称添加到ListView中 ViewItem = this.lvServices.Items.Add(svc.DisplayName); ViewItem.SubItems.Add(svc.ServiceName.ToString()); ViewItem.SubItems.Add(svc.Status.ToString()); ViewItem.SubItems.Add(svc.ServiceType.ToString()); /*使用HashTable类型的mcolSvcs将ListView每个项目中的值(即服务 的显示名称)和ServiceController类型的服务实例对应起来*/ mcolSvcs.Add(svc.DisplayName,svc); } 在接下来的服务刷新显示中,将充分利用这种一一对应关系。 foreach(ListViewItem lvi in this.lvServices.Items) { /*通过服务显示名称在HashTalbe中找到其对应的键,并强制转换成 ServiceController类型*/ msvc = ((ServiceController) (mcolSvcs[lvi.Text])); msvc.Refresh(); lvi.SubItems[2].Text = msvc.Status.ToString(); } UpdateUIForSelectedService(); 当实现服务执行启动、停止、暂停以及继续这些操作按钮的代码逻辑时需要根据当前服务的状态来判断按钮是否为Enabled。例如,如果当前状态是Stopped,就应该用户允许执行开始服务,即开始按钮应该设置为Enabled。下面代码完整的说明了这点。 if (this.lvServices.SelectedItems.Count == 1) { strName = this.lvServices.SelectedItems[0].SubItems[0].Text; ItemIndex = this.lvServices.FocusedItem.Index; msvc = ((ServiceController) (mcolSvcs[strName]) ); // 如果当前状态是Stopped,就应该允许用户执行开始服务 this.cmdStart.Enabled = (msvc.Status == ServiceControllerStatus.Stopped); /* 如果当前状态不是Stopped并且系统允许停止服务,就应该允许用户执 行停止服务*/ this.cmdStop.Enabled = (msvc.CanStop && (!(msvc.Status == ServiceControllerStatus.Stopped))); /* 如果当前状态不是Paused并且系统允许暂停恢复服务,就应该允 许用户执行暂停服务*/ this.cmdPause.Enabled = (msvc.CanPauseAndContinue && (!(msvc.Status == ServiceControllerStatus.Paused))); // 如果当前状态是Paused,就应该允许用户执行恢复服务 this.cmdResume.Enabled = (msvc.Status == ServiceControllerStatus.Paused); }
四、核心代码解析 实现服务列表显示功能的程序源代码如下。 private void EnumServices() { try { fUpdatingUI = true; this.sbInfo.Text = "Loading Service List, pleasse wait"; this.sbInfo.Refresh(); this.lvServices.Items.Clear(); if (!(mcolSvcs == null)) { mcolSvcs = new Hashtable(); } ServiceController[] svcs = ServiceController.GetServices(); foreach(ServiceController svc in svcs) { //将服务的显示名称添加到ListView中 ViewItem = this.lvServices.Items.Add(svc.DisplayName); ViewItem.SubItems.Add(svc.ServiceName.ToString()); ViewItem.SubItems.Add(svc.Status.ToString()); ViewItem.SubItems.Add(svc.ServiceType.ToString()); /*使用HashTable类型的mcolSvcs将ListView每个项目中的值(即服务 的显示名称)和ServiceController类型的服务实例对应起来*/ mcolSvcs.Add(svc.DisplayName,svc); } } catch (Exception exp) { MessageBox.Show(exp.Message, exp.Source, MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { this.sbInfo.Text = "Ready"; fUpdatingUI = false; } } 当用户选中CheckBox或者取消CheckBox选择时,启用或者停止Timer控件。 private void chkAutoRefresh_CheckedChanged(object sender, System.EventArgs e) { // 通过判断CheckBox是否被选中来决定Timer控件是否启用 if (this.chkAutoRefresh.CheckState == CheckState.Checked) { this.tmrStatus.Enabled = true; } else { this.tmrStatus.Enabled = false; } } 实现更新所有服务状态功能的程序源代码及解释如下。 private void UpdateServiceStatus() { try { fUpdatingUI = true; this.sbInfo.Text = "Checking Service Status . . "; this.sbInfo.Refresh(); /*通过服务显示名称在HashTalbe中找到其对应的键,并强制转换成 ServiceController类型*/ foreach(ListViewItem lvi in this.lvServices.Items) { msvc = ((ServiceController) (mcolSvcs[lvi.Text])); msvc.Refresh(); lvi.SubItems[2].Text = msvc.Status.ToString(); } UpdateUIForSelectedService(); } catch (Exception exp) { MessageBox.Show(exp.Message, exp.Source, MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { this.sbInfo.Text = "Ready"; fUpdatingUI = false; } 实现更新选定服务状态功能的程序源代码及解释如下,通过ListView列表中服务显示名称在HashTalbe中找到其对应的键,即对应的服务类型。 private void UpdateUIForSelectedService() { // Update the command buttons for the selected service. string strName; int ItemIndex; try { if (this.lvServices.SelectedItems.Count == 1) { strName = this.lvServices.SelectedItems[0].SubItems[0].Text; ItemIndex = this.lvServices.FocusedItem.Index; /*通过服务显示名称在HashTalbe中找到其对应的键,并强制转换成 ServiceController类型*/ msvc = ((ServiceController) (mcolSvcs[strName]) ); // 如果当前状态是Stopped,就应该用户允许执行开始服务 this.cmdStart.Enabled = (msvc.Status == ServiceControllerStatus.Stopped); /* 如果当前状态不是Stopped并且系统允许停止服务,就应该允许用户执 行停止服务*/ this.cmdStop.Enabled = (msvc.CanStop && (!(msvc.Status == ServiceControllerStatus.Stopped))); /* 如果当前状态不是Paused并且系统允许暂停恢复服务,就应该允 许用户执行暂停服务*/ this.cmdPause.Enabled = (msvc.CanPauseAndContinue && (!(msvc.Status == ServiceControllerStatus.Paused))); // 如果当前状态是Paused,就应该允许用户执行恢复服务 this.cmdResume.Enabled = (msvc.Status == ServiceControllerStatus.Paused); } } catch (Exception exp) { MessageBox.Show(exp.Message, exp.Source, MessageBoxButtons.OK, MessageBoxIcon.Error); } }
五、程序运行效果 编译代码,程序执行效果如下图6所示。当选中某项具体的系统服务时,根据当前服务的状态,确定用户可以对服务执行哪些操作。例如,图6中Alerter服务的当前状态为Stopped,因此用户只能开启这项服务。
图6 系统服务控制程序运行效果
|