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

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

編輯:關於C#

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方法內部只是通知操作系統啟動指定 名稱的服務,它發送通知後立即返回,並不會等待服務啟動後返回。

本文配套源碼

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