首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 開發 Windows Service 實戰 (上):服務架構設計、FileSystemWatcher 監視核心、單一 EXE 多模式啟動器

     編輯:關於C#

Windows Service (Windows 服務) 是長期在後台運行、無使用者介面、支援開機自動啟動的系統級程式。.NET System.ServiceProcess 命名空間完整封裝了 SCM (Service Control Manager) 介面,讓 C# 開發者能以極低成本構建健壯的後台服務。本文從 **服務概念**、**開發基本流程**、**專案架構設計**、**命令列參數路由設計**、**資料庫結構**、**FileSystemWatcher 監視邏輯**、**單一 EXE 多模式啟動器** 完整鋪陳,為下集客戶端控制與部署奠基。

Windows Service 核心概念

屬性說明
服務名稱唯一識別碼,net start/stop <名稱> 操作依據
啟動類型自動 (開機啟動)、手動、已停用
登入身分LocalSystem (預設, 高權限, 無網路)、NetworkService、LocalService、指定使用者帳號
桌面互動勾選「允許服務與桌面互動」可顯示 UI,**強烈不建議** (Session 0 隔離、安全風險)
依賴關係啟動前會自動啟動相依服務 (如 IIS 依賴 RPC)

C# 開發服務標準流程 (5 步驟)

  1. 建立專案:Console App 或 WinForm (單一 EXE 多模式) → 參考 System.ServiceProcess.dll, System.Configuration.Install.dll。
  2. 服務類別:繼承 ServiceBase,設 ServiceName,覆寫 OnStart/OnStop/OnPause/OnContinue。**方法必須立即返回**,耗時工作另開執行緒。
  3. 安裝類別:繼承 Installer,標記 [RunInstaller(true)],建構式加入 ServiceProcessInstaller (帳號) + ServiceInstaller (名稱/啟動類型/描述)。
  4. 啟動點:Main() 呼叫 ServiceBase.Run(new MyService())。
  5. 安裝部署:InstallUtil.exe MyService.exe 註冊至 SCM;InstallUtil.exe /u MyService.exe 移除。

本專案功能需求

  • 監視指定目錄 (支援通配符) 的檔案/子目錄 **建立、修改、刪除、重命名** 操作。
  • 記錄寫入本地 Access 資料庫 (FileSystemWatcher.mdb)。
  • 服務模式運行,支援多用戶帳號操作審計。
  • WinForm 客戶端:查看日誌、啟動/停止服務、設定監視路徑與事件類型。

命令列參數設計:單一 EXE 多模式

參數模式行為
(無)服務ServiceBase.Run 進入 SCM 管控
/install安裝啟動 InstallUtil.exe 註冊自身
/uninstall解除安裝啟動 InstallUtil.exe /u 移除註冊
/client客戶端啟動 WinForm UI (Application.Run)
/debug除錯直接實例化服務類別呼叫 StartFileSystemWatching,主執行緒 Sleep 方便 VS 斷點

資料庫設計 (Access 2000 / Jet 4.0)

FileSystemLog - 操作日誌

RecordID   Text(50)  PK  (GUID)
WatchTime  Text(20)      yyyy-MM-dd HH:mm:ss
ObjectName Text(250)     完整路徑
EventStyle Text(50)      Created / Changed / Deleted / Renamed

SystemConfig - 系統設定

ConfigName  Text(50)  PK
ConfigValue Text(250)
-- 範例資料
LogChanged  True
LogCreated  True
LogDeleted  True
LogRenamed  False
path0       C:\Data\*.txt
path1       D:\

FileSystemWatcher 監視邏輯設計

  • 啟動時讀取 SystemConfig → MyConfig.Instance。
  • 遍歷 WatchedPaths,解析目錄與檔名過濾器:
    • C:\Data\*.txt → Path=C:\Data, Filter=*.txt
    • D:\ → Path=D:\, Filter=*.*
  • 排除:非絕對路徑、不存在目錄、**程式自身目錄** (防遞迴觸發)。
  • 每個路徑建立一個 FileSystemWatcher,設 EnableRaisingEvents=true、IncludeSubdirectories=false (需遞迴改 true)。
  • 依 MyConfig 旗標動態綁定 Changed/Created/Deleted/Renamed 事件 → 統一呼叫 WriteLog 寫入資料庫。
  • 使用 **匿名委託 (C# 2.0)** 簡化事件處理器。

客戶端主視窗 (frmClient) 核心程式碼

載入檢查服務安裝狀態


private bool _serviceInstalled;
private void frmClient_Load(object sender, EventArgs e)
{
    try
    {
        using (var sc = new ServiceController("MyFileSystemWatcher"))
        {
            var _ = sc.Status; // 嘗試讀取狀態,若服務未安裝會拋例外
            _serviceInstalled = true;
        }
    }
    catch
    {
        _serviceInstalled = false;
        lblServiceStatus.Text = "服務尚未安裝";
        btnStart.Enabled = btnStop.Enabled = false;
        timer.Enabled = false;
        MessageBox.Show(this, "服務尚未安裝,請先以 /install 安裝。");
    }
    btnRefresh_Click(null, null);
}

重新整理日誌列表 (ListView 分組)


private void btnRefresh_Click(object sender, EventArgs e)
{
    Cursor = Cursors.WaitCursor;
    lvwLog.BeginUpdate();
    try
    {
        lvwLog.Items.Clear();
        using (var cmd = Util.DBConnection.CreateCommand())
        {
            cmd.CommandText = "SELECT RecordID, ObjectName, WatchTime, EventStyle FROM FileSystemLog ORDER BY WatchTime";
            using (var reader = cmd.ExecuteReader())
            {
                while (reader.Read())
                {
                    var item = new ListViewItem(reader.GetString(1)) // ObjectName
                    {
                        Tag = reader.GetString(0) // RecordID
                    };
                    item.SubItems.Add(reader.GetString(2)); // WatchTime
                    string style = reader.GetString(3);     // EventStyle
                    item.SubItems.Add(style);

                    // 分組與圖示
                    style = style.ToLower();
                    if (style == "created") { item.Group = lvwLog.Groups[0]; item.ImageIndex = 0; }
                    else if (style == "changed") { item.Group = lvwLog.Groups[1]; item.ImageIndex = 1; }
                    else if (style == "deleted") { item.Group = lvwLog.Groups[2]; item.ImageIndex = 2; }
                    else if (style == "renamed") { item.Group = lvwLog.Groups[3]; item.ImageIndex = 3; }
                    item.StateImageIndex = item.ImageIndex;
                    lvwLog.Items.Add(item);
                }
            }
        }
        lblStatus.Text = $"共列出 {lvwLog.Items.Count} 筆記錄";
    }
    catch (Exception ex) { MessageBox.Show(ex.ToString(), "錯誤"); }
    finally { Cursor = Cursors.Default; lvwLog.EndUpdate(); }
}

刪除選取記錄


private void btnDelete_Click(object sender, EventArgs e)
{
    if (lvwLog.SelectedItems.Count == 0) return;
    using (var cmd = Util.DBConnection.CreateCommand())
    {
        for (int i = lvwLog.Items.Count - 1; i >= 0; i--)
        {
            var item = lvwLog.Items[i];
            if (item.Selected)
            {
                cmd.CommandText = $"DELETE FROM FileSystemLog WHERE RecordID = '{item.Tag}'";
                cmd.ExecuteNonQuery();
                lvwLog.Items.Remove(item);
            }
        }
    }
}

Program.cs 完整啟動路由


[STAThread]
static void Main(string[] args)
{
    try
    {
        // 定位 InstallUtil.exe (在 .NET Framework 目錄下,與 mscorlib.dll 同層)
        string runtimePath = Path.GetDirectoryName(typeof(string).Assembly.CodeBase.Replace("file:///", ""));
        string installUtil = Path.Combine(runtimePath, "InstallUtil.exe");

        foreach (string arg in args)
        {
            switch (arg.ToLower())
            {
                case "/install":
                    Process.Start(installUtil, $"\"{Application.ExecutablePath}\"");
                    return;
                case "/uninstall":
                    Process.Start(installUtil, $"/u \"{Application.ExecutablePath}\"");
                    return;
                case "/client":
                    Application.EnableVisualStyles();
                    Application.SetCompatibleTextRenderingDefault(false);
                    using (var frm = new frmClient())
                    {
                        Application.Run(frm);
                        Util.CloseDBConnection();
                    }
                    return;
                case "/debug":
                    var svc = new MyFileSystemWatcherService();
                    svc.StartFileSystemWatching();
                    Thread.Sleep(Timeout.Infinite); // 阻塞,保持服務邏輯在前景執行
                    return;
            }
        }
    }
    catch (Exception ex) { Console.WriteLine(ex); return; }

    // 無參數 → 服務模式
    ServiceBase.Run(new MyFileSystemWatcherService());
}

安裝類別 (ProjectInstaller.cs)


[RunInstaller(true)]
public partial class ProjectInstaller : Installer
{
    public ProjectInstaller()
    {
        InitializeComponent();
        // ServiceProcessInstaller: 帳號設定
        var spi = new ServiceProcessInstaller { Account = ServiceAccount.LocalSystem };
        // ServiceInstaller: 服務屬性
        var si = new ServiceInstaller
        {
            ServiceName = "MyFileSystemWatcher",
            DisplayName = "檔案系統監視服務",
            Description = "監視指定目錄檔案變更並記錄至資料庫",
            StartType = ServiceStartMode.Manual // 或 Automatic
        };
        Installers.AddRange(new Installer[] { spi, si });
    }
}

小結

上集完成了:

  1. **服務架構設計**:ServiceBase 衍生、Installer 註冊、SCM 概念釐清。
  2. **單一 EXE 多模式**:命令列參數路由服務/安裝/客戶端/除錯,InstallUtil 自動定位免硬編碼路徑。
  3. **資料庫結構**:Access MDB 兩張表 (FileSystemLog, SystemConfig)。
  4. **監視核心邏輯**:FileSystemWatcher 多實例、路徑解析、配置驅動事件綁定、匿名委託。
  5. **客戶端雛形**:服務安裝檢查、ListView 分組顯示日誌、刪除記錄。

下集將完善:系統設定對話框、MyConfig 單例持久化、服務生命週期完整實現、部署指令與常見問題排查。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved