zoukankan      html  css  js  c++  java
  • 编写 Window 服务程序

    编写 Window 服务程序
     
     

    一、直观认识Windows服务。

           打开Windows“控制面板/管理工具/服务”,系统显示Windows服务列表。
           
     
     
           双击服务,可以显示和更改服务属性。在这个对话框中,可以控制服务的启动、暂停和停止。在这里还可以配置服务的启动类型,令服务在系统启动时自行启动。因此,Windows服务经常作为服务器程序运行。
           
     
           在故障恢复这个属性页,可以配置该服务失败后系统的相应。一些病毒程序就是在这里做文章,将病毒程序激活的。
           
     
     

    二、Windows服务的开发要点

            Visual Studio的随机文档里,详细介绍了Windows服务程序的开发步骤,并且带有实例,笔者不再赘述。读者只需注意几个要点:
            1、创建一个派生自ServiceBase的入口类。这个入口类管理这个Windows服务的生存期。
     
         public class MyService : System.ServiceProcess.ServiceBase
         {
             ……
         }
     
            2、在入口类的main方法里将服务向Windows的服务控制器(Service Control Manager, SCM)注册,代码:
     
           ……
             System.ServiceProcess.ServiceBase[] ServicesToRun;
             ServicesToRun = new System.ServiceProcess.ServiceBase[] { new MyService() };
             System.ServiceProcess.ServiceBase.Run(ServicesToRun);
             ……
     
            3、重写 OnStart 、OnStop ,或OnPause 和 OnContinue 方法来响应服务状态的更改。通常需要重写OnStart 方法,结束服务时在 OnStop 方法中释放资源,酌情重写OnPause 和 OnContinue方法。
            4、Windows服务通常启动一个定时器来定时或轮询进行业务处理。
            5、Windows服务需要安装后才能使用。通常通过两个办法安装Windows服务:
    • 在命令行运行InstallUtil.exe;
    • 在Windows服务程序的代码中添加ProjectInstraller类的实例,里面包含ServiceProcessInstaller类和ServiceInstaller类的实例。
            上述两个办法在Framework的随机文档中均有描述,在此不再赘述。
            6、Windows服务在Windows的服务控制器(Service Control Manager, SCM)中运行,因此调试起来不像其他Visual Studio应用程序那样简单。关于Windows服务的调试,在Visual Studio的随机文档里面有介绍,在此不再赘述。

    三、Windows服务的异常处理

            Windows服务没有用户界面,在运行过程中难以将异常通知给用户。通常情况下,Windows服务在运行过程中发生了异常,可能导致服务运行挂起,但没有任何提醒。
            推荐的一个做法是在Windows服务中捕获异常,并把异常信息写在Windows的事件日志中。打开Windows的“控制面板/管理工具/事件查看器”,系统显示Windows事件日志。
            
     
            在一个实际的应用中,笔者除了把异常和提示记录在事件日志中,还把严重错误自动通过邮件发送给相关人员。同时,所有记录在事件日志中的信息,还重定向到一个自行开发的控制台程序中,用以随时监控服务。
            
     

    三、Windows事件日志的开发要点和技巧

            Visual Studio的随机文档里,在介绍Windows服务程序的开发步骤的同时,也介绍了如何向Windows服务中加入事件日志,笔者不再赘述。开发要点如下:
            1、在需要写入日志的类中创建EventLog的实例eventLog,在构造函数里加入代码:
         if (!System.Diagnostics.EventLog.SourceExists("mySource"))
         {        
         System.Diagnostics.EventLog.CreateEventSource("mySource","myEventLog");
         }
         eventLog.Source = " mySource ";
         eventLog.Log = " myEventLog ";
        2、在需要写事件日志的地方写日志,例如:
         protected override void OnStop()
         {
             eventLog.WriteEntry("In onStop.");
         }
     
             读者可以在实际应用中尝试使用下面的技巧。
             1、把写Windows事件日志的代码封装成独立的class,这样不仅在Windows服务中,而且在其他的业务代码中都可以使用Windows事件日志。代码见附件。
            2、为方便调试和跟踪,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 );
       
            3、写入事件日志的信息,还可以同时写入其他应用程序窗体中的显示控件。
            首先打开窗体的设计视图,从工具箱/组件中选择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 );
         }
    }

    四、与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
  • 相关阅读:
    bzoj 1295 [SCOI2009]最长距离 最短路
    bzoj 3669 [Noi2014]魔法森林
    bzoj 1432 [ZJOI2009]Function 思想
    用JSP输出Hello World
    Web开发基础
    JSP相关背景
    JSP概述
    Java视频播放器的制作
    为JFileChooser设定扩展名过滤
    使用JFileChooser保存文件
  • 原文地址:https://www.cnblogs.com/mq0036/p/3860364.html
Copyright © 2011-2022 走看看