譯文,個人原創,轉載請注明出處(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操作系統)。
(本章完)