程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> C#發現之旅:C#開發Windows Service程序(下)

C#發現之旅:C#開發Windows Service程序(下)

編輯:關於C#

類似的對於“停止服務”,其點擊事件處理為

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框架是 可以低成本的開發出一些功能強大的軟件。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved