類似的對於“停止服務”,其點擊事件處理為
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框架是 可以低成本的開發出一些功能強大的軟件。