C#發現之旅 --- C#開發Windows Service程序
C#發現之旅 C#開發Windows Service程序
本課程介紹
本課程說明了Windows Service程序的概念,并演示如何使用C#開發一個簡單的Windows Service程序。C#工程名為MyWindowsService,編譯生成的文件是MyWindowsService.exe。本課程的演示代碼下載地址為https://files.cnblogs.com/xdesigner/MyWindowsService.zip。
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資源管理器,在左邊的樹狀列表中選中“桌面-控制面板-管理工具”。

在右邊的列表中打開“服務”項目即可打開Windows服務管理器。
在這些服務中,有我們最熟悉的IIS Admin和World Wide Web Publishing服務了。我們雙擊一個服務項目即可打開服務屬性對話框。

Windows服務有一個服務名稱屬性,該屬性是服務的惟一的不可重復的名稱,我們可以在命令行中使用命令“net start 服務名稱”來啟動服務,使用“net stop 服務名稱”來停止服務。
Windows服務的啟動類型有自動,手動和已禁用。當啟動類型為自動時,Windows啟動后不等用戶登錄就自動啟動服務,當啟動類型為手動時,需要某個操作員登錄后點擊這里的“啟動”按鈕來啟動服務,而當啟動類型為已禁用時,Windows服務不能啟動。
該頁面中的“啟動”按鈕用于啟動尚未啟動的Windows服務,運行提供服務的進程;“停止”按鈕用于停止已經啟動的服務,殺死服務進程;而“暫停”按鈕用于通知服務進程暫時停止提供服務,但服務進程依然存在;而“恢復”按鈕用于通知處于暫停模式的服務進程重新提供服務。
我們可以查看服務屬性對話框的“登錄”頁面。

可以指定服務使用本地系統帳戶登錄,也可另外指定其他的用戶,這里有一個允許服務和桌面交互的選項,若選中此選項,則Windows服務可以顯示圖形化用戶界面,比如顯示自己的窗體,顯示消息框等等。不過不建議使用該選項,而且Windows服務運行時不要顯示圖形化用戶界面。
我們切換到“依存關系”頁面,可以看到本服務和其他服務的依存關系。

各個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#工程開發,但這里為了方便我們只在一個工程中實現服務器和客戶端軟件的開發。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,主要包含服務端軟件模塊和客戶端軟件模塊。首先對比較好理解的具有圖形化用戶界面的客戶端模塊進行說明,客戶端的主界面為

這個界面主要功能是數據庫信息管理,最上面為一個工具條,中間大部分的一個ListView控件,最下面為狀態欄。
對于ListView控件其內容是分組的,因此需要設計其分組信息,在VS.NET的窗體設計器中我們點中ListView控件,在旁邊的屬性列表中選擇Groups屬性,點擊旁邊的小按鈕可以彈出分組設計器。

使用這個分組編輯器我們可以很容易的設計該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(); } |
在該按鈕事件處理中,我們查詢數據表FileSystemLog,對每一條查詢所得的數據創建一個ListViewItem項目,并根據記錄的EventStyle值設置該列表項目的圖標序號和分組狀態。
工具條的“刪除記錄”按鈕用于刪除列表中選擇的項目,其點擊事件處理為
|
private void btnDelete_Click(object sender, EventArgs e) { if (lvwRecord.SelectedItems.Count > 0) { using (System.Data.IDbCommand cmd = Util.DBConnection.CreateCommand()) { for (int iCount = lvwRecord.Items.Count - 1; iCount >= 0; iCount--) { ListViewItem item = lvwRecord.Items[iCount]; if (item.Selected) { cmd.CommandText = "Delete From FileSystemLog Where RecordID = '" + item.Tag + "'"; cmd.ExecuteNonQuery(); lvwRecord.Items.Remove(item); } }//for }//using } } |
在刷新列表中,我們將列表項目的Tag屬性值設置為數據庫記錄的編號,在這里我們利用這個事先保存的數據庫記錄的編號拼湊出SQL語句然后刪除指定的記錄。
工具條的“啟動服務”按鈕用于啟動后臺的文件監視服務。其點擊事件處理為
|
private void btnStartService_Click(object sender, EventArgs e) { if (bolServiceInstalled == false) return; using (System.ServiceProcess.ServiceController control = new System.ServiceProcess.ServiceController("MyFileSystemWatcher")) { if (control.Status == System.ServiceProcess.ServiceControllerStatus.Stopped) { control.Start(); } } } |
在這里我們創建一個ServiceController對象,若判斷出服務的狀態為停止,則調用控制器的Start方法來啟動服務,在這里Start方法內部只是通知操作系統啟動指定名稱的服務,它發送通知后立即返回,并不會等待服務啟動后返回。
類似的對于“停止服務”,其點擊事件處理為
|
private void btnStopService_Click(object sender, EventArgs e) { if (bolServiceInstalled == false) return; using (System.ServiceProcess.ServiceController control = new System.ServiceProcess.ServiceController("MyFileSystemWatcher")) { if (control.Status == System.ServiceProcess.ServiceControllerStatus.Running) { control.Stop(); } } } |
在這個處理過程中,若判斷出服務狀態為運行中,則調用控制器的Stop方法來停止服務。在這里Stop方法內部只是通知操作系統停止指定的服務,它發送通知后立即返回,不會等待服務停止后返回。
我們還在窗體上放置一個定時器控件,定時間隔為2秒,用于根據服務的狀態刷新工具條按鈕狀態,其定時事件處理為
|
private void myTimer_Tick(object sender, EventArgs e) { if (bolServiceInstalled == false) return; using (System.ServiceProcess.ServiceController control = new System.ServiceProcess.ServiceController("MyFileSystemWatcher")) { btnStartService.Enabled = (control.Status == System.ServiceProcess.ServiceControllerStatus.Stopped); btnStopService.Enabled = (control.Status == System.ServiceProcess.ServiceControllerStatus.Running); } } |
在這里我們創建了一個綁定到文件系統監控服務的ServiceController對象,然后根據它的Status狀態來設置“啟動服務”和“停止服務”按鈕的可用狀態。
系統配置對話框 dlgConfig
在客戶端主窗體中點擊工具條的“系統配置”按鈕就會彈出系統設置對話框,該對話框的用戶界面為
該對話框比較簡單,就是用于顯示和修改系統配置信息對象MyConfig中的內容。由于文件系統監視服務只有在啟動的時候讀取系統配置信息,因此對系統配置的任何修改都需要重新啟動服務才能生效。
系統配置信息對象 MyConfig
系統配置信息對象MyConfig用于讀取和修改保存在數據表SystemConfig中的系統配置信息。其包含的配置信息的代碼如下
|
private bool bolLogRenamed = true; /// <summary> /// 是否記錄重命名事件 /// </summary> public bool LogRenamed { get { return bolLogRenamed; } set { bolLogRenamed = value; } }
private bool bolLogChanged = true; /// <summary> /// 是否記錄文件修改事件 /// </summary> public bool LogChanged { get { return bolLogChanged; } set { bolLogChanged = value; } } private bool bolLogCreated = true; /// <summary> /// 是否記錄對象創建事件 /// </summary> public bool LogCreated { get { return bolLogCreated; } set { bolLogCreated = value; } } private bool bolLogDeleted = true; /// <summary> /// 是否記錄對象刪除事件 /// </summary> public bool LogDeleted { get { return bolLogDeleted; } set { bolLogDeleted = value; } }
private string[] myWatchedPaths = null; /// <summary> /// 監視的目錄 /// </summary> public string[] WatchedPaths { get { return myWatchedPaths; } set { myWatchedPaths = value; } } |
它的Load方法用于從數據庫中加載配置信息,其處理過程為
|
public void Load() { myWatchedPaths = null; System.Collections.ArrayList paths = new System.Collections.ArrayList(); using (System.Data.IDbCommand cmd = Util.DBConnection.CreateCommand()) { cmd.CommandText = "Select ConfigName , ConfigValue From SystemConfig"; System.Data.IDataReader reader = cmd.ExecuteReader(); while (reader.Read()) { string Name = Convert.ToString(reader.GetValue(0)); if (Name == null) { continue; } Name = Name.Trim().ToLower(); string Value = Convert.ToString(reader.GetValue(1)); if (Name.StartsWith("path")) { paths.Add(Value.Trim()); } else if (Name == "logrenamed") { bolLogRenamed = Convert.ToBoolean(Value); } else if (Name == "logchanged") { bolLogChanged = Convert.ToBoolean(Value); } else if (Name == "logdeleted") { bolLogDeleted = Convert.ToBoolean(Value); } else if (Name == "logcreated") { bolLogCreated = Convert.ToBoolean(Value); } } } myWatchedPaths = (string[])paths.ToArray(typeof(string)); } |
在該方法中程序查詢數據表SystemConfig中的配置項目名稱和數據,若項目名稱以“path”開頭則為要監視的路徑,而配置項logrenamed,logchanged,logdeleted,logcreated分別表示是否監視文件目錄重命名,修改,刪除和新建等操作。
MyConfig對象還有一個Save方法用于將系統配置信息保存到數據庫中,其處理過程為
|
public void Save() { using (System.Data.IDbCommand cmd = Util.DBConnection.CreateCommand()) { cmd.CommandText = "Delete From SystemConfig"; cmd.ExecuteNonQuery(); cmd.CommandText = "Insert Into SystemConfig ( ConfigName , ConfigValue ) Values( ? , ? )" ; System.Data.IDbDataParameter pName = cmd.CreateParameter(); cmd.Parameters.Add( pName ); System.Data.IDbDataParameter pValue = cmd.CreateParameter(); cmd.Parameters.Add( pValue );
pName.Value = "LogRenamed"; pValue.Value = bolLogRenamed.ToString(); cmd.ExecuteNonQuery();
pName.Value = "LogChanged"; pValue.Value = bolLogChanged.ToString(); cmd.ExecuteNonQuery();
pName.Value = "LogDeleted"; pValue.Value = bolLogDeleted.ToString(); cmd.ExecuteNonQuery();
pName.Value = "LogCreated"; pValue.Value = bolLogCreated.ToString(); cmd.ExecuteNonQuery();
for (int iCount = 0; iCount < myWatchedPaths.Length; iCount++) { string path = myWatchedPaths[ iCount ] ; if( path == null || path.Trim().Length == 0 ) { continue ; } pName.Value = "path" + iCount ; pValue.Value = path ; cmd.ExecuteNonQuery(); } } } |
在這個方法中,首先刪除數據表SystemConfig中所有的記錄,然后將所有的配置信息保存到數據表SystemConfig中。
文件系統監視服務 MyFileSystemWatcherService
類MyFileSystemWatcherService就是文件系統監視服務,它是從ServiceBase派生的,首先說明一下執行文件系統監視的功能性的過程,其代碼如下
|
/// <summary> /// 文件系統監視器列表 /// </summary> private System.Collections.ArrayList myWatchers = null;
/// <summary> /// 開始啟動文件系統監視 /// </summary> /// <returns>操作是否成功</returns> internal bool StartFileSystemWatching() { myWatchers = new System.Collections.ArrayList(); MyConfig.Instance.Load(); string[] paths = MyConfig.Instance.WatchedPaths; System.Text.StringBuilder myPathList = new StringBuilder(); if (paths != null) { foreach (string path in paths) { if (System.IO.Path.IsPathRooted(path) == false) { continue; } string BasePath = null; string Filter = null;
if (System.IO.Directory.Exists(path)) { BasePath = path; Filter = "*.*"; } else { BasePath = System.IO.Path.GetDirectoryName(path); Filter = System.IO.Path.GetFileName(path); } if (BasePath == null) { continue; } BasePath = BasePath.Trim(); if (BasePath.ToUpper().StartsWith(System.Windows.Forms.Application.StartupPath)) { // 不能監視程序本身所在的目錄的文件系統更改 continue; }
if (System.IO.Directory.Exists(BasePath) == false) { // 不能監視不存在的目錄 continue; } if (myPathList.Length > 0) { myPathList.Append(";"); } myPathList.Append(path); System.IO.FileSystemWatcher watcher = new System.IO.FileSystemWatcher(); watcher.Path = BasePath; watcher.Filter = Filter; watcher.EnableRaisingEvents = true; watcher.IncludeSubdirectories = false; if (MyConfig.Instance.LogChanged) { watcher.Changed += delegate(object sender, System.IO.FileSystemEventArgs args) { WriteFileSystemLog(args.FullPath, args.ChangeType.ToString()); }; } if (MyConfig.Instance.LogCreated) { watcher.Created += delegate(object sender, System.IO.FileSystemEventArgs args) { WriteFileSystemLog(args.FullPath, args.ChangeType.ToString()); }; } if (MyConfig.Instance.LogDeleted) { watcher.Deleted += delegate(object sender, System.IO.FileSystemEventArgs args) { WriteFileSystemLog(args.FullPath, args.ChangeType.ToString()); }; } if (MyConfig.Instance.LogRenamed) { watcher.Renamed += delegate(object sender, System.IO.RenamedEventArgs args) { WriteFileSystemLog(args.FullPath, args.ChangeType.ToString()); }; } myWatchers.Add(watcher); }//foreach this.EventLog.WriteEntry( "開始監視文件系統 " + myPathList.ToString(), EventLogEntryType.Information); }//if return true; } |
在這個過程中,首先使用MyConfig.Load從數據庫中加載系統配置,然后遍歷所有需要監視的路徑,對其中的每個路徑解析出目錄名和文件名,然后創建一個FileSystemWatcher對象,設置其Path和Filter屬性,還根據MyConfig中的系統配置來綁定監視對象的Changed事件,Created事件,Deleted事件和Renamed事件,以實現對文件系統的監視。這里綁定事件的代碼使用了C#2.0的匿名委托的語法功能。設置FileSystemWatcher對象后將該對象添加到文件系統監視器列表myWatchers中。
啟動服務后使用EventLog.WriteEntry向Windows系統事件日志添加一些日志信息。
這里使用了一個WriteFileSystemLog方法,該方法代碼為
|
private void WriteFileSystemLog(string ObjectName, string EventStyle ) { System.Data.IDbConnection conn = Util.DBConnection; if (conn == null) return; // 將監視結果添加到數據庫中 using (System.Data.IDbCommand cmd = conn.CreateCommand()) { cmd.CommandText = "Insert Into FileSystemLog ( RecordID , WatchTime , ObjectName , EventStyle ) Values ( '" + System.Guid.NewGuid().ToString() + "' , '" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "' , ? , '" + EventStyle + "')" ; System.Data.IDbDataParameter p = cmd.CreateParameter(); p.Value = ObjectName; cmd.Parameters.Add(p); cmd.ExecuteNonQuery(); } } |
該方法參數是記錄的文件或目錄名,以及事件類型,程序首先拼湊出一個Insert的SQL語句,然后向數據表FileSystemLog添加一條數據。
類型MyFileSystemWatcherService還重載了ServiceBase的OnStart,OnStop,OnPause,OnContinue等方法來響應外界對服務過程的控制。
OnStart方法的代碼如下,該方法調用StartFileSystemWatching函數就算完成了啟動服務的操作。
|
protected override void OnStart(string[] args) { this.StartFileSystemWatching(); } |
OnStop方法的代碼如下,該方法首先銷毀掉所有正在運行的文件系統監視器,然后關閉數據庫連接。
|
protected override void OnStop() { if (myWatchers != null) { foreach (System.IO.FileSystemWatcher w in myWatchers) { w.EnableRaisingEvents = false; w.Dispose(); } myWatchers = null; } Util.CloseDBConnection(); base.OnStop(); } |
OnPause方法代碼如下,該方法設置所有的文件系統監視器不觸發事件,這樣軟件不能感知文件系統的修改,因此也就暫停了對文件系統的監視。
|
protected override void OnPause() { if (myWatchers != null) { foreach (System.IO.FileSystemWatcher w in myWatchers) { w.EnableRaisingEvents = false; } } base.OnPause(); } |
OnContinue方法的代碼如下,該方法重新設置所有的文件系統監視器能觸發事件,因此軟件又能監視文件系統的修改了。
|
protected override void OnContinue() { if (myWatchers != null) { foreach (System.IO.FileSystemWatcher w in myWatchers) { w.EnableRaisingEvents = true ; } } base.OnContinue(); } |
管理數據庫連接
類型Util用于管理數據庫連接,其代碼為
|
private static System.Data.IDbConnection myDBConnection = null; /// <summary> /// 獲得數據庫連接對象 /// </summary> public static System.Data.IDbConnection DBConnection { get { if (myDBConnection == null) { myDBConnection = new System.Data.OleDb.OleDbConnection( "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=""" + System.IO.Path.Combine( System.Windows.Forms.Application.StartupPath, "FileSystemWatcher.mdb") + """"); myDBConnection.Open(); } return myDBConnection; } } /// <summary> /// 關閉數據庫連接 /// </summary> public static void CloseDBConnection() { if (myDBConnection != null) { myDBConnection.Close(); myDBConnection = null; } } |
從這個代碼可以看出軟件使用的數據庫是應用程序目錄下的FileSystemWatcher.mdb數據庫。為了提高效率,減少數據庫的連接次數,服務在運行其間只連接一次數據庫,使用完畢后不斷開,只有退出軟件時才斷開數據庫連接。
啟動程序
在類型Program中定義了Main函數,該函數就是本軟件的啟動入口方法。其代碼為
|
[System.STAThread()] static void Main() { try { System.Uri uri = new Uri(typeof(string).Assembly.CodeBase); string RuntimePath = System.IO.Path.GetDirectoryName( uri.LocalPath ) ; string strInstallUtilPath = System.IO.Path.Combine(RuntimePath, "InstallUtil.exe"); foreach (string arg in System.Environment.GetCommandLineArgs()) { Console.WriteLine(arg); if (arg == "/install") { System.Diagnostics.Process.Start(strInstallUtilPath, """" + System.Windows.Forms.Application.ExecutablePath + """"); return; } else if (arg == "/uninstall") { System.Diagnostics.Process.Start(strInstallUtilPath, "/u """ + System.Windows.Forms.Application.ExecutablePath + """"); return; } else if (arg == "/client") { // 啟動客戶端 Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false);
using (frmClient frm = new frmClient()) { Application.Run(frm); //frm.ShowDialog(); Util.CloseDBConnection(); } return; } else if (arg == "/debug") { MyFileSystemWatcherService service = new MyFileSystemWatcherService(); service.StartFileSystemWatching(); System.Threading.Thread.Sleep(1000 * 600); return; } } } catch (Exception ext) { Console.WriteLine(ext.ToString()); return; } // 運行服務對象 ServiceBase.Run( new MyFileSystemWatcherService()); } |
Main函數決定調用本軟件的那個功能模塊,由于Main函數本身具有安裝和卸載服務的功能,首先得找到微軟.NET框架所帶的InstallUtil.exe的完整的路徑。微軟.NET編程中,基礎類型string屬于mscorlib.dll,因此可以使用typeof(string).Assembly.CodeBase獲得文件mscorlib.dll的絕對路徑名,而InstallUtil.exe和mscorlib.dll是同一個目錄的,因此也就能獲得InstallUtil.exe的絕對路徑名了。
我們使用System.Environment.GetCommandLineArgs()獲得所有的命令行參數。遍歷所有的參數,若存在“/install”則表示要安裝服務,于是調用InstallUtil.exe來將軟件本身注冊為服務,若遇到“/uninstall”則調用InstallUtil.exe卸載服務,若遇到“/client”則調用客戶端模塊,若遇到“/debug”則創建服務對象,調用它的StartFileSystemWatching模擬啟動服務,然后主線程阻塞掉,但此時文件系統監視的功能性模塊還在運行,可以設置斷點進行調試。
若沒有遇到任何可識別的命令行參數,則調用ServiceBase.Run函數來執行服務。
由于向Windows系統注冊自己為服務時沒有指明任何命令行參數,因此服務管理器啟動進程時不會添加任何命令行參數,因此本程序也就是以服務模式運行。若在Windows資源管理器中雙擊執行程序時也是以服務模式運行,此時沒有相關的運行環境,程序啟動后會報錯。此時必須添加程序代碼可識別的命令行參數。
運行軟件
程序編寫完畢,編譯通過,生成一個MyWindowsService.exe文件,我們就可以開始運行這個軟件了。
首先我們得向系統注冊服務,我們可以使用命令行“程序路徑/MyWindowsService.exe /install”來注冊服務,也可以直接運行“微軟.NET框架路徑/installutil.exe 程序路徑/MyWindowsService.exe”;相反的,我們可以使用命令行“程序路徑/MyWindowsService.exe /uninstall”或者“微軟.NET框架路徑/installutil.exe /u 程序路徑/MyWindowsService.exe”來卸載服務。
安裝服務后,我們可以使用命令行“程序路徑/MyWindowsService.exe /client”來運行該服務的客戶端軟件了。
小結
在本課程中,我們使用C#編寫了一個簡單的用于監視文件系統的Windows服務,包括服務器軟件和客戶端軟件,若使用傳統的C++開發服務這種底層程序需要熟悉大量的API函數,而微軟.NET框架很好的封裝了這些技術細節,簡化了編程過程,使得我們可以把主要警力放在提供服務內容的功能性模塊的開發上來,從這里可以看出基于微軟.NET框架是可以低成本的開發出一些功能強大的軟件。
posted on 2009-04-06 15:56 袁永福 電子病歷,醫療信息化 閱讀(20936) 評論(22) 收藏 舉報
本課程說明了Windows Service程序的概念,并演示如何使用C#開發一個簡單的Windows Service程序。C#工程名為MyWindowsService,編譯生成的文件是MyWindowsService.exe。本課程的演示代碼下載地址為http://files.cnblogs.com/xdesigner/MyWindowsService.zip。
浙公網安備 33010602011771號