編寫 Window 服務程序

編寫  Window  服務程序
 
 

1、直觀認識Windows服務。

       打開Windows「控制面板/管理工具/服務」,系統顯示Windows服務列表。
       
 
 
       雙擊服務,能夠顯示和更改服務屬性。在這個對話框中,能夠控制服務的啓動、暫停和中止。在這裏還能夠配置服務的啓動類型,令服務在系統啓動時自行啓動。所以,Windows服務常常做爲服務器程序運行。
       
 
       在故障恢復這個屬性頁,能夠配置該服務失敗後系統的相應。一些病毒程序就是在這裏作文章,將病毒程序激活的。
       
 
 

2、Windows服務的開發要點

        Visual Studio的隨機文檔裏,詳細介紹了Windows服務程序的開發步驟,而且帶有實例,筆者再也不贅述。讀者只需注意幾個要點:
        一、建立一個派生自ServiceBase的入口類。這個入口類管理這個Windows服務的生存期。
 
     public class MyService : System.ServiceProcess.ServiceBase
     {
         ……
     }
 
        二、在入口類的main方法裏將服務向Windows的服務控制器(Service Control Manager, SCM)註冊,代碼:
 
       ……
         System.ServiceProcess.ServiceBase[] ServicesToRun;
         ServicesToRun = new System.ServiceProcess.ServiceBase[] { new MyService() };
         System.ServiceProcess.ServiceBase.Run(ServicesToRun);
         ……
 
        三、重寫  OnStart 、 OnStop ,或 OnPause 和  OnContinue 方法來響應服務狀態的更改。一般須要重寫 OnStart 方法,結束服務時在 OnStop 方法中釋放資源,酌情重寫 OnPause 和  OnContinue方法。
        四、Windows服務一般啓動一個定時器來定時或輪詢進行業務處理。
        五、Windows服務須要安裝後才能使用。一般經過兩個辦法安裝Windows服務:
  • 在命令行運行InstallUtil.exe;
  • 在Windows服務程序的代碼中添加ProjectInstraller類的實例,裏面包含ServiceProcessInstaller類和ServiceInstaller類的實例。
        上述兩個辦法在Framework的隨機文檔中均有描述,在此再也不贅述。
        六、Windows服務在Windows的服務控制器(Service Control Manager, SCM)中運行,所以調試起來不像其餘Visual Studio應用程序那樣簡單。關於Windows服務的調試,在Visual Studio的隨機文檔裏面有介紹,在此再也不贅述。

3、Windows服務的異常處理

        Windows服務沒有用戶界面,在運行過程當中難以將異常通知給用戶。一般狀況下,Windows服務在運行過程當中發生了異常,可能致使服務運行掛起,但沒有任何提醒。
        推薦的一個作法是在Windows服務中捕獲異常,並把異常信息寫在Windows的事件日誌中。打開Windows的「控制面板/管理工具/事件查看器」,系統顯示Windows事件日誌。
        
 
        在一個實際的應用中,筆者除了把異常和提示記錄在事件日誌中,還把嚴重錯誤自動經過郵件發送給相關人員。同時,全部記錄在事件日誌中的信息,還重定向到一個自行開發的控制檯程序中,用以隨時監控服務。
        
 

3、Windows事件日誌的開發要點和技巧

        Visual Studio的隨機文檔裏,在介紹Windows服務程序的開發步驟的同時,也介紹瞭如何向Windows服務中加入事件日誌,筆者再也不贅述。開發要點以下:
        一、在須要寫入日誌的類中建立EventLog的實例eventLog,在構造函數里加入代碼:
     if (!System.Diagnostics.EventLog.SourceExists("mySource"))
     {        
     System.Diagnostics.EventLog.CreateEventSource("mySource","myEventLog");
     }
     eventLog.Source = " mySource ";
     eventLog.Log = " myEventLog ";
    二、在須要寫事件日誌的地方寫日誌,例如:
     protected override void OnStop()
     {
         eventLog.WriteEntry("In onStop.");
     }
 
         讀者能夠在實際應用中嘗試使用下面的技巧。
         一、把寫Windows事件日誌的代碼封裝成獨立的class,這樣不只在Windows服務中,並且在其餘的業務代碼中均可以使用Windows事件日誌。代碼見附件。
        二、爲方便調試和跟蹤,Visual Sdudio提供了Trace類。在應用程序的debug編譯版本中,用Trace類能夠把調試和跟蹤信息寫到控制檯。有一個技巧,能夠同時把寫入Trace的內容寫入Windows事件日誌。要點以下:
        首先聲明一個事件監聽類EventLogTraceListener的實例,
          static private  EventLogTraceListener cTraceListener = new EventLogTraceListener( m_eventLog );
        將EventLogTraceListener的實例加入Trace的監聽列表:
          Trace.Listeners.Add( cTraceListener );
        此後,凡是寫入Trace的調試信息,均寫入Windows事件日誌中。若是不但願將Trace繼續寫入事件日誌,運行下面代碼便可:
          Trace.Listeners.Remove( cTraceListener );
   
        三、寫入事件日誌的信息,還能夠同時寫入其餘應用程序窗體中的顯示控件。
        首先打開窗體的設計視圖,從工具箱/組件中選擇EventLog並加入窗體,配置EventLog的EnableRaisingEvents屬性爲True。
        加入EventLog的EntryWritten事件處理方法,該事件的第二個參數類行爲System.Diagnostics.EntryWrittenEventArgs,其中包含了Windows事件日誌條目中的必要內容,將該內容顯示在窗體中的某個顯示控件中便可。示例代碼以下:
/// <summary>
/// 監聽事件日誌
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void eventLog_EntryWritten(object sender,
     System.Diagnostics.EntryWrittenEventArgs e)
{
     try
     {
         // 把日誌內容寫到名爲listEventLog的List控件中
         listEventLog.Items.Insert( 0,
              e.Entry.TimeWritten + " " +
              e.Entry.Message );
 
         // List控件保存不超過500行的日誌
         while( listEventLog.Items.Count > 500 )
         {
              listEventLog.Items.RemoveAt( listEventLog.Items.Count-1 );
         }
     }
     catch( Exception ex )
     {
         MessageBox.Show( ex.Message );
     }
}

4、與Windows服務的通信

       在應用程序或其餘服務中,能夠與Windows服務通信,包括:
  •          管理Windows服務的生命期,即開啓、中止、暫停和重啓服務;
  •          得到Windows服務的屬性和狀態;
  •          得到特定計算機上的服務列表;
  •          向特定的服務發送命令。
        這些操做是經過ServiceController 類完成的。ServiceController是一個可視化控件,能夠在工具箱中找到。
        比較有意思的是ServiceController 中ExecuteCommand這個方法,調用這個方法,能夠向Windows服務發送命令,指揮Windows服務的一些操做。例如,在Windows服務的入口類中有一個複寫OnCustomCommand()的方法:
         /// <summary>
         /// 執行用戶自定義消息
         /// </summary>
         /// <param name="command">消息編號</param>
         protected override void OnCustomCommand( int command )
         {
              try
              {
                   switch( command )
                   {
                       case 1: // 業務操做
                            doBusiness1();
                            break;
                       case 2: //業務操做
                            doBusiness2();
                            break;
                       default:
                            ……
                            break;
                   }
              }
              catch( Exception ex )
              {
                   // 錯誤信息
                   string strErrorMsg = string.Format("異常:{0}/n", ex.Message );
                   // 寫日誌
                   TLineEventLog.DoWriteEventLog( strErrorMsg, EventType.Error );
                   // 給管理員發郵件
                   CMail.SendMail(
PropertyManager.strMailFromAddress, PropertyManager.strMailAdminAddress, "",
                       "異常信息提示",
strErrorMsg );
                   // 寫Trace
                   Trace.WriteLine( strErrorMsg );
              }
         }
        在另一個應用程序中經過ServiceController的ExecuteCommand()方法向這個Windows服務發送命令:
            myController.ExecuteCommand(2);
        Windows服務將執行業務方法:doBusiness2();
        應該認可,利用ServiceController與Windows服務通信的功能目前還十分薄弱。經過ExecuteCommand只能與Windows服務進行簡單而有限的通信。
        筆者在實際的應用中,分別用一個命令行程序、一個控制檯程序和一個Webservice和Windows服務進行通信,啓動、中止服務,或經過ExecuteCommand控制服務的行爲。
        
 

附件:操縱Windows事件日誌的通用類

using System;
using System.Diagnostics;
using System.Configuration;
 
namespace MYCOMMON.EVENTLOG
{
     public enum EventType { Error,Information,Warning }
     /// <summary>
     ///
     /// </summary>
     public class TLineEventLog
     {
         // 任務日誌
         static private EventLog m_eventLog = new EventLog();
         // 源名稱,從配置文件中讀取
         static private string m_strEventSource =
              ConfigurationSettings.AppSettings["f_eventLog.Source"].ToString().Trim();
         // 日誌名稱,從配置文件中讀取
         static private string m_strEventLog =
              ConfigurationSettings.AppSettings["f_eventLog.Log"].ToString().Trim();
 
         // 調試信息寫入日誌
         static private EventLogTraceListener cTraceListener =
              new EventLogTraceListener( m_eventLog );
 
         // 缺省構造函數。配置文件讀取失敗時,提供默認的源名稱和日誌名稱
         public TLineEventLog()
         {
              if( m_strEventSource.Length == 0 )
                   m_strEventSource = "mySource";
 
              if( m_strEventLog.Length == 0 )
                   m_strEventLog    = "myLog";
 
              m_eventLog.Source = m_strEventSource;
              m_eventLog.Log    = m_strEventLog;
         }
 
         // 構造函數。提供源名稱和日誌名稱。
         public TLineEventLog( string strEventSource, string strEventLog )
         {
              m_strEventSource = strEventSource;
              m_strEventLog    = strEventLog;
              m_eventLog.Source = m_strEventSource;
              m_eventLog.Log    = m_strEventLog;
     }
 
         /// <summary>
         /// 寫事件日誌
         /// </summary>
         /// <param name="strMessage">事件內容</param>
         /// <param name="eventType">事件類別,錯誤、警告或者消息</param>
         static public void DoWriteEventLog( string strMessage, EventType eventType )
         {
              if (!System.Diagnostics.EventLog.SourceExists( m_strEventSource ))
              {         
                   System.Diagnostics.EventLog.CreateEventSource(
                       m_strEventSource,m_strEventLog );
              }
 
              EventLogEntryType entryType = new EventLogEntryType();
              switch(eventType)
              {
                   case EventType.Error:      
                       entryType = EventLogEntryType.Error;
                       break;
                   case EventType.Information:
                       entryType = EventLogEntryType.Information;
                       break;
                   case EventType.Warning:    
                       entryType = EventLogEntryType.Warning;
                       break;
                   default:                   
                       entryType = EventLogEntryType.Information;
                       break;
              }
              m_eventLog.WriteEntry( strMessage, entryType );
         }
 
         /// <summary>
         /// 寫事件日誌,默認爲消息
         /// </summary>
         /// <param name="strMessage">事件內容</param>
         static public void DoWriteEventLog( string strMessage )
         {
              if (!System.Diagnostics.EventLog.SourceExists( m_strEventSource ))
              {        
                   System.Diagnostics.EventLog.CreateEventSource(
                       m_strEventSource,m_strEventLog );
              }
              m_eventLog.WriteEntry( strMessage );
         }
 
         /// <summary>
         /// 調試信息寫入日誌
         /// </summary>
         public static void OpenTrace()
         {
              if( cTraceListener != null )
              {
                   if( !Trace.Listeners.Contains( cTraceListener ) )
                   {
                       Trace.Listeners.Add( cTraceListener );
                   }
              }
         }
 
         /// <summary>
         /// 調試信息不寫入日誌
         /// </summary>
         public static void CloseTrace()
         {
              if( Trace.Listeners.IndexOf(cTraceListener) >= 0 )
              {
                   Trace.Listeners.Remove( cTraceListener );
              }
         }
     }
}
 
做者簡介:張昱,聯想利泰軟件公司(原聯想軟件設計中心) e-zhangyu@vip.sina.com
相關文章
相關標籤/搜索