C# 6 与 .NET Core 1.0 高级编程 - 39 章 Windows 服务(下)
译文,个人原创,转载请注明出处(C# 6 与 .NET Core 1.0 高级编程 - 39 章 Windows 服务(下)),不对的地方欢迎指出与交流。
章节出自《Professional C# 6 and .NET Core 1.0》。水平有限,各位阅读时仔细分辨,唯望莫误人子弟。
附英文版原文:Professional C# 6 and .NET Core 1.0 - Chapter 39 Windows Services
本章节译文分为上下篇,上篇见:C# 6 与 .NET Core 1.0 高级编程 - 39 章 Windows 服务(上)
(接下来会翻译本书的重点 asp.net mvc 和 asp.net core,这是译者找到此书的动机之一。
这两章篇幅也较长,对于每章除了翻译,译者发表中英文版前都要整理排版,效率不算高,请大家耐心等待。
当然如果有园友愿意义务帮忙排版的请大意大胆留言或私信我吧,热烈欢迎!
目前能找到关于 .net core的资料特别是中文资料太少了,据说asp.net core 2.0 今年4月份前相关书籍会发表,希望同样有机会翻译,为技术更新换代的中文资料贡献一点绵薄之力。)
------------------------------------------------------------------------------
监视和控制Windows服务
要监视和控制Windows服务,可以使用属于计算机管理管理工具的服务MMC管理单元。每个Windows系统还有一个命令行实用程序net.exe,它能够控制服务。另一个Windows命令行实用程序是sc.exe,此实用程序具有比net.exe更多的功能。还可以直接从Visual Studio Server Explorer 控制服务。在本节中创建了一个小型Windows应用程序,它使用System.ServiceProcess.ServiceController类来监视和控制服务。
MMC管理单元
MMC的服务管理单元可以查看所有服务的状态(见图39.11)。还可以向服务发送控制请求以停止、启用或禁用它们,以及改变它们的配置。服务管理单元是服务控制程序以及服务配置程序。
图39.11
双击QuoteService以打开属性对话框,如图39.12所示。从这里可以查看服务名称、描述、可执行文件的路径、启动类型和状态。服务当前是启动状态。通过选择此对话框中的“Log On”选项卡,可以更改服务过程的帐户。
图39.12
net.exe实用程序
“服务”管理单元虽然易于使用,但系统管理员无法自动执行,因为它无法使用脚本管理。要使用 自动执行脚本的工具来控制服务,可以使用命令行实用程序net.exe。 “net start” 命令显示所有正在运行的服务,“net start servicename”启动服务,“net stop servicename” 向服务发送停止请求。也可以用“net pause”和“net continue”(如果服务允许的话)暂停和继续服务。
sc.exe实用程序
操作系统操作的另一个鲜为人知的实用程序是sc.exe。这是操作服务的一个很好的工具。使用 sc.exe 比net.exe实用程序可以做更多事情。sc.exe 可以检查服务的实际状态,或配置、删除和添加服务。如果服务无法正常工作,该工具还可以很方便地卸载服务。
Visual Studio服务器资源管理器
在Visual Studio中使用服务器资源管理器监视服务,可以从树视图中选择服务器,然后选择您的计算机,然后选择服务元素。可以看到所有服务的状态,如图39.13所示。通过选择服务,可以看到服务的属性。
图39.13
编写自定义服务控制器
本节中创建一个使用ServiceController类来监视和控制Windows服务的小型WPF应用程序。
使用用户界面创建一个WPF应用程序,如图39.14所示。此应用程序的主窗口有一个列表框去显示所有服务,四个文本框显示服务的显示名称、状态、类型和名称,还有六个按钮:四个按钮用于发送控制事件,一个按钮用于刷新列表,一个按钮用于退出应用程序。
图39.14
注意 您可以在第29到35章中了解有关WPF和XAML的更多信息。
监控服务
ServiceController类可以获取有关每个服务的信息。下表显示了ServiceController类的属性:
属性 |
描述 |
CanPauseAndContinue |
如果可以发送暂停和继续请求到服务,则返回true。 |
CanShutdown |
如果服务有系统关机的处理事件,则返回true。 |
CanStop |
如果服务可停止,则返回true。 |
DependentServices |
返回依赖服务的集合。如果服务要停止,则所有依赖服务必须在该服务停止前停止。 |
ServicesDependentOn |
返回该服务的依赖服务集合。 |
DisplayName |
该服务显示的名称。 |
MachineName |
服务运行所在的计算机的名称 |
ServiceName |
指定服务的名称。 |
ServiceType |
服务的类型。服务可以运行在一个共享进程内,多个服务使用相同的进程(Win32ShareProcess),或在一个进程中只有一个服务的方式(Win32OwnProcess)。如果服务可以在桌面进行交互,类型为InteractiveProcess。 |
Status |
服务的状态,可以是运行、停止、暂停或在某些中间模式,例如开始挂起、停止挂起等。状态值在枚举类ServiceControllerStatus中定义。 |
示例应用程序中使用属性DisplayName,ServiceName,ServiceType和Status来显示服务信息。 CanPauseAndContinue 和 CanStop 用于启用或禁用暂停,继续和停止按钮。
要获取用户交互所需要的信息,可以创建ServiceControllerInfo类。该类可用于数据绑定,并提供状态信息,服务名称、服务类型以及按钮应启用或禁用的服务控制的信息。
注意 由于使用了System.ServiceProcess.ServiceController类,必须引用程序集System.ServiceProcess。
ServiceControllerInfo包含一个嵌入的ServiceController,它使用ServiceControllerInfo类的构造函数设置。还有一个只读属性Controller可以访问嵌入式ServiceController(代码文件
ServiceControlWPF/ServiceControllerInfo.cs):
public class ServiceControllerInfo
{
public ServiceControllerInfo(ServiceController controller)
{
Controller = controller;
}
public ServiceController Controller { get; }
// etc.
}
要显示有关服务的当前信息,ServiceControllerInfo类有只读属性DisplayName,ServiceName,ServiceTypeName和ServiceStatusName。属性DisplayName和ServiceName的实现只访问基础ServiceController类的属性。属性ServiceTypeName和ServiceStatusName的实现需要更多的工作 - 服务的状态和类型不容易返回,因为显示的应该一个字符串而不是一个数字,这是ServiceController类返回的。属性ServiceTypeName返回一个表示服务类型的字符串。从属性ServiceController.ServiceType获取的ServiceType表示一组可以使用按位或运算符组合的标志。 InteractiveProcess位可以与Win32OwnProcess和Win32ShareProcess一起设置。因此,第一次在继续检查其他值之前先确定是否设置InteractiveProcess位。服务返回的字符串将是“Win32 Service Process”或“Win32 Shared Process”(代码文件ServiceControlWPF/ServiceControllerInfo.cs):
public class ServiceControllerInfo
{
// etc.
public string ServiceTypeName
{
get
{
ServiceType type = controller.ServiceType;
string serviceTypeName ="";
if ((type & ServiceType.InteractiveProcess) != 0)
{
serviceTypeName ="Interactive";
type -= ServiceType.InteractiveProcess;
}
switch (type)
{
case ServiceType.Adapter:
serviceTypeName +="Adapter";
break;
case ServiceType.FileSystemDriver:
case ServiceType.KernelDriver:
case ServiceType.RecognizerDriver:
serviceTypeName +="Driver";
break;
case ServiceType.Win32OwnProcess:
serviceTypeName +="Win32 Service Process";
break;
case ServiceType.Win32ShareProcess:
serviceTypeName +="Win32 Shared Process";
break;
default:
serviceTypeName +="unknown type" + type.ToString();
break;
}
return serviceTypeName;
}
}
public string ServiceStatusName
{
get
{
switch (Controller.Status)
{
case ServiceControllerStatus.ContinuePending:
return"Continue Pending";
case ServiceControllerStatus.Paused:
return"Paused";
case ServiceControllerStatus.PausePending:
return"Pause Pending";
case ServiceControllerStatus.StartPending:
return"Start Pending";
case ServiceControllerStatus.Running:
return"Running";
case ServiceControllerStatus.Stopped:
return"Stopped";
case ServiceControllerStatus.StopPending:
return"Stop Pending";
default:
return"Unknown status";
}
}
}
public string DisplayName => Controller.DisplayName;
public string ServiceName => Controller.ServiceName;
// etc.
}
ServiceControllerInfo类具有一些其他的属性,用来启用 启动、停止、暂停和继续按钮:EnableStart,EnableStop,EnablePause和EnableContinue。这些属性根据服务的当前状态返回一个布尔值(代码文件ServiceControlWPF/ServiceControllerInfo.cs):
public class ServiceControllerInfo
{
// etc.
public bool EnableStart => Controller.Status ==
ServiceControllerStatus.Stopped;
public bool EnableStop => Controller.Status ==
ServiceControllerStatus.Running;
public bool EnablePause =>
Controller.Status == ServiceControllerStatus.Running &&
Controller.CanPauseAndContinue;
public bool EnableContinue => Controller.Status ==
ServiceControllerStatus.Paused;
}
ServiceControlWindow 类的方法 RefreshServiceList 获取所有使用ServiceController.GetServices的服务显示到列表框中。 GetServices 方法返回一个ServiceController实例的数组,表示操作系统上安装的所有Windows服务。 ServiceController 类也有静态方法GetDevices,它返回一个表示所有设备驱动程序的ServiceController数组。返回的数组在扩展方法OrderBy的帮助下进行排序。这是按传递给 OrderBy 方法的 lambda 表达式定义的DisplayName 来进行排序的。使用Select语句 将 ServiceController 实例转换为 ServiceControllerInfo 类型。在一句中,传递了一个lambda表达式,为每个ServiceController对象调用ServiceControllerInfo构造函数。最后,将结果分配给用于数据绑定的窗口的DataContext属性(代码文件ServiceControlWPF/MainWindow.xaml.cs):
protected void RefreshServiceList()
{
this.DataContext = ServiceController.GetServices().
OrderBy(sc => sc.DisplayName).
Select(sc => new ServiceControllerInfo(sc));
}
获取列表框中的所有服务的方法 RefreshServiceList 在类 ServiceControlWindow 的构造函数内调用。构造函数还定义了按钮的Click事件:
public ServiceControlWindow()
{
InitializeComponent();
RefreshServiceList();
}
现在可以定义XAML代码将信息绑定到控件。首先,为ListBox中显示的信息定义DataTemplate。 ListBox包含一个Label,其中Content绑定到数据源的DisplayName属性。绑定 ServiceControllerInfo 对象数组时,DisplayName属性在ServiceControllerInfo类定义(代码文件ServiceControlWPF/MainWindow.xaml):
<Window.Resources>
<DataTemplate x:Key="listTemplate">
<Label Content="{Binding DisplayName}"/>
</DataTemplate>
</Window.Resources>
放置在窗口左侧的ListBox将ItemsSource属性设置为{Binding}。这样,列表中显示的数据从在RefreshServiceList方法中设置的DataContext属性中检索。 ItemTemplate属性引用前面所示的DataTemplate 资源定义的listTemplate。属性 IsSynchronizedWithCurrentItem 设置为True,以便在同一窗口内的TextBox和Button控件绑定到使用ListBox选择的当前项:
<ListBox Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left"
Name="listBoxServices" VerticalAlignment="Top"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource listTemplate}"
IsSynchronizedWithCurrentItem="True">
</ListBox>
要用Button控件区分 启动/停止/暂停/继续 服务,定义以下枚举(代码文件ServiceControlWPF/ButtonState.cs):
public enum ButtonState
{
Start,
Stop,
Pause,
Continue
}
TextBlock控件的Text属性绑定到ServiceControllerInfo实例的相应属性。启用或禁用Button控件也通过将IsEnabled属性绑定到ServiceControllerInfo实例的相应属性,对应属性返回一个布尔值。按钮的Tag属性被分配给之前定义的ButtonState枚举类型的值,以便在同一处理事件OnServiceCommand中区分按钮(代码文件ServiceControlWPF/MainWindow.xaml):
<TextBlock Grid.Row="0" Grid.ColumnSpan="2" Text="{Binding /DisplayName, Mode=OneTime}" /> <TextBlock Grid.Row="1" Grid.ColumnSpan="2" Text="{Binding /ServiceStatusName, Mode=OneTime}" /> <TextBlock Grid.Row="2" Grid.ColumnSpan="2" Text="{Binding /ServiceTypeName, Mode=OneTime}" /> <TextBlock Grid.Row="3" Grid.ColumnSpan="2" Text="{Binding /ServiceName, Mode=OneTime}" /> <Button Grid.Row="4" Grid.Column="0" Content="Start" IsEnabled="{Binding /EnableStart, Mode=OneTime}" Tag="{x:Static local:ButtonState.Start}" Click="OnServiceCommand" /> <Button Grid.Row="4" Grid.Column="1" Name="buttonStop" Content="Stop" IsEnabled="{Binding /EnableStop, Mode=OneTime}" Tag="{x:Static local:ButtonState.Stop}" Click="OnServiceCommand" /> <Button Grid.Row="5" Grid.Column="0" Name="buttonPause" Content="Pause" IsEnabled="{Binding /EnablePause, Mode=OneTime}" Tag="{x:Static local:ButtonState.Pause}" Click="OnServiceCommand" /> <Button Grid.Row="5" Grid.Column="1" Name="buttonContinue" Content="Continue" IsEnabled="{Binding /EnableContinue, Tag="{x:Static local:ButtonState.Continue}" Mode=OneTime}" Click="OnServiceCommand" /> <Button Grid.Row="6" Grid.Column="0" Name="buttonRefresh" Content="Refresh" Click="OnRefresh" /> <Button Grid.Row="6" Grid.Column="1" Name="buttonExit" Content="Exit" Click="OnExit" />
控制服务
使用ServiceController类还可以向服务发送控制请求。 下表描述了可以应用的方法。
方法 |
描述 |
Start |
告诉SCM要启动服务。 在示例中服务程序,OnStart被调用。 |
Stop |
示例服务程序中,如果属性CanStop在服务类中为true,在SCM的帮助下调用OnStop方法 |
Pause |
如果属性CanPauseAndContinue 为 true,则调用OnPause 方法。 |
Continue |
如果属性CanPauseAndContinue为true,则调用 OnContinue 方法。 |
ExecuteCommand |
启用向服务发送自定义命令。 |
以下代码控制服务。 因为启动,停止,挂起和暂停的代码是类似的,所以四个按钮可以用同一个处理事件(代码文件ServiceControlWPF/MainWindow.xaml.cs)(译者注:学习时不要“唯书论”,书中的观点不一定全部正确,代码也不一定是最好的,例如以下 判断 currentButtonState 用了四个 if 语句,至少可以优化为 switch 语句):
protected void OnServiceCommand(object sender, RoutedEventArgs e)
{
Cursor oldCursor = this.Cursor;
try
{
this.Cursor = Cursors.Wait;
ButtonState currentButtonState = (ButtonState)(sender as Button).Tag;
var si = listBoxServices.SelectedItem as ServiceControllerInfo;
if (currentButtonState == ButtonState.Start)
{
si.Controller.Start();
si.Controller.WaitForStatus(ServiceControllerStatus.Running,
TimeSpan.FromSeconds(10));
}
else if (currentButtonState == ButtonState.Stop)
{
si.Controller.Stop();
si.Controller.WaitForStatus(ServiceControllerStatus.Stopped,
TimeSpan.FromSeconds(10));
}
else if (currentButtonState == ButtonState.Pause)
{
si.Controller.Pause();
si.Controller.WaitForStatus(ServiceControllerStatus.Paused,
TimeSpan.FromSeconds(10));
}
else if (currentButtonState == ButtonState.Continue)
{
si.Controller.Continue();
si.Controller.WaitForStatus(ServiceControllerStatus.Running,
TimeSpan.FromSeconds(10));
}
int index = listBoxServices.SelectedIndex;
RefreshServiceList();
listBoxServices.SelectedIndex = index;
}
catch (System.ServiceProcess.TimeoutException ex)
{
MessageBox.Show(ex.Message,"Timout Service Controller",
MessageBoxButton.OK, MessageBoxImage.Error);
}
catch (InvalidOperationException ex)
{
MessageBox.Show(String.Format("{0} {1}", ex.Message,
ex.InnerException != null ? ex.InnerException.Message:
String.Empty), MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
this.Cursor = oldCursor;
}
}
protected void OnExit(object sender, RoutedEventArgs e) =>
Application.Current.Shutdown();
protected void OnRefresh_Click(object sender, RoutedEventArgs e) =>
RefreshServiceList();
因为控制服务的操作可能需要一些时间,所以在第一条语句中将光标切换到等待状态。然后根据按下的按钮调用ServiceController方法。WaitForStatus方法表示正在等待确认服务将状态更改为请求的值,但等待最长时间仅为10秒。之后将刷新列表框中的信息,并将所选索引设置为与之前相同的值。然后显示该服务的最新状态。
由于应用程序需要管理权限,正如大多数服务需要权限去启动和停止,将设置为 requireAdministrator 的requestedExecutionLevel的应用程序清单添加到项目(应用程序清单文件ServiceControlWPF/app.manifest)中:
<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly manifestVersion="1.0"
xmlns:asmv1="urn:schemas-microsoft-com:asm.v1"
xmlns:asmv2="urn:schemas-microsoft-com:asm.v2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="requireAdministrator"
uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
</asmv1:assembly>
图39.15显示了已完成及正在运行的应用程序。
图39.15
故障排除和事件日志
故障排除服务与对其他类型的应用程序进行故障排除不同。本部分涉及一些服务问题,交互式服务特有的问题和事件日志。
开始构建服务的最佳方法是在实际创建服务之前创建具有所需功能和测试客户端的程序集。这里可以做正常的调试和错误处理。只要应用程序正在运行,就可以使用该程序集来创建服务。当然,服务可能还有问题:
- 不要在服务(在客户端系统上运行的交互式服务除外)的消息框中显示错误。而应使用事件日志记录服务将错误写入事件日志。当然,在使用该服务的客户端应用程序中,可以在消息框显示以通知用户有关错误。
- 从调试器中无法启动服务,但可以将调试器附加到正在运行的服务进程。打开服务源代码的解决方案并设置断点。在 Visual Studio的“Debug”菜单中,选择进程并附加服务的运行进程。
- 性能监视器可用于监视服务的活动,还可以将自定义的性能对象添加到服务。还可以添加一些有用的信息进行调试。例如,使用Quote服务,可以设置一个对象,以提供返回的引用总数,初始化所需的时间等。
服务可以通过向事件日志中添加事件来报告错误和其他信息。当AutoLog属性设置为true时,从ServiceBase派生的服务类自动记录事件。 ServiceBase类检查此属性,并在开始,停止,暂停和继续请求时写入日志条目。
图39.16显示了示例来自服务的日志。
图39.16
注意 有关事件日志以及如何编写自定义事件的详细信息,请阅读第20章“诊断和应用程序分析”。
总结
本章中了解了Windows服务的架构以及如何使用.NET Framework创建服务。Windows 服务应用程序可以在开机时自动启动,可以使用特权系统帐户作为服务的用户。 Windows服务是从主函数,主服务函数和处理事件创建的。还查看了有关Windows服务的其他相关程序,例如服务控制程序和服务安装程序。
.NET Framework对Windows服务有很大的支持。创建,控制和安装服务所需的所有管道代码都内置到System .ServiceProcess命名空间中的.NET Framework类中。通过从ServiceBase派生类,可以重写服务在暂停、恢复或停止时调用的方法。对于服务的安装,ServiceProcessInstaller和ServiceInstaller类处理服务所需的所有注册表配置。还可以使用ServiceController控制和监视服务。
在下一章中,可以了解有关ASP.NET Core 1.0的技术,它利用Web服务器,且通常运行在Windows服务上(如果服务器使用Windows操作系统)。
(本章完)