字体: | 推荐给好友 上一篇 | 下一篇

Windows2000 服务器端应用程序开发设计指南-服务应用程序(2)

发布: 2008-5-06 20:10 | 作者: Jeffrey Richter Jaso | 来源: 本站原创 | 查看: 155次


客户端应用程序应该在Windows 2000、Windows 98、UNIX或任何其他作业系统上执行。您可以建立一个以HTML为基础的使用者介面以与使用ActiveX控制或Active Server Pages的服务沟通。您也应该为了MMC而考虑建立您的使用者介面之嵌入式管理单元。想想这个客户端应用程序为了您而开启的可能性,不是因为所有使用者介面被发布而限制它们。


在移至这个主题之前,我想要多讨论一些有关使用者介面的议题:一些Windows函数会产生硬体错误的讯息方块。例如,如果您正从光碟执行一个应用程序并且移除了光碟片,此时系统会自动地显示一个讯息方块。如果系统没有显示讯息方块,它的另一个选择即是删除该处理程序。


它有被系统修改的可能性,所以硬体错误会被记录至事件记录中并且不会产生讯息方块。要改变系统的行为,您必须在以下所列之登录子机码中修改ErrorMode的值:


HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Windows

表3-4列出了ErrorMode的可能值。



 

   
     
   
 
 表3-4 定义是否显示硬体错误之讯息方块的ErrorMode值



 

   


   
   
     
     
   
   
     
     
   
   
     
     
   
 
说明
0(预设值)系统会显示错误讯息方块。
1对于非系统产生的错误,会显示一个错误讯息方块。若为系统产生的错误,则会在事件记录中加入一个项目并且不会显示错 误讯息方块。
2 (一个没有人看顾的服务器之最佳选择)不管为系统产生或非系统产生的错误,皆加入事件记录中,并且不会显示错误讯息方块。


对服务侦错
 


在对一个服务侦错时比对一般应用程序的侦错更难处理,原因有许多个。第一,除错器无法启动服务,必须由SCM启动服务。第二,许多服务在使用者登入前即已启动。由于这个原因,当您在对服务做侦错时,将设定为自动启动的服务改为手动启动的方式是一个好方法。第叁,服务在它们各自拥有的视窗配置与桌面中执行,而它们不会为了互动式使用者而显示。


所以您要如何对服务侦错呢?最好的方法是像服务那样固定地执行一个固定的可执行程序。在您服务内的(w)main或(w)WinMain函数会为一个特定的命令列打开您所拥有的设计,如果该开关已被打开,则以直接地呼叫您服务之ServiceMain函数的方式取代呼叫StartServiceCtrlDispatcher。这个技术当然会有很多缺点:



  • 如果您的可执行档中包含了许多个服务,则在同一个时间内您只能对其中一个服务做侦错的动作。
     
  • 该执行档正以您的帐户执行而您的帐户不能让SCM使用。这也许会限制一个正常情形下应被允许的资源存取。
     
  • 您不能传送一个暂停、继续执行、停止、关机或任何使用者已定义的通知至服务中,并且被禁止对执行路径做测式。
     

刚才所提的方法可以使您较容易的对您的服务做侦错,但是有一个较好的方法是当服务正在执行时,将侦错器与服务连接起来。大部份的侦错器皆提供连接至正在执行之处理程序的能力。如果您已经在系统上安装了一个侦错器,那么您便可以开启工作管理员,在服务处理程序的名称上敲击滑鼠右键,并且由在功能表中选择侦错选项。这个动作会使侦错器连接至您的服务。现在您可以设定中断点、对服务程序代码侦错甚至可以测试您的程序如何回应控制码的通知。以下为将侦错器与服务连接所产生的几个问题:



  • 您所使用来登入系统的帐户必须拥有侦错的权限。在预设的情形下,这个权限只被指定给管理员。如果您以Power User的身份或一些其他的帐户登入系统,则您必须拥有管理员指定给您的侦错权限。
     
  • 您不能对您的初始化程序代码做侦错,因为侦错器在服务执行后才与它连结。
     

如果您真的想要在使用这个方法来对您的服务之初始化程序代码做侦错,只要在您的(w)main或(w)WinMain中增加一个对DebugBreak函数的呼叫即可以简单地达到这个目的。然而这个技术只在您的服务以本机帐户执行时才能产生作用。如果您在一个不同的使用者帐户下执行您的服务时,此时您的侦错器不会正常地执行,因为系统不允许它与互动式视窗配置及桌面沟通。


这里有另一个您可以用来对服务侦错的技巧:Windows提供了一个每当一个处理程序启动时即唤起侦错器的能力。要使系统达到这个功能,您必须先建立一个以下所列之登录机码中的子机码:


HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\

 CurrentVersion\Image File Execution Options

在这个机码下建立一个与可执行之服务名称相同的子机码(不用加入路径)。在该可执行名称之子机码中,加入一个名称为Debugger的字串值,并且设定其值为您的侦错器之完整的执行路径(例如,「C:\Program Files\Microsoft Visual Studio\Common\MSDev98\Bin\msdev.exe」)。


一旦您将这些设定完成后,您便可以到服务嵌入式管理单元中启动服务。此时SCM会启动侦错器而非可执行之服务。由此,您可以开启您的服务程序代码文件、设定中断点然后让服务执行。注意在SCM强迫终止侦错器前,您有30秒的时间可以做您的侦错工作(因为服务不会呼叫StartServiceCtrlDispatcher)。


如果所有的这些限制令您烦恼,而且您想要能够在最自然的情形下对服务侦错,不用与桌面互动或担心使用者帐户的问题,那么您可以使用核心的侦错器。


TimeService服务范例
 


在本节最后面的列表3-1中说明了TimeService服务范例(「03 TimeService.exe」),其中包括了所有建立一个服务所需的元件。该应用程序的程序代码与资源文件皆存放在随书光碟中的03-TimeService目录中。这是一个非常简单的服务,当一个客户端连结至此服务时,它会回传执行服务之机器的日期以及时间。此服务假设您已经稍微对命名管道与I/O完成端口有所了解(在第二章中讨论)。


如果您检查_tWinMain函数,您将会看见这个服务拥有依据命令列参数所传送之「-install」或「-remove」可以选择从SCM的资料库中安装或移除它自己的能力。一旦您建立了服务并且第一次从命令列环境中执行它时,请传递「-install」参数给它。当您不想让该服务保存在您的机器上时,请从命令列环境中执行它,并传递「-remove」参数给它。我将会在下一章中讨论如何从SCM增加或移除一个服务之函数的细节。


_tWinMain之最重要的观念是去通知一个被二个成员初始化的SERVICE_TABLE_ENTRY结构阵列:一个是服务,另一个是使用NULL项目来识别最后一个服务。那些为了服务而建立的线程之服务表格阵列位址会被传递到StartServiceCtrlDispatcher中。这个新的线程会与TimeServiceMain函数一起开始执行。注意除非TimeServiceMain函数已离开且它的线程已终止执行,否则StartServiceCtrlDispatcher不会返回至_tWinMain函数。


TimeServiceMain函数实作了真实的程序代码以处理客户端的要求。经由建立一个I/O完成端口可以启动它。该服务线程会在一个等待要求以进入完成连接埠的回圈中执行。有二种可能的要求类型:一个客户端已连接至管道并且等待机器的日期与时间资讯;或是该服务需要去处理一个例如暂停、继续或停止的动作要求。


一旦完成连接埠被建立后,我初始化了一个全域的CserviceStatus物件—g_ssTime。该CserviceStatusC++ 类别是我自己建立的,而且它只是简单地回报服务的更新状态。这个类别是来自Windows的SERVICE_STATUS结构而且基本上它使用了一点点的抽象化概念,用来放置被更新的成员变数之一些逻辑的状态。CserviceStatus拥有一个被使用来让TimeHandlerEx线程与TimeServiceMain达到同?以及允许ServiceMain线程在同一时间处理一个单一动作之CGate类别物件成员。


在CserviceStatus的Initialize方法内呼叫了RegisterServiceCtrlHandlerEx以通知HandlerEx函数(TimeHandlerEx)的SCM,而且它会传递C++ 类别物件之I/O完成端口的位址iocp至它的pvContext中以控制函数。Initialize方法中也设定了dwServiceType成员,以表示在服务的执行期间中没有被改变的内容。


接下来,我呼叫了AcceptControls方法,以设定dwControlsAccepted成员。当服务执行至接受或拒绝被要求的控制时,AcceptControls方法可以被周期性地呼叫。在执行期间TimeService接受停止、暂停、继续以及关闭之控制码。


您将会注意到TimeHandlerE x函数经由呼叫CIOCP的PostStatus方法来传递控制码给服务线程,并经由使用I/O完成端口的Handle来由内部呼叫PostQueuedCompletionStatus。一个代表完成的CK_SERVICECONTROL控制码会被指定以指示ServiceMain已经因为一个服务之要求而醒来,然后TimeHandlerEx函数会尽快地返回。服务线程的责任为醒来,处理控制码,然后等待更多的客户端要求。


在TimeServiceMain函数中,一个do-while回圈开始启动。在该回圈中,我检查了CompKey变数的值察看服务下一个需要回应的动作。由于此变数被初始化到CK_SERVICECONTROL中,而dwControl变数被初始化至SERVICE_CONTROL_ CONTINUE中,所以此服务的第一件工作即是建立一个命名管道,然后客户端应用程序会使用它来建立对服务的要求。接下来会使用一个CK_PIPE的完成控制码来使这个管道与完成连接埠关联,并且建立一个对ConnectNamedPipe的非同步呼叫。此服务现在会经由呼叫g_ssTime物件之ReportUltimateState方法中以呼叫内部的SetServiceStatus方式将SERVICE_RUNNING回报至SCM中。


服务呼叫iocp物件之GetStatus方法(内部呼叫GetQueuedCompletionStatus)。这导致服务线程进入睡眠状态,直到一个事件在完成连接埠中出现为止。如果出现了一个服务控制码(因为TimeHandlerEx已经呼叫PostQueuedCompletion Status),则服务线程会醒来,适当地处理控制码以及再次将作业完成的状态回报至SCM。请注意,TimeHandlerEx的责任是回报动作的悬置状态,而TimeServiceMain的义务则是回报服务的最后执行状态(即是我曾在之前的〈处理线程内部通讯议题〉一节中讨论的第叁个方法)。


当客户端已经连结至管道,而服务线程因为GetQueuedCompletionStatus的返回而醒来时, CK_PIPE的完成控制码会被回传。由此,服务会取得系统时间并呼叫WriteFile以传送时间至客户端。然后服务会中断客户端与发布另一个对ConnectNamedPipe的非同步呼叫的连结,以让其他的客户端可以与它连接。


当服务线程因为SERVICE_CONTROL_STOP或SERVICE_CONTROL_ SHUTDOWN控制码而醒来时,它会关闭管道并终止执行。这会导致完成连接埠关闭、TimeServiceMain函数返回、并且将该服务线程删除。由此,StartServiceCtrlDispatcher会返回至_tWinMain中,而它也会返回并删除该处理程序。


在您建立了服务后,您必须在命令列环境中传递「-install」以将服务安装至SCM的资料库中。因为在执行档名称中包含了空白字元,所以请确定在可执行档名称前后包括了引号,即「"03 TimeService.exe"」。而且,您会想要使用服务嵌入管理单元去启动以及管理该「Programming Server-Side Applications Time」服务。

 

评分:0

我来说两句

seccode